Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ReferenceError: require is not defined is ESM Node projects #946

Open
aral opened this issue Mar 10, 2021 · 12 comments
Open

ReferenceError: require is not defined is ESM Node projects #946

aral opened this issue Mar 10, 2021 · 12 comments

Comments

@aral
Copy link

aral commented Mar 10, 2021

In my ESM Node project, Place that also imports CommonJS modules (any any Node project these days probably does given ESM modules are not the norm yet), I get the following error when I run the bundle:

ReferenceError: require is not defined

If I add the following line to the top of the bundle to create the require function, it runs:

const require = createRequire(import.meta.url)

This is not a small, reproducible case but filing it anyway in case it helps without one.

To reproduce

  1. Check out / download Place at the following tag: https://github.com/small-tech/place/releases/tag/esbuild-issue-946
  2. npm i
  3. npm run build

Try to run the generated place.js with, e.g., ./place.js evanw.small-web.org and you should see it fail with the error above. If you add the line mentioned above, it should run.

@zaydek
Copy link

zaydek commented Mar 10, 2021

I ran into something similar to this when playing with const evaluator = new Function(code). I didn’t know you could do this but I found the technique here: https://github.com/digital-loukoum/esrun/blob/main/src/main.js#L63.

I don’t know how relevant this is for you but given I encountered the same error, I needed to do this: const evaluator = new Function("require", code) and then invoke the evaluator like this evaluator(require). This seemed hacky to me so I’ve since changed my implementation, but I thought it was worth sharing.

So my guess is what createRequire is evaluating JavaScript in memory and therefore require becomes undefined as a side-effect. This is my best guess as to what’s going on.

Edit: My point is that I don’t think this has anything to do w/ esbuild. I think this has to do with evaluating JavaScript programmatically / in-memory.

@aral aral changed the title WIP: ReferenceError: require is not defined is ESM Node projects ReferenceError: require is not defined is ESM Node projects Mar 10, 2021
@aral
Copy link
Author

aral commented Mar 10, 2021

@zaydek Thank you for the additional information… interesting. If that is the case, it doesn’t affect good ’ol eval() itself as JSDB uses it and that’s not creating a problem, so it must be limited to the new Function() method. Hmm…

Update: here’s a link to the relevant code in Node, in case it helps: https://github.com/nodejs/node/blob/831f4c755d2270e7b44c075176ab4a05698d21d9/lib/internal/modules/cjs/helpers.js#L48

Given that it’s a single line of code that needs to be added, I’m just going to inject that as part of my build process for the time being.

aral added a commit to small-tech/place that referenced this issue Mar 10, 2021
@aral
Copy link
Author

aral commented Mar 11, 2021

My hacky workaround for the moment is running this after the esbuild script in the npm build task (the app is a CLI app, hence the hashbang at the start:

import fs from 'fs'
import path from 'path'
const __dirname = new URL('.', import.meta.url).pathname

const build = path.join(__dirname, '..', 'place.js')
const unpatchedBuild = fs.readFileSync(build, 'utf-8')
const patchedBuild = unpatchedBuild.replace('#!/usr/bin/env node', '#!/usr/bin/env node\n\nconst require = createRequire(import.meta.url)\n')
fs.writeFileSync(build, patchedBuild, 'utf-8')

@popeindustries
Copy link

In case anyone else gets tripped up by this error when converting cjs to esm (where require('native-node-module or module-marked-as-external') preserves the require statement and throws a runtime error), the most elegant solution I've found is via use of the banner option:

esbuild.build({
  banner: {
    js: "import { createRequire as topLevelCreateRequire } from 'module';\n const require = topLevelCreateRequire(import.meta.url);"
  },
  ...
})

Note: it is a good idea to rename createRequire during import in case another module already imports the same, since esbuild will not be able to deduplicate the reference

@aral
Copy link
Author

aral commented Apr 9, 2021

@popeindustries That’s excellent, thank you :)

@karlhorky
Copy link

