I'm trying to update my NodeJS 12 & TypeScript app to Node16, one of the reasons is the need to use top-level-awaits.
The code compiles correctly after the update, but Jest won't accept the specific top-level-await code:
ts-jest[ts-compiler] (WARN) src/xxx.ts:11:17 - error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', or 'nodenext', and the 'target' option is set to 'es2017' or higher.
11 const project = await client.getProjectId();
~~~~~
FAIL src/xxx.test.ts
● Test suite failed to run
Jest encountered an unexpected token
package.json:
{
"name": "functions",
"scripts": {
"lint": "eslint --ext .js,.ts .",
"lint:fix": "eslint --ext .js,.ts . --fix",
"build": "tsc -b",
"build:watch": "tsc-watch",
"serve": "...",
"test": "env-cmd -f .env.json jest --runInBand --verbose"
},
"type": "module",
"engines": {
"node": "16"
},
"main": "lib/index.js",
"exports": "./lib/index.js",
"dependencies": {
"test": "^1.0.0"
},
"devDependencies": {
"@google-cloud/functions-framework": "^2.1.0",
"@types/busboy": "^1.3.0",
"@types/compression": "1.7.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.12",
"@types/express-serve-static-core": "^4.17.28",
"@types/google-libphonenumber": "^7.4.23",
"@types/jest": "^27.4.0",
"@types/jsonwebtoken": "^8.5.7",
"@types/luxon": "^2.0.9",
"@types/node-zendesk": "^2.0.6",
"@types/sinon": "^10.0.6",
"@types/supertest": "^2.0.11",
"@types/swagger-ui-express": "^4.1.3",
"@types/uuid": "^8.3.4",
"@types/yamljs": "^0.2.31",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"env-cmd": "^10.1.0",
"eslint": "^8.6.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react-hooks": "^4.2.1-beta-a65ceef37-20211130",
"jest": "^27.4.7",
"prettier": "^2.5.1",
"sinon": "^12.0.1",
"supertest": "^6.2.1",
"ts-jest": "^27.1.3",
"ts-node": "^10.4.0",
"tsc-watch": "^4.6.0",
"typescript": "^4.5.4"
},
"private": true
}
tsconfig.json:
{
"compilerOptions": {
"module": "es2022",
"noImplicitReturns": true,
"noUnusedLocals": true,
"outDir": "lib",
"sourceMap": false,
"strict": true,
"target": "es2021",
"moduleResolution": "Node",
"resolveJsonModule": true
},
"compileOnSave": true,
"include": [
"src"
],
"ts-node": {
"moduleTypes": {
"jest.config.ts": "cjs"
}
}
}
jest.config.ts:
export default {
roots: [
'<rootDir>/src'
],
setupFiles: ['./src/setupJestEnv.ts'],
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['/node_modules/'],
coverageDirectory: './coverage',
coveragePathIgnorePatterns: ['node_modules', 'src/database', 'src/test', 'src/types'],
globals: { 'ts-jest': { diagnostics: false } },
};
I can't really understand what's wrong here. Any idea?
After some research, I can certainly tell you that we are still far from a reliable solution. The main problem is that the top-level
await
is availableEven if you set both
module
andtarget
toes2022
in thetsconfig.json
, you will have to understand and solve a lot of errors, as you have experienced. I have just found a configuration that works for me now, but it might present other problems I am not aware of.I have other libraries, but they did not have to be readapted. I am using
jest
for testing andsequelize
as ORM: they need to be configured appropriately. Let's start from thepackage.json
:"type": "module"
is explained herestart
command in thescripts
section needs the option--es-module-specifier-resolution=node
, necessary to enable ES Modules instead of the default CommonJS modules.--no-warnings
is not mandatory, but--experimental-vm-modules
is.As for the Jest configurations:
You can add new properties, of course, but this is more or less the minimal configuration. You can find the other presets here (in case you need them), while the
globals
section forts-jest
is furtherly explained here.Let's see the
tsconfig.json
, which is a bit more complex:Again, it is more or less the minimal configuration, I guess
module
andtarget
properties can be initialized with the other alternatives in the error shown when trying to use the top-levelawait
, but I am not sure they are all working.Now to the worst part, the packages' compatibility with ES modules. I have not studied enough to tell you when an
import
from a certain package needs to be adapted, but I will provide you with some examples.Built-in libraries/
config
/timespan
Built-in libraries should be ok as they are. In my project I am also using
config
andtimespan
:timespan
:import * as timespan from "timespan"
. TheTimespan
object is accessible usingnew timespan.Timespan(...)
for example.config
:import config from 'config'
.util
: with built-in packages, I think you can import any exported function as if you are using the CommonJS syntax. For instance,import { format } from 'util'
.sequelize
(and many others...)I am using
sequelize
with typescript, but I had to change theimport
syntax. The error I have encountered is:The problem is that experimental modules do not support named exports. The new
import
syntax depends on what you need to import:import { Type1, Type2 } from 'your_library'
For example, with
sequelize
:Additional sources
nodemon
too)