TypeScript+ESLint+Jest project template to import modules without extension, and modules with default exports that use node built-ins
MIT License
Minimalistic example of configuring TypeScript and Node to:
apollo-server
, node-influx
)?.
Bonus: continuous integration script for GitHub Actions. It automatically runs tests on every pushed commit.
There is no need for Babel.
In tsconfig.json
, set this in compilerOptions
:
"target": "esnext",
"module": "esnext", // Output `import`/`export` ES modules
http
, url
etc.)npm install --save-dev @types/node
tsconfig.json
under compilerOptions
, set
"moduleResolution": "node"
, so tsc
can find modules when targeting ES6+
"types": ["node"]
to avoid errors related to Node built-in modulesNormally we could write in TypeScript
import { InfluxDB } from 'influx';
but when generating ES modules code, that statement will be passed through as is, and will cause Node to fail with
SyntaxError: The requested module 'influx' does not provide an export named 'InfluxDB'
because node-influx
doesn't provide named exports (and neither does an even more popular module, apollo-server
).
One alternative would be to generate old ugly commonjs modules code by,
"type": "module"
line from package.json
, and"module": "CommonJS"
in tsconfig.json
(allowSyntheticDefaultImports
also becomes unnecessary)What we'll do is import the entire module:
import Influx from 'influx';
const influx = new Influx.InfluxDB();
However, this will generate Error TS1192: Module '...' has no default export.
To prevent that, set "allowSyntheticDefaultImports": true
in tsconfig.json
.
When transpiling, TypeScript won't generate an extension for you. Run Node with the node --experimental-specifier-resolution=node
parameter:
node --experimental-specifier-resolution=node run.js
Otherwise, node mandates that you specify the extension in the import
statement.
To support optional chaining, add the --harmony
flag to the node command line.
Add "type": "module"
to package.json
, because TypeScript can't generate files with the .mjs extension.
To be able to run eslint
, we must create an .eslintrc.cjs
file, rather than a .js
one (due to "type": "module"
in package.json
). Then, install the required dependencies:
npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
Here's the diff to add ESLint support.
The cleanest way to fully support Jest with TypeScript, ES Modules and ESLint, is to use ts-jest
, which has a number of advantages over using Babel. What we need to do:
npm install --save-dev jest @types/jest eslint-plugin-jest
- for ES Lint support"jest"
to the types
array in tsconfig.json
'jest'
plugin to .eslintrc.cjs
and also add 'jest/globals': true
to its env
keyjest.config.cjs
generated by ts-jest config:init
(just renamed .js -> .cjs).Normally, to run Jest from package.json
, we'd add a "test": "jest"
line. That won't be sufficient, because we need to pass the --harmony
flag to node (for optional chaining support).
To pass parameters to Node when running Jest, we'll add the following test
line:
"test": "node --harmony node_modules/.bin/jest"
The only caveat here is that Jest seems to prefer generated .js
files over their .ts
originals, so we'll exclude them via jest.config.cjs
:
testRegex: '.*.test.ts', // test filenames matching this regex
moduleFileExtensions: ['ts', 'js'], // modules are only in .ts files, but 'js' *must* be specified too
If your script generates an error, you'll see the line numbers from the generated .js
files, which is not helpful. We want to see the original paths and line numbers from the .ts
files. To do that, we'll add sourceMap: true
to tsconfig.json
, install source-map-support
and run node with the -r source-map-support/register
parameter. Note that Jest already takes care of source mapping so you'll see the .ts
line numbers without having to do anything extra.
Here's the diff to add source map support.
Using GitHub Actions, we can configure automatic testing via .yml
files under .github/workflows.
The tsconfig.json
settings generate .js
built files, and .js.map
source map files, next to the original .ts
file. While some IDEs conveniently hide these files, it may be desirable to output them in a separate directory, typically dist
.
This can be done using the rootDir
/outDir
settings in tsconfig.json
, but that sort of setup comes with an annoying limitation of Typescript that forbids importing files outside the rootDir
. That can be a problem with monorepos.