fix(bundling): add faux-ESM files so "import" in Node works with both named and default exports (#18916)
This commit is contained in:
parent
c9aad2dc8c
commit
99c44f9e88
@ -35,10 +35,14 @@ describe('packaging libs', () => {
|
|||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/js:lib ${rollupLib} --bundler=rollup --no-interactive`
|
`generate @nx/js:lib ${rollupLib} --bundler=rollup --no-interactive`
|
||||||
);
|
);
|
||||||
|
updateFile(`libs/${rollupLib}/src/index.ts`, (content) => {
|
||||||
|
// Test that default functions work in ESM (Node).
|
||||||
|
return `${content}\nexport default function f() { return 'rollup default' }`;
|
||||||
|
});
|
||||||
|
|
||||||
runCLI(`build ${esbuildLib}`);
|
runCLI(`build ${esbuildLib}`);
|
||||||
runCLI(`build ${viteLib}`);
|
runCLI(`build ${viteLib}`);
|
||||||
runCLI(`build ${rollupLib}`);
|
runCLI(`build ${rollupLib} --generateExportsField`);
|
||||||
|
|
||||||
const pmc = getPackageManagerCommand();
|
const pmc = getPackageManagerCommand();
|
||||||
let output: string;
|
let output: string;
|
||||||
@ -66,10 +70,11 @@ describe('packaging libs', () => {
|
|||||||
`
|
`
|
||||||
const { ${esbuildLib} } = require('@proj/${esbuildLib}');
|
const { ${esbuildLib} } = require('@proj/${esbuildLib}');
|
||||||
const { ${viteLib} } = require('@proj/${viteLib}');
|
const { ${viteLib} } = require('@proj/${viteLib}');
|
||||||
const { ${rollupLib} } = require('@proj/${rollupLib}');
|
const { default: rollupDefault, ${rollupLib} } = require('@proj/${rollupLib}');
|
||||||
console.log(${esbuildLib}());
|
console.log(${esbuildLib}());
|
||||||
console.log(${viteLib}());
|
console.log(${viteLib}());
|
||||||
console.log(${rollupLib}());
|
console.log(${rollupLib}());
|
||||||
|
console.log(rollupDefault());
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
runCommand(pmc.install, {
|
runCommand(pmc.install, {
|
||||||
@ -81,6 +86,7 @@ describe('packaging libs', () => {
|
|||||||
expect(output).toContain(esbuildLib);
|
expect(output).toContain(esbuildLib);
|
||||||
expect(output).toContain(viteLib);
|
expect(output).toContain(viteLib);
|
||||||
expect(output).toContain(rollupLib);
|
expect(output).toContain(rollupLib);
|
||||||
|
expect(output).toContain('rollup default');
|
||||||
|
|
||||||
// Make sure outputs in esm project
|
// Make sure outputs in esm project
|
||||||
createFile(
|
createFile(
|
||||||
@ -105,10 +111,11 @@ describe('packaging libs', () => {
|
|||||||
`
|
`
|
||||||
import { ${esbuildLib} } from '@proj/${esbuildLib}';
|
import { ${esbuildLib} } from '@proj/${esbuildLib}';
|
||||||
import { ${viteLib} } from '@proj/${viteLib}';
|
import { ${viteLib} } from '@proj/${viteLib}';
|
||||||
import { ${rollupLib} } from '@proj/${rollupLib}';
|
import rollupDefault, { ${rollupLib} } from '@proj/${rollupLib}';
|
||||||
console.log(${esbuildLib}());
|
console.log(${esbuildLib}());
|
||||||
console.log(${viteLib}());
|
console.log(${viteLib}());
|
||||||
console.log(${rollupLib}());
|
console.log(${rollupLib}());
|
||||||
|
console.log(rollupDefault());
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
runCommand(pmc.install, {
|
runCommand(pmc.install, {
|
||||||
@ -120,6 +127,7 @@ describe('packaging libs', () => {
|
|||||||
expect(output).toContain(esbuildLib);
|
expect(output).toContain(esbuildLib);
|
||||||
expect(output).toContain(viteLib);
|
expect(output).toContain(viteLib);
|
||||||
expect(output).toContain(rollupLib);
|
expect(output).toContain(rollupLib);
|
||||||
|
expect(output).toContain('rollup default');
|
||||||
}, 500_000);
|
}, 500_000);
|
||||||
|
|
||||||
it('should build with tsc, swc and be used in CJS/ESM projects', async () => {
|
it('should build with tsc, swc and be used in CJS/ESM projects', async () => {
|
||||||
|
|||||||
@ -29,7 +29,8 @@ describe('Rollup Plugin', () => {
|
|||||||
checkFilesExist(`dist/libs/${myPkg}/index.cjs.d.ts`);
|
checkFilesExist(`dist/libs/${myPkg}/index.cjs.d.ts`);
|
||||||
expect(readJson(`dist/libs/${myPkg}/package.json`).exports).toEqual({
|
expect(readJson(`dist/libs/${myPkg}/package.json`).exports).toEqual({
|
||||||
'.': {
|
'.': {
|
||||||
import: './index.esm.js',
|
module: './index.esm.js',
|
||||||
|
import: './index.cjs.mjs',
|
||||||
default: './index.cjs.js',
|
default: './index.cjs.js',
|
||||||
},
|
},
|
||||||
'./package.json': './package.json',
|
'./package.json': './package.json',
|
||||||
@ -95,15 +96,18 @@ describe('Rollup Plugin', () => {
|
|||||||
expect(readJson(`dist/libs/${myPkg}/package.json`).exports).toEqual({
|
expect(readJson(`dist/libs/${myPkg}/package.json`).exports).toEqual({
|
||||||
'./package.json': './package.json',
|
'./package.json': './package.json',
|
||||||
'.': {
|
'.': {
|
||||||
import: './index.esm.js',
|
module: './index.esm.js',
|
||||||
|
import: './index.cjs.mjs',
|
||||||
default: './index.cjs.js',
|
default: './index.cjs.js',
|
||||||
},
|
},
|
||||||
'./bar': {
|
'./bar': {
|
||||||
import: './bar.esm.js',
|
module: './bar.esm.js',
|
||||||
|
import: './bar.cjs.mjs',
|
||||||
default: './bar.cjs.js',
|
default: './bar.cjs.js',
|
||||||
},
|
},
|
||||||
'./foo': {
|
'./foo': {
|
||||||
import: './foo.esm.js',
|
module: './foo.esm.js',
|
||||||
|
import: './foo.cjs.mjs',
|
||||||
default: './foo.cjs.js',
|
default: './foo.cjs.js',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -98,7 +98,8 @@ describe('updatePackageJson', () => {
|
|||||||
exports: {
|
exports: {
|
||||||
'./package.json': './package.json',
|
'./package.json': './package.json',
|
||||||
'.': {
|
'.': {
|
||||||
import: './index.esm.js',
|
module: './index.esm.js',
|
||||||
|
import: './index.cjs.mjs',
|
||||||
default: './index.cjs.js',
|
default: './index.cjs.js',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { basename, dirname, parse, relative } from 'path';
|
import { basename, join, parse } from 'path';
|
||||||
import { ExecutorContext } from 'nx/src/config/misc-interfaces';
|
import { ExecutorContext } from 'nx/src/config/misc-interfaces';
|
||||||
import { ProjectGraphProjectNode } from 'nx/src/config/project-graph';
|
import { ProjectGraphProjectNode } from 'nx/src/config/project-graph';
|
||||||
import {
|
import {
|
||||||
@ -6,9 +6,10 @@ import {
|
|||||||
updateBuildableProjectPackageJsonDependencies,
|
updateBuildableProjectPackageJsonDependencies,
|
||||||
} from '@nx/js/src/utils/buildable-libs-utils';
|
} from '@nx/js/src/utils/buildable-libs-utils';
|
||||||
import { writeJsonFile } from 'nx/src/utils/fileutils';
|
import { writeJsonFile } from 'nx/src/utils/fileutils';
|
||||||
|
import { writeFileSync } from 'fs';
|
||||||
import { PackageJson } from 'nx/src/utils/package-json';
|
import { PackageJson } from 'nx/src/utils/package-json';
|
||||||
import { NormalizedRollupExecutorOptions } from './normalize';
|
import { NormalizedRollupExecutorOptions } from './normalize';
|
||||||
import { normalizePath } from '@nx/devkit';
|
import { stripIndents } from '@nx/devkit';
|
||||||
|
|
||||||
// TODO(jack): Use updatePackageJson from @nx/js instead.
|
// TODO(jack): Use updatePackageJson from @nx/js instead.
|
||||||
export function updatePackageJson(
|
export function updatePackageJson(
|
||||||
@ -43,7 +44,10 @@ export function updatePackageJson(
|
|||||||
if (options.generateExportsField) {
|
if (options.generateExportsField) {
|
||||||
for (const [exportEntry, filePath] of Object.entries(esmExports)) {
|
for (const [exportEntry, filePath] of Object.entries(esmExports)) {
|
||||||
packageJson.exports[exportEntry] = hasCjsFormat
|
packageJson.exports[exportEntry] = hasCjsFormat
|
||||||
? { import: filePath }
|
? // If CJS format is used, make sure `import` (from Node) points to same instance of the package.
|
||||||
|
// Otherwise, packages that are required to be singletons (like React, RxJS, etc.) will break.
|
||||||
|
// Reserve `module` entry for bundlers to accommodate tree-shaking.
|
||||||
|
{ [hasCjsFormat ? 'module' : 'import']: filePath }
|
||||||
: filePath;
|
: filePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,7 +68,35 @@ export function updatePackageJson(
|
|||||||
if (options.generateExportsField) {
|
if (options.generateExportsField) {
|
||||||
for (const [exportEntry, filePath] of Object.entries(cjsExports)) {
|
for (const [exportEntry, filePath] of Object.entries(cjsExports)) {
|
||||||
if (hasEsmFormat) {
|
if (hasEsmFormat) {
|
||||||
|
// If ESM format used, make sure `import` (from Node) points to a wrapped
|
||||||
|
// version of CJS file to ensure the package remains a singleton.
|
||||||
|
// TODO(jack): This can be made into a rollup plugin to re-use in Vite.
|
||||||
|
const relativeFile = parse(filePath).base;
|
||||||
|
const fauxEsmFilePath = filePath.replace(/\.cjs\.js$/, '.cjs.mjs');
|
||||||
|
packageJson.exports[exportEntry]['import'] ??= fauxEsmFilePath;
|
||||||
packageJson.exports[exportEntry]['default'] ??= filePath;
|
packageJson.exports[exportEntry]['default'] ??= filePath;
|
||||||
|
// Re-export from relative CJS file, and Node will synthetically export it as ESM.
|
||||||
|
// Make sure both ESM and CJS point to same instance of the package because libs like React, RxJS, etc. requires it.
|
||||||
|
// Also need a special .cjs.default.js file that re-exports the `default` from CJS, or else
|
||||||
|
// default import in Node will not work.
|
||||||
|
writeFileSync(
|
||||||
|
join(
|
||||||
|
options.outputPath,
|
||||||
|
filePath.replace(/\.cjs\.js$/, '.cjs.default.js')
|
||||||
|
),
|
||||||
|
`exports._default = require('./${parse(filePath).base}').default;`
|
||||||
|
);
|
||||||
|
writeFileSync(
|
||||||
|
join(options.outputPath, fauxEsmFilePath),
|
||||||
|
// Re-export from relative CJS file, and Node will synthetically export it as ESM.
|
||||||
|
stripIndents`
|
||||||
|
export * from './${relativeFile}';
|
||||||
|
export { _default as default } from './${relativeFile.replace(
|
||||||
|
/\.cjs\.js$/,
|
||||||
|
'.cjs.default.js'
|
||||||
|
)}';
|
||||||
|
`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
packageJson.exports[exportEntry] = filePath;
|
packageJson.exports[exportEntry] = filePath;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user