I've recently created an nx workspace and added in an Express application. This works out-of-the-box as you would expect. However, as soon as I add a dependency to a ESM library (e.g. nanoid v4+), I'm unable to start the server and I get the following error message.
Error [ERR_REQUIRE_ESM]: require() of ES Module /workspace/node_modules/nanoid/index.js not supported
What is the correct way to configure an NX workspace and Express application so it is able to import both ESM libraries and local workspace libraries and successfully test/build/serve the application?
npx nx run my-express-api:test
npx nx run my-express-api:build
npx nx run my-express-api:serve
Attempt 1
This is the out-of-the-box attempt
Error:
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)
Config:
express app 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"]
}
express app 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',
}),
],
};
express app 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": []
}
root workspace 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",
...
}
}
Attempt 2
Modifying tsconfig/webpack settings.
These modifications aren't ideal but this does allow me to import ESM libraries. However, I now can't import local workspace libraries.
Error:
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
Config:
project.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",
}
},
...
jest.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',
};
package.json
{
"name": "my-express-api",
"version": "0.0.1",
"type": "commonjs"
}
tsconfig.json
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"module": "ES2022",
"moduleResolution": "bundler",
"esModuleInterop": true
}
}
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"]
}
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
],
};
Attempt 3
Using @nx/esbuild:esbuild
I thought I had solved it with this solution. If I import esm libraries (e.g. nanoid v4+) then I no longer get the Error [ERR_REQUIRE_ESM]
error and I am able to build/serve my application.
The problem occurs when I try to import a workspace js library
Error:
error TS2307: Cannot find module '@myorg/mylib' or its corresponding type declarations.
Config:
project.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
}
},
...
package.json
{
"name": "my-express-api",
"version": "0.0.1",
"type": "module"
}
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"],
}
tsconfig.base.json
...
"paths": {
"@myorg/mylib": ["libs/mylib/src/index.ts"],
}
...
Edits
- Updated sandbox link
- Added detailed error message
- Added attempt 2 details
- Added attempt 3 details
- Added link to Github repo with all attempts and more