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
awaitis availableEven if you set both
moduleandtargettoes2022in 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
jestfor testing andsequelizeas ORM: they need to be configured appropriately. Let's start from thepackage.json:"type": "module"is explained herestartcommand in thescriptssection needs the option--es-module-specifier-resolution=node, necessary to enable ES Modules instead of the default CommonJS modules.--no-warningsis not mandatory, but--experimental-vm-modulesis.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
globalssection forts-jestis 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
moduleandtargetproperties 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
importfrom a certain package needs to be adapted, but I will provide you with some examples.Built-in libraries/
config/timespanBuilt-in libraries should be ok as they are. In my project I am also using
configandtimespan:timespan:import * as timespan from "timespan". TheTimespanobject 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
sequelizewith typescript, but I had to change theimportsyntax. The error I have encountered is:The problem is that experimental modules do not support named exports. The new
importsyntax depends on what you need to import:import { Type1, Type2 } from 'your_library'For example, with
sequelize:Additional sources
nodemontoo)