My personal types in js focused tsconfig bases.
Are meant to be used with javascript code, not typescript code, hence they're having eg. noEmit: true
set.
npm install --save-dev @voxpelli/tsconfig
Then add an extends
to your tsconfig.json
like this:
{
"extends": "@voxpelli/tsconfig/node20.json",
"files": [
"index.js"
],
"include": [
"test/**/*",
]
}
base-node-bare
β where most of the configuration is set (Node.js focused)base-node-jsdoc
β adds JSDoc related config tobase-node-bare
These extends base-node-jsdoc
with the correct lib
, module
, moduleResolution
and target
for each Node.js version.
Inspired by tsconfig/bases.
node14
deprecatednode16
deprecatednode18
node20
node22
node24
nodenext
(currently an alias forbase-node-jsdoc
)
Absolutely, my pleasure!
Just as with voxpelli/eslint-config I follow Semantic Versioning and thus won't do any breaking changes in any non-major releases.
Give me a ping if you use it, would be a delight to know you like it π
When publishing a module, no matter if we use JSDoc or TS syntax we need to publish type declarations.
Here's how to generate type declarations when using JSDoc,
Add a new declaration specific tsconfig (eg. declaration.tsconfig.json
) that extends your base tsconfig. Something like:
{
"extends": "./tsconfig",
"files": [],
"exclude": [
"test/**/*.js"
],
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"noEmit": false,
"emitDeclarationOnly": true
}
}
The above excludes all top level files and all files in test/
from having types generated. If one wants eg. index.js
to have auto-generated types, then one needs to either remove "files": [],
to use the inherited value or explicitly add it ("files": ["index.js"],
).
If one uses eg. @deprecated
and wants to retain JSDoc comments in ones type declarations, then one should set "removeComments": false
in the compilerOptions
. By default @voxpelli/tsconfig
sets "removeComments": true
to keep generated types lean and DRY.
We should add scripts that uses the config file. These are examples add them to "scripts"
in package.json
and uses npm-run-all2
to give clean separation and enable parallel execution.
"build:0": "run-s clean",
"build:1-declaration": "tsc -p declaration.tsconfig.json",
"build": "run-s build:*",
When we run build
we sequentially run all build:*
using run-s
.
- First we run
clean
to remove any pre-existing generated type declarations (as else they will be used as the source) - Then we run
tsc
which generates the new type declarations thanks to it using the declaration specific tsconfig
"clean:declarations-top": "rm -f $(find . -maxdepth 1 -type f -name '*.d.ts*' ! -name 'index.d.ts')",
"clean:declarations-lib": "rm -f $(find lib -type f -name '*.d.ts*' ! -name '*-types.d.ts')",
"clean": "run-p clean:*",
When we run clean
we run all clean:*
in parallel using run-p
.
Both clean commands use rm -f
to delete a list of files found through find
. The -f
flag is needed since find
may return an empty list, which without -f
causes rm
to fail.
The find
command returns all matching type declaration files. It uses three flags:
-maxdepth 1'
is used when running in.
to avoid recursing intonode_modules
(as we of course do not want to remove any type declarations in there)-name '*.d.ts*'
limits matching file names to.d.ts
and.d.ts.map
files. (If you generate.mts
or.cts
as well, then change this to*.d.*ts*
)-type f
ensures that only files are returned
The two clean scripts are:
clean:declarations-top
cleans all top level (.
) type declarations except forindex.d.ts
(as that's often hand coded instead). One can remove the! -name 'index.d.ts'
or add additional! -name
sections to tweak what is ignored.clean:declarations-lib
recursively cleans all type declarations inlib
except for those ending with-types.d.ts
(as our naming convention is that all such files are hand coded). One can add additional! -name
sections to ignore further files.
Assuming that we have something like the following that checks our types (if you're not using type-coverage
you should start!):
"check:tsc": "tsc",
"check:type-coverage": "type-coverage --detail --strict --at-least 99 --ignore-files 'test/*'",
Then we should make sure that we run clean
before we run our checks as else tsc
will use the type declarations rather than the JSDoc types when validating.
So we should do something like the following, it first runs clean
, then runs check:*
in parallel.
"check": "run-s clean && run-p check:*",
Lastly we should make sure that we generate the files before publish, something we can do by eg. adding a prepublishOnly
life cyle script:
"prepublishOnly": "run-s build",
And something like this in your .gitignore
:
# Generated types
*.d.ts
*.d.ts.map
!/lib/**/*-types.d.ts
!/index.d.ts
The ignores here (!/lib/**/*-types.d.ts
, !/index.d.ts
) matches the ignores we added in our clean:*
scripts
See my voxpelli/node-module-template
repository for a fully functioning example of my current (and hopefully up to date) reference of this pattern.
- voxpelli/eslint-config β the reusable ESLint setup I use in my projects
- voxpelli/ghatemplates β the reusable GitHub Actions workflows I use in my projects
- voxpelli/renovate-config-voxpelli β the reusable Renovate setup I use in my projects