NX 错误 [ERR_REQUIRE_ESM] - 快速应用程序 - 无法导入 ESM 库

BSi*_*son 6 nrwl-nx

我最近创建了一个 nx 工作区并添加到 Express 应用程序中。正如您所期望的,这是开箱即用的。但是,一旦我添加对 ESM 库(例如 nanoid v4+)的依赖项,我就无法启动服务器,并且收到以下错误消息。

Error [ERR_REQUIRE_ESM]: require() of ES Module /workspace/node_modules/nanoid/index.js not supported

配置 NX 工作区和 Express 应用程序以便能够导入 ESM 库和本地工作区库并成功测试/构建/提供应用程序的正确方法是什么?

  • npx nx run my-express-api:test
  • npx nx run my-express-api:build
  • npx nx run my-express-api:serve

尝试1

这是开箱即用的尝试

错误:

Error [ERR_REQUIRE_ESM]: require() of ES Module /workspace/node_modules/nanoid/index.js not supported.

Instead change the require of index.js in null to a dynamic import() which is available in all CommonJS modules.
    at Module._load (/workspace/node_modules/@nx/js/src/executors/node/node-with-require-overrides.js:18:31)
    at Array.__webpack_modules__ (/workspace/dist/apps/my-express-api/main.js:26:18)
    at __webpack_require__ (/workspace/dist/apps/my-express-api/main.js:49:41)
    at /workspace/dist/apps/my-express-api/main.js:69:18
    at /workspace/dist/apps/my-express-api/main.js:81:3
    at Object.<anonymous> (/workspace/dist/apps/my-express-api/main.js:83:12)
    at Module._load (/workspace/node_modules/@nx/js/src/executors/node/node-with-require-overrides.js:10:31)
Run Code Online (Sandbox Code Playgroud)

配置:

快速应用程序tsconfig.app.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/out-tsc",
    "module": "commonjs",
    "types": ["node", "express"]
  },
  "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
  "include": ["src/**/*.ts"]
}
Run Code Online (Sandbox Code Playgroud)

快速应用程序webpack.config.js

const { NxWebpackPlugin } = require('@nx/webpack');
const { join } = require('path');

module.exports = {
  output: {
    path: join(__dirname, '../../dist/apps/my-express-api'),
  },
  plugins: [
    new NxWebpackPlugin({
      target: 'node',
      compiler: 'tsc',
      main: './src/main.ts',
      tsConfig: './tsconfig.app.json',
      assets: ['./src/assets'],
      optimization: false,
      outputHashing: 'none',
    }),
  ],
};
Run Code Online (Sandbox Code Playgroud)

快递应用程序project.json

{
  "name": "my-express-api",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "apps/my-express-api/src",
  "projectType": "application",
  "targets": {
    "serve": {
      "executor": "@nx/js:node",
      "defaultConfiguration": "development",
      "options": {
        "buildTarget": "my-express-api:build"
      },
      "configurations": {
        "development": {
          "buildTarget": "my-express-api:build:development"
        },
        "production": {
          "buildTarget": "my-express-api:build:production"
        }
      }
    }
  },
  "tags": []
}
Run Code Online (Sandbox Code Playgroud)

根工作区package.json

{
  "name": "nx-template",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "start": "nx serve",
    "build": "nx build",
    "test": "nx test"
  },
  "private": true,
  "dependencies": {
    "@emotion/react": "11.11.1",
    "@emotion/styled": "11.11.0",
    "axios": "^1.6.0",
    "express": "^4.18.1",
    "lodash-es": "^4.17.21",
    "nanoid": "^5.0.5",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "tslib": "^2.3.0"
  },
  "devDependencies": {
    "@emotion/babel-plugin": "11.11.0",
    "@nx/eslint": "18.0.4",
    "@nx/eslint-plugin": "18.0.4",
    "@nx/express": "18.0.4",
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

尝试2

修改 tsconfig/webpack 设置。

这些修改并不理想,但这确实允许我导入 ESM 库。但是,我现在无法导入本地工作区库。

错误:

ERROR in ./apps/my-express-api/src/main.ts 9:0-35
Module not found: Error: Can't resolve 'test-lib' in '/workspace/apps/my-express-api/src'
resolve 'test-lib' in '/workspace/apps/my-express-api/src'
  Parsed request is a module
  using description file: /workspace/apps/my-express-api/package.json (relative path: ./src)
    resolve as module
      /workspace/apps/my-express-api/src/node_modules doesn't exist or is not a directory
      /workspace/apps/my-express-api/node_modules doesn't exist or is not a directory
      /workspace/apps/node_modules doesn't exist or is not a directory
      looking for modules in /workspace/node_modules
        single file module
          using description file: /workspace/package.json (relative path: ./node_modules/test-lib)
            no extension
              /workspace/node_modules/test-lib doesn't exist
            .js
              /workspace/node_modules/test-lib.js doesn't exist
            .mjs
              /workspace/node_modules/test-lib.mjs doesn't exist
            .ts
              /workspace/node_modules/test-lib.ts doesn't exist
            .mts
              /workspace/node_modules/test-lib.mts doesn't exist
        /workspace/node_modules/test-lib doesn't exist
      /node_modules doesn't exist or is not a directory
Run Code Online (Sandbox Code Playgroud)

配置:

项目.json

...
    "build": {
  "executor": "@nx/webpack:webpack",
  "dependsOn": ["lint"],
  "outputs": ["{options.outputPath}"],
  "options": {
    "target": "node",
    "compiler": "tsc",
    "outputPath": "dist/apps/my-express-api",
    "main": "apps/my-express-api/src/main.ts",
    "tsConfig": "apps/my-express-api/tsconfig.app.json",
    "assets": ["apps/my-express-api/src/assets"],
    "isolatedConfig": true,
    "webpackConfig": "apps/my-express-api/webpack.config.cjs",
  }
},
...
Run Code Online (Sandbox Code Playgroud)

笑话.config.json

/* eslint-disable */
export default {
  displayName: 'new-app-server',
  preset: '../../jest.preset.js',
  testEnvironment: 'node',
  transform: {
    '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
  },
  moduleFileExtensions: ['ts', 'js', 'html'],
  coverageDirectory: '../../coverage/apps/my-express-api',
};
Run Code Online (Sandbox Code Playgroud)

包.json

{
  "name": "my-express-api",
  "version": "0.0.1",
  "type": "commonjs"
}
Run Code Online (Sandbox Code Playgroud)

tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "files": [],
  "include": [],
  "references": [
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.spec.json"
    }
  ],
  "compilerOptions": {
    "module": "ES2022",
    "moduleResolution": "bundler",
    "esModuleInterop": true
  }
}
Run Code Online (Sandbox Code Playgroud)