karlhorky commented Sep 2, 2021

the most elegant solution I've found is via use of the banner option

@popeindustries amazing, thanks!

This can also be passed in on the command line:

esbuild index.ts --bundle --outfile=out.mjs --platform=node --target=node16.8 --format=esm  --banner:js='import { createRequire as topLevelCreateRequire } from \"module\"; const require = topLevelCreateRequire(import.meta.url);'

@RealAlphabet
Copy link

I ran into something similar to this when playing with const evaluator = new Function(code). I didn’t know you could do this but I found the technique here: https://github.com/digital-loukoum/esrun/blob/main/src/main.js#L63.

I don’t know how relevant this is for you but given I encountered the same error, I needed to do this: const evaluator = new Function("require", code) and then invoke the evaluator like this evaluator(require). This seemed hacky to me so I’ve since changed my implementation, but I thought it was worth sharing.

So my guess is what createRequire is evaluating JavaScript in memory and therefore require becomes undefined as a side-effect. This is my best guess as to what’s going on.

Edit: My point is that I don’t think this has anything to do w/ esbuild. I think this has to do with evaluating JavaScript programmatically / in-memory.

I don't think it's related to the JS engine. require is defined for CJS modules only and you should use import in ESM modules instead (or use createRequire from module).

The side effect of ESM modules is that require becomes undefined.
This is expected behavior.

Since Function requires text as the function's code, esbuild can't guess and convert any require to import. I'm sure esbuild replaced your evaluator(require) with evaluator(createRequire) when converting to ESM.


Unfortunately, this does not explain the problem with esbuild. Why isn't require defined?
Probably because it is not using createRequire inside these ESM files.

We should take a better look at the output.

@aral
Copy link
Author

aral commented Jan 19, 2022

This can also be passed in on the command line:

esbuild index.ts --bundle --outfile=out.mjs --platform=node --target=node16.8 --format=esm  --banner:js='import { createRequire as topLevelCreateRequire } from \"module\";\n const require = topLevelCreateRequire(import.meta.url);'

Just a heads up that the build was choking on the \n for me (wasn’t being substituted). Removing it worked.

@karlhorky
Copy link

karlhorky commented Jan 19, 2022

Just a heads up that the build was choking on the \n for me (wasn’t being substituted). Removing it worked.

Thanks, I've edited my comment above. I think the \n works on macOS, fails on Windows.

@aral
Copy link
Author

aral commented Jan 19, 2022

@karlhorky Thanks, Karl. (In my case, it was failing on Linux.)

@hanneswidrig
Copy link

hanneswidrig commented Jun 26, 2023

This might be the answer for many, especially for those using ESM with AWS SDK v3.

aws/aws-sdk-js-v3#4217 (comment)

bundling: {
	format: aws_lambda_nodejs.OutputFormat.ESM,
	mainFields: ["module", "main"],
},
architecture: aws_lambda.Architecture.ARM_64,

@hanneswidrig
Copy link

@evanw It would be amazing if we could have a solution to the current errors that didn't require including a banner to separately define require and other node built-ins. That would improve experience in environments like AWS Lambdas where you'll get an error like Dynamic require of "buffer" is not supported when certain cjs dependencies pull in node built-ins.

EricCrosson added a commit to EricCrosson/batch-edit-prs that referenced this issue Nov 12, 2023
When I just added `--format=esm`, I saw runtime errors about `require`
not being defined.

I used a trick from
evanw/esbuild#946 (comment)
that seems to compile and execute happily.
github-actions bot pushed a commit to EricCrosson/batch-edit-prs that referenced this issue Nov 12, 2023
## [1.4.1](v1.4.0...v1.4.1) (2023-11-12)

### Bug Fixes

* bundle as an ES Module ([7561499](7561499)), closes [/github.com/evanw/esbuild/issues/946#issuecomment-911869872](https://github.com//github.com/evanw/esbuild/issues/946/issues/issuecomment-911869872)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants