vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.73k stars 26.62k forks source link

ERR_ESM_REQUIRE - Webpack 5 config should support ESM packages #23725

Closed mikestopcontinues closed 2 years ago

mikestopcontinues commented 3 years ago

What version of Next.js are you using?

10.1.3

What version of Node.js are you using?

14.x

What browser are you using?

Chrome

What operating system are you using?

macOS

How are you deploying your application?

Vercel

Describe the Bug

p-queue recently updated to using ESM modules, and it looks like some webpack configuration is required to support it. I doubt this is the last time this particular issue will come up.

Expected Behavior

Users should be able to require ESM modules without getting an error.

To Reproduce

Install p-queue@7.0.0, import it, and try to start next.

Thanks!

stefanprobst commented 3 years ago

not the op, but reproducible with this. both import and import() fail with:

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /data/Development/playground/issue-next-esm/node_modules/p-queue/dist/index.js
require() of ES modules is not supported.
require() of /data/Development/playground/issue-next-esm/node_modules/p-queue/dist/index.js from /data/Development/playground/issue-next-esm/.next/server/pages/index.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename /data/Development/playground/issue-next-esm/node_modules/p-queue/dist/index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /data/Development/playground/issue-next-esm/node_modules/p-queue/package.json.
mikestopcontinues commented 3 years ago

Thanks @stefanprobst I didn't see they'd requested a repro. :)

maiertech commented 3 years ago

Some background: @sindresorhus is in the process of moving most of his packages to ESM only in 2021. This error will likely become more common as more packages move to ESM only.

I ran into the same issue with https://github.com/sindresorhus/slugify. Workaround: don't upgrade to latest major.

yurtaev commented 3 years ago

workaround: use next-transpile-modules

const withTM = require('next-transpile-modules')([
 // ...
  'p-debounce', // ESM module
])
mikestopcontinues commented 3 years ago

I like @yurtaev's solution, but if anyone's relying on npm-check-updates, my fix was to prefix breaking module versions with =, then run ncu -u --rejectVersion '/^=/'.

nemanja-tosic commented 3 years ago