tsconfig.app.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/out-tsc",
    "module": "ESNext",
    "strict": true,
    "types": ["node", "express"]
  },
  "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
  "include": ["src/**/*.ts"]
}
Run Code Online (Sandbox Code Playgroud)

webpack.config.cjs

const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  mode: 'development', // or 'production' for production mode
  entry: './apps/my-express-api/src/main.ts', // Entry point of your Express application
  output: {
    path: path.resolve(__dirname, '../../dist/apps/my-express-api'),
    filename: 'main.mjs',
    module: true,
    libraryTarget: 'module',
    chunkFormat: 'module',
    chunkFilename: 'main.mjs',
    library: {
      type: 'module',
    },
    environment: {
      module: true,
    },
  },
  node: {
    __dirname: 'node-module',
  },
  resolve: {
    extensions: ['.js', '.mjs', '.ts', '.mts'], // Support both .js and .mjs extensions for ESM
    extensionAlias: {
      '.js': ['.ts', '.js'],
      '.mjs': ['.mts', '.mjs'],
      '.cjs': ['.cts', '.cjs'],
    },
  },
  target: 'node', // Important for server-side applications
  externals: [nodeExternals({ importType: 'module' })], // Exclude node_modules from the bundle
  externalsPresets: {
    node: true,
  },
  module: {
    rules: [
      // Add any necessary loaders or rules here
      { test: /\.([cm]?ts|tsx)$/, loader: 'ts-loader' },
    ],
  },
  experiments: {
    outputModule: true,
  },
  plugins: [
    // Add any necessary plugins here
  ],
};
Run Code Online (Sandbox Code Playgroud)

尝试3

使用@nx/esbuild:esbuild

我以为我已经用这个解决方案解决了这个问题。如果我导入 esm 库(例如 nanoid v4+),那么我将不再收到错误Error [ERR_REQUIRE_ESM],并且我能够构建/提供我的应用程序。

当我尝试导入工作区 js 库时出现问题

错误:

error TS2307: Cannot find module '@myorg/mylib' or its corresponding type declarations.
Run Code Online (Sandbox Code Playgroud)

配置:

项目.json

...
  "build": {
  "executor": "@nx/esbuild:esbuild",
  "outputs": ["{options.outputPath}"],
  "options": {
    "outputPath": "dist/apps/my-express-api",
    "main": "apps/my-express-api/src/main.ts",
    "tsConfig": "apps/my-express-api/tsconfig.app.json",
    "assets": [],
    "generatePackageJson": true,
    "format": ["esm"],
    "platform": "node",
    "bundle": true
  }
},
...
Run Code Online (Sandbox Code Playgroud)

包.json

{
  "name": "my-express-api",
  "version": "0.0.1",
  "type": "module"
}
Run Code Online (Sandbox Code Playgroud)

tsconfig.app.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    //    "outDir": "../../../dist/out-tsc",
    "outDir": "dist",
    "lib": ["ES2021"],
    "module": "NodeNext",
    "target": "ES2021",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "NodeNext",
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "baseUrl": ".",
    "resolveJsonModule": true,
    "types": ["node", "express"]
  },
  "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
  "include": ["src/**/*.ts"],
}
Run Code Online (Sandbox Code Playgroud)

tsconfig.base.json

...
    "paths": {
      "@myorg/mylib": ["libs/mylib/src/index.ts"],
    }
...
Run Code Online (Sandbox Code Playgroud)

源代码示例


编辑

  • 更新了沙盒链接
  • 添加了详细的错误消息
  • 添加了尝试 2 的详细信息
  • 添加了尝试 3 的详细信息
  • 添加了 Github 存储库的链接,包含所有尝试及更多内容

Ali*_*eid 0

尝试以下选项:

  1. 确保您的 Express 应用程序可以处理 ESM 和 CommonJS 模块。由于 Express 传统上是基于 CommonJS 的框架,并且您要引入像 nanoid 这样的 ESM 模块,因此您需要一个能够容纳这两者的设置。

  2. 在 tsconfig 中为后端代码设置 "module": "CommonJS"。这可确保 TypeScript 编译为 CommonJS,这是 Node.js 和 Express 的默认设置。使用 "moduleResolution": "node" 来利用 Node.js 的解析算法,它可以处理 CommonJS 和 ESM。对于 ESM 包,请考虑 CommonJS 和 ESM 环境都支持的动态导入(import() 语法)。NX 配置:NX 应自动处理大部分 TypeScript 配置和路径映射。确保 tsconfig.base.json 中的路径已为工作区库正确设置。

  3. 对 ESM 包(如 nanoid)使用动态导入。例如:

    const { nanoid } =等待导入('nanoid');

这种方法是异步的,但允许您在 CommonJS 上下文中使用 ESM 模块。