|
1 | 1 | import type { Plugin } from 'esbuild';
|
| 2 | +import { readFile } from 'fs-extra'; |
| 3 | +import { dirname } from 'path'; |
2 | 4 |
|
3 |
| -export function esbuildPluginCommonjsNamedExports(module: string, namedExports: string[]): Plugin { |
| 5 | +export function esbuildPluginCommonjsNamedExports(modules: string[]): Plugin { |
4 | 6 | return {
|
5 | 7 | name: 'commonjs-named-exports',
|
6 |
| - setup(build) { |
7 |
| - build.onResolve({ filter: new RegExp(`^${module}$`) }, args => { |
| 8 | + async setup(build) { |
| 9 | + const { init, parse } = await import('cjs-module-lexer'); |
| 10 | + await init(); |
| 11 | + |
| 12 | + build.onResolve({ filter: new RegExp(`^(${modules.join('|')})$`) }, async args => { |
| 13 | + if (args.pluginData?.preventInfiniteRecursion) return; |
| 14 | + |
| 15 | + const { path, ...rest } = args; |
| 16 | + rest.pluginData = { preventInfiniteRecursion: true }; |
| 17 | + const resolveResult = await build.resolve(path, rest); |
| 18 | + const resolvedPath = resolveResult.path; |
| 19 | + |
| 20 | + // skip if resolved to an ESM file |
| 21 | + if (resolvedPath.endsWith('.mjs')) return; |
| 22 | + |
| 23 | + const namedExports = await getNamedExports(resolvedPath); |
| 24 | + |
| 25 | + // skip if nothing is exported |
| 26 | + // (or was an ESM file with .js extension or just failed) |
| 27 | + if (namedExports.length === 0) return; |
| 28 | + |
8 | 29 | return {
|
9 | 30 | path: args.path,
|
10 |
| - namespace: `commonjs-named-exports-${module}`, |
| 31 | + namespace: 'commonjs-named-exports', |
11 | 32 | pluginData: {
|
12 | 33 | resolveDir: args.resolveDir,
|
| 34 | + resolvedPath, |
| 35 | + namedExports, |
13 | 36 | },
|
14 | 37 | };
|
15 | 38 | });
|
16 |
| - build.onLoad({ filter: /.*/, namespace: `commonjs-named-exports-${module}` }, async args => { |
| 39 | + |
| 40 | + build.onLoad({ filter: /.*/, namespace: `commonjs-named-exports` }, async args => { |
| 41 | + const { resolveDir, resolvedPath, namedExports } = args.pluginData; |
| 42 | + |
| 43 | + const filteredNamedExports = namedExports.filter((name: string) => { |
| 44 | + return ( |
| 45 | + // interop for "default" export heavily relies on the esbuild work done automatically |
| 46 | + // we just always reexport it |
| 47 | + // but we need to filter it out here to prevent double reexport if "default" was identified by the lexer |
| 48 | + name !== 'default' && |
| 49 | + // we don't need "__esModule" flag in this wrapper |
| 50 | + // because it outputs native ESM which will be consumed by other native ESM in the browser |
| 51 | + name !== '__esModule' |
| 52 | + ); |
| 53 | + }); |
| 54 | + |
| 55 | + const finalExports = ['default', ...filteredNamedExports]; |
| 56 | + |
17 | 57 | return {
|
18 |
| - resolveDir: args.pluginData.resolveDir, |
19 |
| - contents: ` |
20 |
| - import { default as commonjsExports } from '${module}?force-original'; |
21 |
| - ${namedExports |
22 |
| - .map(name => { |
23 |
| - if (name === 'default') { |
24 |
| - return `export default commonjsExports;`; |
25 |
| - } else { |
26 |
| - return `export const ${name} = commonjsExports.${name};`; |
27 |
| - } |
28 |
| - }) |
29 |
| - .join('\n')} |
30 |
| - `, |
| 58 | + resolveDir, |
| 59 | + contents: `export { ${finalExports.join(',')} } from '${resolvedPath}';`, |
31 | 60 | };
|
32 | 61 | });
|
| 62 | + |
| 63 | + async function getNamedExports(path: string): Promise<string[]> { |
| 64 | + const source = await readFile(path, 'utf8'); |
| 65 | + |
| 66 | + let exports: string[] = []; |
| 67 | + let reexports: string[] = []; |
| 68 | + try { |
| 69 | + ({ exports, reexports } = parse(source)); |
| 70 | + } catch (e) { |
| 71 | + // good place to start debugging if imports are not working |
| 72 | + } |
| 73 | + |
| 74 | + for (const reexport of reexports) { |
| 75 | + const reexportPath = require.resolve(reexport, { paths: [dirname(path)] }); |
| 76 | + const deepExports = await getNamedExports(reexportPath); |
| 77 | + for (const deepExport of deepExports) { |
| 78 | + exports.push(deepExport); |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + return exports; |
| 83 | + } |
33 | 84 | },
|
34 | 85 | };
|
35 | 86 | }
|
0 commit comments