We have encountered the same issue with yjs (version 13.5.5). For us, webpack was compiling the module version of yjs, but was not compiling anything yjs was importing, namely a package called lib0. This resulted in an ESM (yjs) importing a CJS module (lib0). Low level, in case it helps anyone: the specific issue was that yjs was adding suffixes to all imports (eg import { observable } from 'lib0/observable.js) which was invalidating conditional exports, resulting to the ESM => CJS import mentioned above.

The solution for us was to add the following line to next.config.js:

module.exports = {
  webpack: function (config, options) {
    config.externals = [...config.externals, 'yjs'];

    return config;
  },
};

This makes webpack treat yjs as an external dependency (non-transpiled that is), and everything goes through the proper builds.

UPDATE: no, this only made the build work. I saw a require('yjs'); in the server build artifacts, but it does not work on the client.

mario-jerkovic commented 3 years ago

Same issue with new version of d3 modules like d3-scale

advaiyalad commented 3 years ago

A more permanent solution would be to import() everything instead of require()ing it. This is possible since next no longer supports node 10 in canary. https://github.com/vercel/next.js/blob/f18ce55d8ac85f8abaa51bcd8a663ef5c8e78d28/package.json#L140-L142

JohnForster commented 3 years ago

This still has the "please add a complete reproduction" tag, so I'm linking a minimal repo:

https://github.com/JohnForster/esm-minimal-repo

This example also does not work with withTM, so if anyone can give advice on how to get it running, I'd appreciate it.

JohnForster commented 3 years ago

For anyone running into similar problems ( @mario-jerkovic ?) that aren't immediately fixed with next-transpile-modules, you may need to include sub-dependencies in your next.config.js as well. I just ran into this problem with d3-delaunay but it might be present in other d3 modules as well.

Keep an eye on the error messages to see which modules you need to add.

My config looks like:

// next.config.js
const withTM = require("next-transpile-modules")([
  "d3-delaunay", // The package I'm trying to import
  "delaunator", // A dependency of d3-delaunay
  "robust-predicates", // A dependency of delaunator
]);

module.exports = withTM({
  reactStrictMode: true,
});
advaiyalad commented 3 years ago

Could the tag please add a complete reproduction please be removed, as this issue has a reproduction?

timneutkens commented 3 years ago

This is being worked on by @sokra currently, you can check out the initial PR here: https://github.com/vercel/next.js/pull/27069. Do keep in mind this is still work in progress.

sokra commented 3 years ago

You can try the latest canary version and verify if it does work in your cases.

talentlessguy commented 3 years ago

@sokra I don't know if this is a bug or not, but when a pure ESM library imports a library that has "module" (non-standard ESM field) and "main" but not "exports", it fails. Is it planned to support non-standard fields? Or "exports" only? There is a large set of such libraries with "main" / "module" fields, so that's why I'm asking

sokra commented 3 years ago

Do you have a name of such a package for reference?

talentlessguy commented 3 years ago

@sokra sure, here are some ("module" / "main" but no "exports"):

talentlessguy commented 3 years ago

not sure if I got this right, but it doesn't seem to work on next@11.0.2-canary.16 when a mixed package uses another mixed package. It results in this error:

import { etherJsFetcher } from 'ether-swr';
         ^^^^^^^^^^^^^^
SyntaxError: Named export 'etherJsFetcher' not found. The requested module 'ether-swr' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'ether-swr';
const { etherJsFetcher } = pkg;

Both have "exports" with "require" / "import" though. Although, ether-swr here doesn't have a "type": "module" field, but AFAIK it should work anyway bc of "exports.import" field.

To reproduce:

  1. create-next-app --example hello-world hello-world-app
  2. npm install next-eth ethers
  3. Add this to next.config.js:
module.exports = {
  experimental: {
    esmExternals: 'loose'
  }
}
  1. See error

Here are package.jsons of both packages for reference:

Weirdly enough, direct import of a mixed package works. But using a package that imports a mixed package doesn't

sokra commented 3 years ago

@talentlessguy you can try if your package is a correct ESM by typing node -e 'import("ether-swr")', which leads to:

(node:31920) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
C:\Repos\_\next-23725\node_modules\ether-swr\esm\index.js:1
export * from './ether-js-fetcher';
^^^^^^

SyntaxError: Unexpected token 'export'

Although, ether-swr here doesn't have a "type": "module" field, but AFAIK it should work anyway bc of "exports.import" field.

Yep, that's exactly the problem. The package is missing "type": "module" (or the .mjs extensions). The exports field has no influence on the decision if ESM or CJS. Best put a package.json with only {"type": "module"} into your ./esm directory -> ./esm/package.json. That would make all files in that directory ESM.

frattaro commented 3 years ago

If anyone gets here because of the new unified stack, maybe this will save you some time:

const withTM = require("next-transpile-modules")([
  "bail",
  "comma-separated-tokens",
  "hast-to-hyperscript",
  "hast-util-from-parse5",
  "hast-util-has-property",
  "hast-util-heading-rank",
  "hast-util-parse-selector",
  "hast-util-raw",
  "hast-util-sanitize",
  "hast-util-to-parse5",
  "hast-util-to-string",
  "hastscript",
  "html-void-elements",
  "is-plain-obj",
  "mdast-util-from-markdown",
  "micromark-core-commonmark",
  "micromark-factory-destination",
  "micromark-factory-label",
  "micromark-factory-space",
  "micromark-factory-title",
  "micromark-factory-whitespace",
  "micromark-util-combine-extensions",
  "micromark-util-character",
  "micromark-util-chunked",
  "micromark-util-classify-character",
  "micromark-util-decode-numeric-character-reference",
  "micromark-util-html-tag-name",
  "micromark-util-normalize-identifier",
  "micromark-util-resolve-all",
  "micromark-util-subtokenize",
  "micromark-util-symbol",
  "property-information",
  "rehype-autolink-headings",
  "rehype-raw",
  "rehype-react",
  "rehype-sanitize",
  "rehype-slug",
  "remark-parse",
  "space-separated-tokens",
  "trough",
  "unified",
  "unist-util-stringify-position",
  "vfile",
  "web-namespaces"
]);
stefanprobst commented 3 years ago

@frattaro you could also try with setting:

// next.config.js
const config = {
  experimental: {
    esmExternals: true,
  },
}
module.exports = config

(requires next@canary version)

frattaro commented 3 years ago

@stefanprobst Doesn't appear to make a difference -- want me to set up a repro repo?

stefanprobst commented 3 years ago

it has been working for me with xdm and retext :shrug:

timneutkens commented 3 years ago

@frattaro can you make sure you're on next@canary?

frattaro commented 3 years ago

Definitely am

timneutkens commented 3 years ago

Can you provide a reproduction? Then we can have a look into it 👍

frattaro commented 3 years ago

Creating the repro revealed my issue. Alone, using esmExternals with the unified stack works just fine.

I have another package that uses imports, does not have type: "module" in it's package.json and needs next-transpile-modules. Unfortunately, it seems you can't use esmExternals alongside next-transpile-modules. So I added type: "module" directly to the package.json of the package in question, and then found another issue with it. It has a babel preset that uses require in it. I renamed the babel preset's suffix to .cjs, and that worked. But then I ran into:

error - ./node_modules/@my-package/path/app.scss
Global CSS cannot be imported from within node_modules.

And at that point I stopped trying to fix it :)

