node.js - TS Jest, NodeJS 16 ve TypeScript ile üst seviye awaitleri kabul etmiyor


NodeJS 12 ve TypeScript uygulamamı Node16’ya güncellemeye çalışıyorum, nedenlerden biri de en üst düzey await ifadelerini kullanma ihtiyacı.

Güncellemeden sonra kod doğru şekilde derleniyor, ancak Jest belirli üst seviye await kodunu kabul etmiyor:

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 } },
};

Burada neyin yanlış olduğunu gerçekten anlayamıyorum. Bir fikrin var mı?

Cevap #1

Biraz araştırmadan sonra, size kesinlikle söyleyebilirim ki hâlâ güvenilir bir çözüme çok uzağız. Ana sorun, üst düzey await'in kullanılabilir olmasıdır

'module' seçeneği 'es2022', 'esnext', 'system' veya 'nodenext' olarak ayarlandığında ve 'target' seçeneği 'es2017' veya daha yüksek olarak ayarlandığında.

Her ne kadar tsconfig.json dosyasında hem module hem de target değerlerini es2022 olarak ayarlasanız da, deneyimlediğiniz gibi birçok hatayı anlamanız ve çözmeniz gerekecek. Şu anda benim için çalışan bir yapılandırma buldum, ancak farkında olmadığım başka sorunlar da ortaya çıkabilir.


node --version
v17.7.2
npm list
[email  /home/me/folder
├── @types/[email 
├── @types/[email 
├── [email 
├── [email 
├── [email 
├── [email 
├── [email 
├── [email 
├── ...
└── [email 

Başka kütüphanelerim var, ama onların yeniden uyarlanması gerekmedi. Test için jest kullanıyorum ve ORM olarak sequelize: bunların uygun şekilde yapılandırılması gerekiyor. package.json'dan başlayalım:

{
    "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" burada açıklanmıştır
  • scripts bölümündeki start komutu, varsayılan CommonJS modülleri yerine ES Modüllerini etkinleştirmek için gerekli olan --es-module-specifier-resolution=node seçeneğine ihtiyaç duyar.
  • (Sadece Jest için): --no-warnings zorunlu değil, ama --experimental-vm-modules zorunludur.

Jest yapılandırmalarına gelince:

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;

Elbette yeni özellikler ekleyebilirsiniz, ancak bu aşağı yukarı minimum yapılandırmadır. Diğer ön ayarları burada bulabilirsiniz (ihtiyacınız olursa), ts-jest için globals bölümü ise burada daha detaylı açıklanmıştır.


Biraz daha karmaşık olan tsconfig.json'a bakalım:

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"]
}

Yine, bu az çok minimum yapılandırma, sanırım module ve target özellikleri, üst düzey await kullanmaya çalışırken gösterilen hatadaki diğer alternatiflerle başlatılabilir, ama hepsinin çalıştığından emin değilim.


Şimdi en kötü kısma, paketlerin ES modülleri ile uyumluluğuna geldik. Hangi paketten bir import işlemine uyarlama yapılması gerektiğini size söyleyecek kadar araştırma yapmadım, ama size bazı örnekler sunacağım.

Yerleşik kütüphaneler/config/timespan

Dahili kütüphaneler oldukları gibi sorun olmamalıdır. Projemde ayrıca config ve timespan kullanıyorum:

  • timespan: import * as timespan from "timespan". Timespan nesnesine örneğin new timespan.Timespan(...) kullanarak erişilebilir.
  • config: config'den config'i içe aktar.
  • util: yerleşik paketlerle, bence CommonJS sözdizimini kullanıyormuş gibi herhangi bir dışa aktarılmış fonksiyonu içe aktarabilirsiniz. Örneğin, import { format } from 'util'.

sequelize (ve birçok diğerleri...)

Typescript ile sequelize kullanıyorum, ama import sözdizimini değiştirmem gerekti. Karşılaştığım hata şudur:

SyntaxError: İstenen modül 'sequelize', 'DataTypes' adında bir ihracat sunmuyor at ...

Sorun şu ki deneysel modüller adlandırılmış ihracatı desteklemiyor. Yeni import sözdizimi, neyi içe aktarmanız gerektiğine bağlıdır:

  • tipler için, onları eskisi gibi hâlâ şu şekilde ithal edebilirsiniz: import { Type1, Type2 } from 'your_library'
  • sınıflar için (ama bence değer alanından gelen her şey için), önce varsayılan bir nesneyi içe aktarmanız ve ardından gerekli değerleri çıkarmak için nesne yapılandırmasını kullanmanız gerekir.

Örneğin, sequelize ile:

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>
    ...
}

Ek kaynaklar

  • Üst düzey await kullanılamıyor
  • Modülün dışa aktarım sağlamadığına dair yanıltıcı hata
  • --experimental-modules ile içe aktarma hatası
  • 2022'de Typescript için Top-level Await (ayrıca nodemon yapılandırmak için faydalı)

İlgili makaleler