Jest 不会接受 NodeJS16 和 TypeScript 的顶级等待
Jest won't accept top-level-awaits with NodeJS16 & TypeScript
我正在尝试将我的 NodeJS12 和 TypeScript 应用程序更新到 Node16,如果原因是需要使用顶级等待。
更新后代码编译正确,但 Jest 不接受特定的顶级等待代码:
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 } },
};
我真的不明白这里出了什么问题。有什么想法吗?
最近突然想到,据我所知,CJS 不支持top-level await,这意味着你需要使用ts-jest 和esm。
我的jest.config.json
{
"extensionsToTreatAsEsm": [".ts"],
"globals": {
"ts-jest": {
"useESM": true
}
},
"preset": "ts-jest/presets/default-esm",
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": "spec.ts$",
"modulePathIgnorePatterns": ["<rootDir>/dist/", "<rootDir>/node_modules/"],
"moduleNameMapper": {
"^src/(.*)": "<rootDir>/src/"
},
"collectCoverage": true,
"coverageDirectory": "./coverage",
"collectCoverageFrom": ["src/**/*.(t|j)s"],
"coveragePathIgnorePatterns": [
".module.ts$",
".spec.ts$",
"src/database/",
"src/server.ts"
],
"verbose": true
}
您还需要 运行 使用 --experimental-vm-modules 选项开玩笑
node --experimental-vm-modules -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand --config jest.overall.json
经过一些研究,我可以肯定地告诉你,我们离可靠的解决方案还很远。主要问题是 top-level await
可用
when the 'module' option is set to 'es2022', 'esnext', 'system', or 'nodenext', and the 'target' option is set to 'es2017' or higher.
即使你在tsconfig.json
中将module
和target
都设置为es2022
,你也必须理解和解决很多错误,因为你有有经验的。我刚刚找到适合我的配置现在,但它可能会出现我不知道的其他问题。
$ node --version
v17.7.2
$ npm list
myproject@1.0.0 /home/me/folder
├── @types/jest@27.4.1
├── @types/node@17.0.25
├── config@3.3.7
├── jest@27.5.1
├── sequelize@6.18.0
├── timespan@2.3.0
├── ts-jest@27.1.4
├── ts-node@10.7.0
├── ...
└── typescript@4.6.3
我还有其他库,但不需要重新改编。我使用 jest
进行测试,sequelize
作为 ORM:它们需要适当配置。让我们从 package.json
:
开始
{
"type": "module",
"scripts": {
"prestart": "npx tsc",
"start": "NODE_ENV=production node --es-module-specifier-resolution=node out/index.js",
"test": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/"
},
...
}
"type": "module"
说明here
scripts
部分中的 start
命令需要选项 --es-module-specifier-resolution=node
,这是启用 ES 模块而不是默认的 CommonJS 模块所必需的。
- (仅适用于 Jest):
--no-warnings
不是强制性的,但 --experimental-vm-modules
是。
至于 Jest 配置:
$ cat jest.config.ts
import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
globals: {
"ts-jest": {
useESM: true
}
},
preset: 'ts-jest/presets/default-esm',
roots: ["tests/"],
modulePathIgnorePatterns: [
"<rootDir>/node_modules/"
]
};
export default config;
当然,您可以添加新属性,但这或多或少是最低配置。您可以找到其他预设 here (in case you need them), while the globals
section for ts-jest
is furtherly explained here.
让我们看看tsconfig.json
,它有点复杂:
$ cat tsconfig.json
{
"ts-node": {
"moduleTypes": {
"jest.config.ts": "cjs"
}
},
"compilerOptions": {
"lib": ["ES2021"],
"module": "ESNext",
"esModuleInterop": true,
"moduleResolution": "node",
"target": "ESNext",
"outDir": "out",
"resolveJsonModule": true
},
"compileOnSave": true,
"include": ["src/"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
同样,它或多或少是最小配置,我想 module
和 target
属性可以用尝试使用 [=136= 时显示的错误中的其他选项初始化] await
,但我不确定它们是否都在工作。
现在最糟糕的是包与 ES 模块的兼容性。我没有研究足够告诉你什么时候来自某个包的 import
需要调整,但我会为你提供一些例子。
Built-in 图书馆/config
/timespan
Built-in 图书馆应该没问题。在我的项目中,我还使用了 config
和 timespan
:
timespan
:import * as timespan from "timespan"
。例如,可以使用 new timespan.Timespan(...)
访问 Timespan
对象。
config
: import config from 'config'
.
util
:使用 built-in 包,我认为您可以像使用 CommonJS 语法一样导入任何导出的函数。例如,import { format } from 'util'
.
sequelize
(以及许多其他...)
我在打字稿中使用 sequelize
,但我不得不更改 import
语法。我遇到的错误是:
SyntaxError: The requested module 'sequelize' does not provide an export named 'DataTypes' at ...
问题是experimental modules do not support named exports。新的 import
语法取决于您需要导入的内容:
- 对于类型,您仍然可以像以前一样导入它们,例如
import { Type1, Type2 } from 'your_library'
- 对于类(但我认为一切都来自值space),你首先需要导入一个默认对象,然后使用对象解构来提取需要的值。
例如 sequelize
:
import Sequelize,
{ InferAttributes, InferCreationAttributes, CreationOptional }
from 'sequelize'; // InferAttributes, InferCreationAttributes, CreationOptional are types
const { Op, DataTypes, Model } = Sequelize // these are classes
class UserModel extends Model<InferAttributes<UserModel>,
InferCreationAttributes<UserModel>>
{
declare id: CreationOptional<number>
...
}
其他来源
我正在尝试将我的 NodeJS12 和 TypeScript 应用程序更新到 Node16,如果原因是需要使用顶级等待。
更新后代码编译正确,但 Jest 不接受特定的顶级等待代码:
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 } },
};
我真的不明白这里出了什么问题。有什么想法吗?
最近突然想到,据我所知,CJS 不支持top-level await,这意味着你需要使用ts-jest 和esm。
我的jest.config.json
{
"extensionsToTreatAsEsm": [".ts"],
"globals": {
"ts-jest": {
"useESM": true
}
},
"preset": "ts-jest/presets/default-esm",
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": "spec.ts$",
"modulePathIgnorePatterns": ["<rootDir>/dist/", "<rootDir>/node_modules/"],
"moduleNameMapper": {
"^src/(.*)": "<rootDir>/src/"
},
"collectCoverage": true,
"coverageDirectory": "./coverage",
"collectCoverageFrom": ["src/**/*.(t|j)s"],
"coveragePathIgnorePatterns": [
".module.ts$",
".spec.ts$",
"src/database/",
"src/server.ts"
],
"verbose": true
}
您还需要 运行 使用 --experimental-vm-modules 选项开玩笑
node --experimental-vm-modules -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand --config jest.overall.json
经过一些研究,我可以肯定地告诉你,我们离可靠的解决方案还很远。主要问题是 top-level await
可用
when the 'module' option is set to 'es2022', 'esnext', 'system', or 'nodenext', and the 'target' option is set to 'es2017' or higher.
即使你在tsconfig.json
中将module
和target
都设置为es2022
,你也必须理解和解决很多错误,因为你有有经验的。我刚刚找到适合我的配置现在,但它可能会出现我不知道的其他问题。
$ node --version
v17.7.2
$ npm list
myproject@1.0.0 /home/me/folder
├── @types/jest@27.4.1
├── @types/node@17.0.25
├── config@3.3.7
├── jest@27.5.1
├── sequelize@6.18.0
├── timespan@2.3.0
├── ts-jest@27.1.4
├── ts-node@10.7.0
├── ...
└── typescript@4.6.3
我还有其他库,但不需要重新改编。我使用 jest
进行测试,sequelize
作为 ORM:它们需要适当配置。让我们从 package.json
:
{
"type": "module",
"scripts": {
"prestart": "npx tsc",
"start": "NODE_ENV=production node --es-module-specifier-resolution=node out/index.js",
"test": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/"
},
...
}
"type": "module"
说明herescripts
部分中的start
命令需要选项--es-module-specifier-resolution=node
,这是启用 ES 模块而不是默认的 CommonJS 模块所必需的。- (仅适用于 Jest):
--no-warnings
不是强制性的,但--experimental-vm-modules
是。
至于 Jest 配置:
$ cat jest.config.ts
import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
globals: {
"ts-jest": {
useESM: true
}
},
preset: 'ts-jest/presets/default-esm',
roots: ["tests/"],
modulePathIgnorePatterns: [
"<rootDir>/node_modules/"
]
};
export default config;
当然,您可以添加新属性,但这或多或少是最低配置。您可以找到其他预设 here (in case you need them), while the globals
section for ts-jest
is furtherly explained here.
让我们看看tsconfig.json
,它有点复杂:
$ cat tsconfig.json
{
"ts-node": {
"moduleTypes": {
"jest.config.ts": "cjs"
}
},
"compilerOptions": {
"lib": ["ES2021"],
"module": "ESNext",
"esModuleInterop": true,
"moduleResolution": "node",
"target": "ESNext",
"outDir": "out",
"resolveJsonModule": true
},
"compileOnSave": true,
"include": ["src/"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
同样,它或多或少是最小配置,我想 module
和 target
属性可以用尝试使用 [=136= 时显示的错误中的其他选项初始化] await
,但我不确定它们是否都在工作。
现在最糟糕的是包与 ES 模块的兼容性。我没有研究足够告诉你什么时候来自某个包的 import
需要调整,但我会为你提供一些例子。
Built-in 图书馆/config
/timespan
Built-in 图书馆应该没问题。在我的项目中,我还使用了 config
和 timespan
:
timespan
:import * as timespan from "timespan"
。例如,可以使用new timespan.Timespan(...)
访问Timespan
对象。config
:import config from 'config'
.util
:使用 built-in 包,我认为您可以像使用 CommonJS 语法一样导入任何导出的函数。例如,import { format } from 'util'
.
sequelize
(以及许多其他...)
我在打字稿中使用 sequelize
,但我不得不更改 import
语法。我遇到的错误是:
SyntaxError: The requested module 'sequelize' does not provide an export named 'DataTypes' at ...
问题是experimental modules do not support named exports。新的 import
语法取决于您需要导入的内容:
- 对于类型,您仍然可以像以前一样导入它们,例如
import { Type1, Type2 } from 'your_library'
- 对于类(但我认为一切都来自值space),你首先需要导入一个默认对象,然后使用对象解构来提取需要的值。
例如 sequelize
:
import Sequelize,
{ InferAttributes, InferCreationAttributes, CreationOptional }
from 'sequelize'; // InferAttributes, InferCreationAttributes, CreationOptional are types
const { Op, DataTypes, Model } = Sequelize // these are classes
class UserModel extends Model<InferAttributes<UserModel>,
InferCreationAttributes<UserModel>>
{
declare id: CreationOptional<number>
...
}