|
1 | 1 | const mdxLoader = require('@mdx-js/loader') |
| 2 | +const fs = require('node:fs') |
| 3 | +const path = require('node:path') |
2 | 4 | const { pathToFileURL } = require('node:url') |
3 | 5 |
|
4 | 6 | function interopDefault(mod) { |
5 | 7 | return mod.default || mod |
6 | 8 | } |
7 | 9 |
|
| 10 | +// Recursively resolve an exports condition value, preferring 'import' then 'default'. |
| 11 | +function resolveExportCondition(value) { |
| 12 | + if (typeof value === 'string') return value |
| 13 | + if (value && typeof value === 'object') { |
| 14 | + for (const key of ['import', 'default']) { |
| 15 | + if (key in value) { |
| 16 | + const result = resolveExportCondition(value[key]) |
| 17 | + if (result) return result |
| 18 | + } |
| 19 | + } |
| 20 | + } |
| 21 | + return null |
| 22 | +} |
| 23 | + |
| 24 | +// Extract the ESM entry point from a package.json object. |
| 25 | +function resolveEsmEntry(pkg) { |
| 26 | + const { exports: exp, module: mod, main } = pkg |
| 27 | + if (exp) { |
| 28 | + const mainExport = typeof exp === 'string' ? exp : exp['.'] ?? exp |
| 29 | + const resolved = resolveExportCondition(mainExport) |
| 30 | + if (resolved) return resolved |
| 31 | + } |
| 32 | + return mod ?? main ?? 'index.js' |
| 33 | +} |
| 34 | + |
8 | 35 | async function importPluginForPath(pluginPath, projectRoot) { |
9 | | - const path = require.resolve(pluginPath, { paths: [projectRoot] }) |
| 36 | + let resolvedPath |
| 37 | + try { |
| 38 | + resolvedPath = require.resolve(pluginPath, { paths: [projectRoot] }) |
| 39 | + } catch (e) { |
| 40 | + if (e.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') { |
| 41 | + // The package defines an `exports` field but has no entry for the |
| 42 | + // `"require"` condition (e.g. an ESM-only package whose exports only |
| 43 | + // include an `"import"` condition). `require.resolve` uses CJS |
| 44 | + // resolution semantics and cannot match those entries. |
| 45 | + // |
| 46 | + // Node.js always includes the absolute path to the package's package.json |
| 47 | + // in the error message. That path was resolved starting from `projectRoot`, |
| 48 | + // so we can use it to find the ESM entry point and import via a file:// URL. |
| 49 | + // A bare import(pluginPath) would resolve from @next/mdx's own location, |
| 50 | + // which fails in pnpm / non-hoisted setups. |
| 51 | + const pkgJsonMatch = e.message.match(/in (.+package\.json)/) |
| 52 | + if (pkgJsonMatch) { |
| 53 | + const pkgJsonPath = pkgJsonMatch[1] |
| 54 | + const pkgDir = path.dirname(pkgJsonPath) |
| 55 | + const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')) |
| 56 | + const entry = resolveEsmEntry(pkg) |
| 57 | + const absolutePath = path.resolve(pkgDir, entry) |
| 58 | + return interopDefault(await import(pathToFileURL(absolutePath))) |
| 59 | + } |
| 60 | + // Could not extract the path from the error message — fall back to a bare |
| 61 | + // import (may fail in strict pnpm / non-hoisted setups). |
| 62 | + return interopDefault(await import(pluginPath)) |
| 63 | + } |
| 64 | + throw e |
| 65 | + } |
10 | 66 | return interopDefault( |
11 | 67 | // "use pathToFileUrl to make esm import()s work with absolute windows paths": |
12 | 68 | // on windows import("C:\\path\\to\\file") is not valid, so we need to use file:// URLs |
13 | 69 | // https://github.com/vercel/next.js/commit/fbf9e12de095e0237d4ba4aa6139d9757bd20be9 |
14 | | - await import(process.platform === 'win32' ? pathToFileURL(path) : path) |
| 70 | + await import(process.platform === 'win32' ? pathToFileURL(resolvedPath) : resolvedPath) |
15 | 71 | ) |
16 | 72 | } |
17 | 73 |
|
|
0 commit comments