Edit: the package I'm importing is a UI framework, so it's going to have some edge cases

exneval commented 3 years ago

another version of @frattaro

I'm using rehype-raw, rehype-slug, remark-gfm

const withTM = require("next-transpile-modules")([
  "bail",
  "ccount",
  "comma-separated-tokens",
  "hast-to-hyperscript",
  "hast-util-from-parse5",
  "hast-util-has-property",
  "hast-util-heading-rank",
  "hast-util-parse-selector",
  "hast-util-raw",
  "hast-util-to-parse5",
  "hast-util-to-string",
  "hastscript",
  "html-void-elements",
  "is-plain-obj",
  "markdown-table",
  "mdast-util-find-and-replace",
  "mdast-util-from-markdown",
  "mdast-util-gfm",
  "mdast-util-to-markdown",
  "micromark-extension-gfm",
  "micromark-factory-space",
  "micromark-util-combine-extensions",
  "micromark-util-character",
  "micromark-util-chunked",
  "micromark-util-classify-character",
  "micromark-util-encode",
  "micromark-util-resolve-all",
  "micromark-util-sanitize-uri",
  "micromark-util-symbol",
  "property-information",
  "rehype-raw",
  "rehype-slug",
  "remark-gfm",
  "remark-parse",
  "space-separated-tokens",
  "trough",
  "unified",
  "unist-util-stringify-position",
  "vfile-location",
  "web-namespaces",
]);
deadcoder0904 commented 3 years ago

@exneval I'm trying to use it with unist-util-visit & I used next-transpile-modules like you said but still getting an error:

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: my-app\node_modules\unist-util-visit\index.js
require() of ES modules is not supported.
require() of my-app\node_modules\unist-util-visit\index.js from my-app\rehype\highlight-code.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from my-app\node_modules\unist-util-visit\package.json.

    at new NodeError (node:internal/errors:363:5)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1126:13)
    at Module.load (node:internal/modules/cjs/loader:989:32)
    at Function.Module._load (node:internal/modules/cjs/loader:829:14)
    at Module.require (node:internal/modules/cjs/loader:1013:19)
    at require (node:internal/modules/cjs/helpers:93:18)
    at Object.<anonymous> (my-app\rehype\highlight-code.js:4:15)
    at Module._compile (node:internal/modules/cjs/loader:1109:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1138:10)
    at Module.load (node:internal/modules/cjs/loader:989:32) {
  code: 'ERR_REQUIRE_ESM'

next.config.js

const withTranspileModules = require('next-transpile-modules')([
    'hast-util-to-html',
    'hast-util-to-string',
    'rehype-slug',
    'remark-gfm',
    'remark-parse',
    'unified',
    'unist-util-visit',
])

const withBundleAnalyzer = bundleAnalyzer({
    enabled: process.env.ANALYZE === 'true',
})

module.exports = withTranspileModules(withBundleAnalyzer(nextConfig))

I even tried changing my custom ./rehype/highlight-code.js to use import instead of require but it gave:

SyntaxError: Cannot use import statement outside a module

Do I need to do anything else?

exneval commented 3 years ago

@deadcoder0904 maybe setup issue? I'm using next compose plugins to wrap the transpile,

This config, for latest rehype-raw, rehype-slug and remark-gfm v1.0.0

const withPlugins = require("next-compose-plugins");
const withTM = require("next-transpile-modules")([
  "escape-string-regexp",
  "comma-separated-tokens",
  "hast-to-hyperscript",
  "hast-util-from-parse5",
  "hast-util-has-property",
  "hast-util-heading-rank",
  "hast-util-parse-selector",
  "hast-util-raw",
  "hast-util-to-parse5",
  "hast-util-to-string",
  "hastscript",
  "html-void-elements",
  "property-information",
  "rehype-raw",
  "rehype-slug",
  "space-separated-tokens",
  "vfile-location",
  "web-namespaces",
]);

const nextConfig = {
  eslint: {
    ignoreDuringBuilds: true,
  },
};

module.exports = withPlugins([withTM], nextConfig);

or maybe you can try NextJS 11.1.0 , and try

// next.config.js
module.exports = {
  // Prefer loading of ES Modules over CommonJS
  experimental: { esmExternals: true }
}

no need to use transpile anymore, finally NextJS support it, and it become default in NextJS 12

deadcoder0904 commented 3 years ago

@exneval I thought I needed to use even next-transpile-modules to make ESM modules work with Next.js v11.1.0. If it's not the case, then I'm wondering why it isn't working?

You also don't need to use next-compose-plugins as it would work properly even without it if you wrap it. I'll probably open an issue on unist-util-visit.

timneutkens commented 3 years ago

@deadcoder0904 can you provide a reproduction repository so that we can have a look?

deadcoder0904 commented 3 years ago

@timneutkens take a look here -> https://github.com/deadcoder0904/next-esm-error

Install & run to see errors :)

