Resolve ESM-only plugins passed as strings#92576
Open
20syldev wants to merge 1 commit intovercel:canaryfrom
Open
Resolve ESM-only plugins passed as strings#9257620syldev wants to merge 1 commit intovercel:canaryfrom
20syldev wants to merge 1 commit intovercel:canaryfrom
Conversation
Collaborator
|
Allow CI Workflow Run
Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer |
Collaborator
|
Allow CI Workflow Run
Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer |
fbe0cc5 to
23ca5eb
Compare
When an MDX plugin is passed as a string (e.g. `rehypePlugins: ['@stefanprobst/rehype-extract-toc']`), the loader resolves it via `require.resolve()` which uses CJS resolution semantics. Packages that ship with an `exports` field containing only an `"import"` condition (no `"require"` condition) cause `require.resolve` to throw ERR_PACKAGE_PATH_NOT_EXPORTED because no CJS-compatible export entry is found. Catch that specific error and fall back to a direct ESM dynamic `import()`, which uses the ESM resolver and correctly matches the `"import"` condition in the exports map. Fixes vercel#73757
23ca5eb to
e05db67
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
When a remark/rehype/recma plugin is passed as a string in
@next/mdxoptions:mdx-js-loader.jsresolves it withrequire.resolve(), which uses CJS resolution semantics. For ESM-only packages that expose anexportsfield with only an"import"condition (no"require"condition), Node throwsERR_PACKAGE_PATH_NOT_EXPORTEDbecause no CJS-compatible entry exists.Why
require.resolve()applies the"require"condition when matching exports. If the package's exports map looks like:{ "exports": { ".": { "import": "./dist/index.mjs" } } }require.resolve()finds no matching condition and throws. A dynamicimport()uses ESM resolution and correctly matches the"import"condition.Fix
Catch
ERR_PACKAGE_PATH_NOT_EXPORTEDfromrequire.resolve()and fall back to a directimport(). This lets Node's ESM resolver handle the exports map, resolving the module correctly.Code diff
async function importPluginForPath(pluginPath, projectRoot) { - const path = require.resolve(pluginPath, { paths: [projectRoot] }) + let resolvedPath + try { + resolvedPath = require.resolve(pluginPath, { paths: [projectRoot] }) + } catch (e) { + if (e.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') { + // ESM-only package: no "require" condition in exports map. + // Fall back to a direct import() which uses ESM resolution. + return interopDefault(await import(pluginPath)) + } + throw e + } return interopDefault( - await import(process.platform === 'win32' ? pathToFileURL(path) : path) + await import(process.platform === 'win32' ? pathToFileURL(resolvedPath) : resolvedPath) ) }Testing
The existing
test/e2e/app-dir/mdxsuite already covers the string plugin resolution happy path (packages with CJS-compatible exports).The fix affects the fallback path only — packages whose exports map has no
"require"entry — which isn't covered by the current suite. Adding a test fixture with a local ESM-only plugin would provide coverage; happy to add one if the maintainers prefer it.Fixes #73757