webpack: Built 'module' bundle errors with "require is not defined", but 'commonjs' bundle errors with "Cannot use import"

Bug report

https://stackoverflow.com/questions/72892145/is-it-possible-to-create-single-file-bundle-with-webpack-target-node-type-module

Done.

What is the current behavior?

I’m trying to bundle an ES module node app into a single file, with the exception of the sharp library which has problems with Webpack. This is a node server app, not browser.

All of my code are modules, and I don’t use require(). Only third=party dependencies might.

In webpack.config.js, ff I set the target to node, node18.3 or async-node18.3, I get the following error:

(output config):

        filename: '[name].bundle.mjs',
        path: path.resolve(__dirname, 'dist'),
        sourceMapFilename: '[name].bundle.map',
        chunkFormat: 'module',
$ node ./dist/index.bundle.mjs

<remove source outout for brevity>

ReferenceError: require is not defined
    at 67361 (file:///Path/to/app/dist/index.bundle.mjs:2:4368640)
    at __webpack_require__ (file:///Path/to/app/dist/index.bundle.mjs:2:5256005)
    at file:///Path/to/app/dist/index.bundle.mjs:2:4313644
    at __webpack_require__.a (file:///Path/to/app/dist/index.bundle.mjs:2:5256751)
    at 63607 (file:///Path/to/app/dist/index.bundle.mjs:2:4313499)
    at __webpack_require__ (file:///Path/to/app/dist/index.bundle.mjs:2:5256005)
    at file:///Path/to/app/dist/index.bundle.mjs:2:5258585
    at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:409:24)

Node.js v18.3.0

If I change target to es2020 and keep output as-is, webpack fails with (290 errors reduced for brevity):

ERROR in /Path/to/app/node_modules/mongoose/lib/browserDocument.js 8:21-51
Module not found: Error: Can't resolve 'events' in '/Path/to/app/node_modules/mongoose/lib'

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:
	- add a fallback 'resolve.fallback: { "events": require.resolve("events/") }'
	- install 'events'
If you don't want to include a polyfill, you can use an empty module like this:
	resolve.fallback: { "events": false }
 @ Path/to/app/node_modules/mongoose/lib/document_provider.js 9:24-55
 @ Path/to/app/node_modules/mongoose/lib/index.js 927:38-68
 @ Path/to/app/node_modules/mongoose/index.js 9:17-34
 @ ./node_modules/hapi-mongoose-connect-next/lib/index.js 3:15-34
 @ ./src/index.ts 6:0-61 31:14-33

Module not found: Error: Can't resolve 'util' in '/Path/to/app/node_modules/mongoose/lib'
Module not found: Error: Can't resolve 'assert' in '/Path/to/app/node_modules/mongoose/lib/cast'
Module not found: Error: Can't resolve 'util' in '/Path/to/app/node_modules/mongoose/lib/cursor'
Module not found: Error: Can't resolve 'stream' in '/Path/to/app/node_modules/mongoose/lib/cursor'
Module not found: Error: Can't resolve 'tty' in '/Path/to/app/node_modules/mongoose/node_modules/debug/src'
Module not found: Error: Can't resolve 'os' in '/Path/to/app/node_modules/mongoose/node_modules/ip/lib'
Module not found: Error: Can't resolve 'dns' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib/cmap/auth'
Module not found: Error: Can't resolve 'crypto' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib/cmap/auth'
Module not found: Error: Can't resolve 'http' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib/cmap/auth'
Module not found: Error: Can't resolve 'url' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib/cmap/auth'
Module not found: Error: Can't resolve 'net' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib/cmap'
Module not found: Error: Can't resolve 'tls' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib/cmap'
Module not found: Error: Can't resolve 'zlib' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib/cmap/wire_protocol'
Module not found: Error: Can't resolve 'dns' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib'
Module not found: Error: Can't resolve 'fs' in '/Path/to/app/node_modules/mongoose/node_modules/mongodb/lib'
Module not found: Error: Can't resolve 'path' in '/Path/to/app/node_modules/mongoose/node_modules/saslprep/lib'
Module not found: Error: Can't resolve 'https' in '/Path/to/app/node_modules/@hapi/hapi/lib'
Module not found: Error: Can't resolve 'vm' in '/Path/to/app/node_modules/fast-redact/lib'
Module not found: Error: Can't resolve 'net' in '/Path/to/app/node_modules/http-proxy-agent/dist'
Module not found: Error: Can't resolve 'child_process' in '/Path/to/app/node_modules/jsdom/lib/jsdom/living/xhr'

290 errors have detailed information that is not shown.
Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.

webpack 5.65.0 compiled with 290 errors and 21 warnings in 32411 ms

If I change target back to node but change output to commonjs, I get:

        filename: '[name].bundle.cjs',
        path: path.resolve(__dirname, 'dist'),
        sourceMapFilename: '[name].bundle.map',
        chunkFormat: 'commonjs',
$ node ./dist/index.bundle.cjs

SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1033:15)
    at Module._compile (node:internal/modules/cjs/loader:1069:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Module._load (node:internal/modules/cjs/loader:827:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47

Node.js v18.3.0

I seem to be stuck in a catch-22 here. I can’t compile to commonjs because I use the following in one of my modules:

import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

And I can’t remove import.meta.url because I’m using modules and I need to polyfill __filename and __dirname. I’ve gone though the documentation, and I don’t see what I’m missing. So, I can only assume this is a webpack bug.

Contents of ./dist with target: 'node':

$ ls -al ./dist
total 44512
drwxr-xr-x   6 me  me       192 Jul  6 23:57 .
drwxr-xr-x  22 me  me       704 Jul  6 21:57 ..
-rw-r--r--   1 me  me  17330789 Jul  6 23:57 index.bundle.map
-rw-r--r--   1 me  me   5417703 Jul  6 23:57 index.bundle.mjs
-rw-r--r--   1 me  me     34784 Jul  6 23:57 index.bundle.mjs.LICENSE.txt
drwxr-xr-x   4 me  me       128 Jul  6 23:57 resources

If the current behavior is a bug, please provide the steps to reproduce.

package.json:

  "type": "module",
  "main": "./dist/index.bundle.mjs",
  "module": "./dist/index.bundle.mjs",
  "sideEffects": false,
  "overrides": {
    "@mapbox/node-pre-gyp": "1.0.9"
  },

tsconfig.json:

{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "src",
    "allowJs": true,
    "target": "es2021",
    "module": "es2022",
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",  
    "types": ["node", "jest"], 
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "resolveJsonModule": true,
    "lib": ["es2021"],
  },
  "include": [
    "src/index.ts",
    "src/**/*",
    "src/resources"
  ],
  "exclude": ["node_modules"]
}

Complete webpack.config.js:

import path from 'path';
import webpack from 'webpack';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
    target: 'async-node18.3',
    entry: {
        index: './src/index.ts'
    },
    devtool: 'source-map',
    node: {
        global: true,
        __filename: true,
        __dirname: true,
    },
    module: {
        rules: [
            {
                test: /\.html$/i,
                loader: "html-loader",
            },
            {
                test: /\.[jt]sx?$/,
                resolve: {
                    fullySpecified: false,
                },
            },
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
            { test: /\.node$/, use: "node-loader" }
        ],
    },
    experiments: {
        topLevelAwait: true,
        outputModule: true,
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    output: {
        filename: '[name].bundle.mjs',
        path: path.resolve(__dirname, 'dist'),
        sourceMapFilename: '[name].bundle.map',
        chunkFormat: 'module',
        environment: {
            arrowFunction: true,
            bigIntLiteral: false,
            const: true,
            destructuring: true,
            dynamicImport: true,
            forOf: true,
            module: true,
            optionalChaining: true,
            templateLiteral: true,
        }
    },
    plugins: [
        new webpack.IgnorePlugin({
            resourceRegExp: /canvas/,
            contextRegExp: /jsdom$/
        }),
        new webpack.ContextReplacementPlugin(/require_optional/)
    ],
    externals: {
        'sharp': 'commonjs sharp'
    }
};

What is the expected behavior?

The expect behavior is that javascript modules bundled as a single file module should have all require references replaced, or modules compiled as commonjs should do something to convert import.meta.*.

Other relevant information: webpack version: 5.73.0 Node.js version: 18.3.0 Operating System: macOS 12.4 Additional tools: typescript: 4.6.4, ts-loader: 9.3.0, webpack-cli: 4.10.0

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 23 (9 by maintainers)

Most upvoted comments

webpack can output only commojs for Node.js right now, please use

output: {
        filename: '[name].bundle.js',
}

@alexander-akait I solved problem with .node files by adding node-loader and

module: {
  rules: [
     {
        test: /\.node$/,
        loader: "node-loader",
     }
  ]
}

@ignoramous Yeah, I fixed this locally in your repo, but it is still have some problems with loading .node files, but it is not related to webpack itself 😄

@ignoramous let’s open a new issue, yes, it is a bug, shorty - __non_webpack_require__ replaced on require, but for ESM we should replace it on createRequire(import.meta.url), it happens only when you want to output ESM

Thanks. A fix for this would be nice, since some native Node modules use bindings which hits this bug. I confirmed that this happens with rollup too.

Same issue 🙋🏽‍♂️

@alexander-akait The real target is node v18. This is a server-side application. To me, I don’t care if it compiles down to commonjs (.cjs) or module (.mjs), so long as it’s bundled with minimum code and preferably only sharp as an external node_modules package. Ultimately, I’ll be creating a docker image and running this in a container, so getting things down to the smallest size is desired.

All of my code is esm ts. No usage of require, unless a dependency uses that. Node shouldn’t care, except when it’s in the top-level js, right? Webpack bundles everything into a single top-level js, including the dependencies that use require. Am I correct in my understanding in that’s how it’s working?

Case 1:

  • target: node
  • output.filename: '[name].bundle.cjs'
  • output.chunkFormat: 'commonjs',
  • output.environment: undefined

Output: index.bundle.cjs (node runs as commonjs) Result: SyntaxError: Cannot use import statement outside a module


Case 2:

  • target: node
  • output.filename: '[name].bundle.mjs'
  • output.chunkFormat: 'module',
  • output.environment: { arrowFunction: true, bigIntLiteral: false, const: true, destructuring: true, dynamicImport: true, forOf: true, module: true, optionalChaining: true, templateLiteral: true, }

Output: index.bundle.mjs (node runs as module) Result: ReferenceError: require is not defined


Case 3:

  • target: es2022
  • output.filename: '[name].bundle.mjs'
  • output.chunkFormat: 'module',
  • output.environment: { arrowFunction: true, bigIntLiteral: false, const: true, destructuring: true, dynamicImport: true, forOf: true, module: true, optionalChaining: true, templateLiteral: true, }
  • resolve.fallback: {"events": false, "util": false, "assert": false, "stream": false, "tty": false, "os": false, "dns": false, "crypto": false, "http": false, "url": false, "net": false, "tls": false, "zlib": false, "fs": false, "path": false, "https": false, "vm": false, "net": false, "child_process": false}

Output: index.bundle.mjs (node runs as module) Result: TypeError: Class extends value undefined is not a constructor or null