stefanprobst commented 3 years ago

@deadcoder0904 looks like you didn't actually enable the esmExternals config?

deadcoder0904 commented 3 years ago

@stefanprobst I did actually, just not in the repro. Pushed a new commit but still error :)

sokra commented 3 years ago

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /../next-esm-error/node_modules/hast-util-to-string/index.js require() of ES modules is not supported. require() of /../next-esm-error/node_modules/hast-util-to-string/index.js from /../next-esm-error/rehype/highlight-code.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules. Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /../next-esm-error/node_modules/hast-util-to-string/package.json.

@deadcoder0904 Your problem is unrelated to next.js/webpack processing your files.

You are trying to require() an ESM, which is not allowed (which is also want the node.js error is trying to tell you). You need to use import or import() to import an ESM.

This happens while loading the next.config.js.

Sadly next.js require()s the config file, so you can't really use import there. I guess that need to be changed in next.js

deadcoder0904 commented 3 years ago

@sokra but I can't import ESM modules from the folder rehype/ as it gives another error:

SyntaxError: Cannot use import statement outside a module

I already said this above :)

Sadly next.js require()s the config file, so you can't really use import there. I guess that need to be changed in next.js

Got it. So I cannot use ESM till next.config.js can use import()? Till then, is there any issue I should subscribe to?

Edit: 13 hours later, came back to the same issue & looks like this is the issue I should be subscribed to :)

Prakhar-FF13 commented 3 years ago

Failed to require remark-math.

I have to import remark-math in next.config.js . It gives this error when trying to import. I am on next@canary.

Error: failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/abc/Desktop/portfolio/node_modules/remark-math/index.js from /Users/abc/Desktop/portfolio/next.config.js not supported. Instead change the require of index.js in /Users/abc/Desktop/portfolio/next.config.js to a dynamic import() which is available in all CommonJS modules. at Object. (/Users/abc/Desktop/portfolio/next.config.js:1:20) at Object.loadConfig [as default] (/Users/abc/Desktop/portfolio/node_modules/next/dist/server/config.js:347:32) at async NextServer.loadConfig (/Users/abc/Desktop/portfolio/node_modules/next/dist/server/next.js:112:22) at async NextServer.prepare (/Users/abc/Desktop/portfolio/node_modules/next/dist/server/next.js:94:24) at async /Users/abc/Desktop/portfolio/node_modules/next/dist/cli/next-dev.js:121:9 { code: 'ERR_REQUIRE_ESM' }

lunelson commented 3 years ago

I'm guessing the use-case for importing remark and rehype plugins in next.config.js is about configuring a markdown or MDX loader for webpack?

The whole unifiedjs ecosystem has moved to ESM but all those plugins also have previous versions that were commonjs format, you can probably make this work if you roll them back to their previous major releases

Prakhar-FF13 commented 3 years ago

Yes, that is what I am thinking of doing.

christopher-caldwell commented 3 years ago
experimental: {
    esmExternals: true,
  },

This worked perfectly for me. Using "next": "11.1.3-canary.7" and "unified": "10.1.0"

timneutkens commented 2 years ago

esmExternals: true is now the default on next@canary meaning it is going to be the default in the upcoming stable release. I'm going to close this issue as this particular case has been fixed. For loading ESModules in next.config.js please subscribe to https://github.com/vercel/next.js/issues/9607.

eric-burel commented 2 years ago

@Prakhar-FF13 by any chance, did you manage to setup math display in MDX?

Prakhar-FF13 commented 2 years ago

No I used mdx bundler by Kent c Dodds.

balazsorban44 commented 2 years ago

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.