From f046c52e5c621a0b8108d3a9f2874a7a16464ab8 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 28 Feb 2024 17:26:34 +0000 Subject: [PATCH] feat(bundling): rollup should support ESM config files (#21999) --- e2e/rollup/src/rollup.test.ts | 44 ++++++++++++++++++- .../src/executors/rollup/rollup.impl.spec.ts | 32 ++++++++------ .../src/executors/rollup/rollup.impl.ts | 40 ++++++++++++++--- 3 files changed, 95 insertions(+), 21 deletions(-) diff --git a/e2e/rollup/src/rollup.test.ts b/e2e/rollup/src/rollup.test.ts index 237fcbfaf2637..716933f5ece3f 100644 --- a/e2e/rollup/src/rollup.test.ts +++ b/e2e/rollup/src/rollup.test.ts @@ -13,7 +13,7 @@ import { import { join } from 'path'; describe('Rollup Plugin', () => { - beforeAll(() => newProject()); + beforeAll(() => newProject({ packages: ['@nx/rollup', '@nx/js'] })); afterAll(() => cleanupProject()); it('should be able to setup project to build node programs with rollup and different compilers', async () => { @@ -119,4 +119,46 @@ describe('Rollup Plugin', () => { runCLI(`generate @nx/js:lib ${jsLib} --bundler rollup`); expect(() => runCLI(`build ${jsLib}`)).not.toThrow(); }); + + it('should be able to build libs generated with @nx/js:lib --bundler rollup with a custom rollup.config.{cjs|mjs}', () => { + const jsLib = uniq('jslib'); + runCLI(`generate @nx/js:lib ${jsLib} --bundler rollup`); + updateFile( + `libs/${jsLib}/rollup.config.cjs`, + `module.exports = { + output: { + format: "cjs", + dir: "dist/test", + name: "Mylib", + entryFileNames: "[name].cjs.js", + chunkFileNames: "[name].cjs.js" + } + }` + ); + updateJson(join('libs', jsLib, 'project.json'), (config) => { + config.targets.build.options.rollupConfig = `libs/${jsLib}/rollup.config.cjs`; + return config; + }); + expect(() => runCLI(`build ${jsLib}`)).not.toThrow(); + checkFilesExist(`dist/test/index.cjs.js`); + + updateFile( + `libs/${jsLib}/rollup.config.mjs`, + `export default { + output: { + format: "es", + dir: "dist/test", + name: "Mylib", + entryFileNames: "[name].mjs.js", + chunkFileNames: "[name].mjs.js" + } + }` + ); + updateJson(join('libs', jsLib, 'project.json'), (config) => { + config.targets.build.options.rollupConfig = `libs/${jsLib}/rollup.config.mjs`; + return config; + }); + expect(() => runCLI(`build ${jsLib}`)).not.toThrow(); + checkFilesExist(`dist/test/index.mjs.js`); + }); }); diff --git a/packages/rollup/src/executors/rollup/rollup.impl.spec.ts b/packages/rollup/src/executors/rollup/rollup.impl.spec.ts index d647bb0a896b7..f135948855dc6 100644 --- a/packages/rollup/src/executors/rollup/rollup.impl.spec.ts +++ b/packages/rollup/src/executors/rollup/rollup.impl.spec.ts @@ -1,11 +1,15 @@ import { ExecutorContext } from '@nx/devkit'; +import * as fs from 'fs'; import * as rollup from 'rollup'; import { RollupExecutorOptions } from './schema'; import { createRollupOptions } from './rollup.impl'; import { normalizeRollupExecutorOptions } from './lib/normalize'; jest.mock('rollup-plugin-copy', () => jest.fn()); - +jest.mock('fs', () => ({ + ...jest.requireActual('fs'), + readdirSync: () => [], +})); describe('rollupExecutor', () => { let context: ExecutorContext; let testOptions: RollupExecutorOptions; @@ -35,8 +39,8 @@ describe('rollupExecutor', () => { }); describe('createRollupOptions', () => { - it('should create rollup options for valid config', () => { - const result: any = createRollupOptions( + it('should create rollup options for valid config', async () => { + const result: any = await createRollupOptions( normalizeRollupExecutorOptions( testOptions, { root: '/root' } as any, @@ -73,7 +77,7 @@ describe('rollupExecutor', () => { () => (o) => ({ ...o, prop: 'my-val' }), { virtual: true } ); - const result: any = createRollupOptions( + const result: any = await createRollupOptions( normalizeRollupExecutorOptions( { ...testOptions, rollupConfig: 'custom-rollup.config.ts' }, { root: '/root' } as any, @@ -85,6 +89,7 @@ describe('rollupExecutor', () => { '/root/src', [] ); + expect(result.map((x) => x.prop)).toEqual(['my-val', 'my-val']); }); @@ -103,7 +108,7 @@ describe('rollupExecutor', () => { }), { virtual: true } ); - const result: any = createRollupOptions( + const result: any = await createRollupOptions( normalizeRollupExecutorOptions( { ...testOptions, @@ -121,6 +126,7 @@ describe('rollupExecutor', () => { '/root/src', [] ); + expect(result.map((x) => x.prop1)).toEqual([ 'my-val-my-val-2', 'my-val-my-val-2', @@ -128,8 +134,8 @@ describe('rollupExecutor', () => { expect(result.map((x) => x.prop2)).toEqual(['my-val-2', 'my-val-2']); }); - it(`should always use forward slashes for asset paths`, () => { - createRollupOptions( + it(`should always use forward slashes for asset paths`, async () => { + await createRollupOptions( { ...normalizeRollupExecutorOptions( testOptions, @@ -156,8 +162,8 @@ describe('rollupExecutor', () => { }); }); - it(`should treat npm dependencies as external if external is all`, () => { - const options = createRollupOptions( + it(`should treat npm dependencies as external if external is all`, async () => { + const options = await createRollupOptions( normalizeRollupExecutorOptions( { ...testOptions, external: 'all' }, { root: '/root' } as any, @@ -177,8 +183,8 @@ describe('rollupExecutor', () => { expect(external('rxjs', '', false)).toBe(false); }); - it(`should not treat npm dependencies as external if external is none`, () => { - const options = createRollupOptions( + it(`should not treat npm dependencies as external if external is none`, async () => { + const options = await createRollupOptions( normalizeRollupExecutorOptions( { ...testOptions, external: 'none' }, { root: '/root' } as any, @@ -198,8 +204,8 @@ describe('rollupExecutor', () => { expect(external('rxjs', '', false)).toBe(false); }); - it(`should set external based on options`, () => { - const options = createRollupOptions( + it(`should set external based on options`, async () => { + const options = await createRollupOptions( normalizeRollupExecutorOptions( { ...testOptions, external: ['rxjs'] }, { root: '/root' } as any, diff --git a/packages/rollup/src/executors/rollup/rollup.impl.ts b/packages/rollup/src/executors/rollup/rollup.impl.ts index e431e9d4ccd6f..651933c140147 100644 --- a/packages/rollup/src/executors/rollup/rollup.impl.ts +++ b/packages/rollup/src/executors/rollup/rollup.impl.ts @@ -28,6 +28,7 @@ import { analyze } from './lib/analyze-plugin'; import { deleteOutputDir } from '../../utils/fs'; import { swc } from './lib/swc-plugin'; import { updatePackageJson } from './lib/update-package-json'; +import { loadConfigFile } from '@nx/devkit/src/utils/config-utils'; export type RollupExecutorEvent = { success: boolean; @@ -74,7 +75,7 @@ export async function* rollupExecutor( .filter((d) => d.target.startsWith('npm:')) .map((d) => d.target.slice(4)); - const rollupOptions = createRollupOptions( + const rollupOptions = await createRollupOptions( options, dependencies, context, @@ -155,14 +156,14 @@ export async function* rollupExecutor( // ----------------------------------------------------------------------------- -export function createRollupOptions( +export async function createRollupOptions( options: NormalizedRollupExecutorOptions, dependencies: DependentBuildableProjectNode[], context: ExecutorContext, packageJson: PackageJson, sourceRoot: string, npmDeps: string[] -): rollup.InputOptions[] { +): Promise { const useBabel = options.compiler === 'babel'; const useTsc = options.compiler === 'tsc'; const useSwc = options.compiler === 'swc'; @@ -195,7 +196,7 @@ export function createRollupOptions( options.format = ['cjs']; } - return options.format.map((format, idx) => { + const _rollupOptions = options.format.map(async (format, idx) => { // Either we're generating only one format, so we should bundle types // OR we are generating dual formats, so only bundle types for CJS. const shouldBundleTypes = options.format.length === 1 || format === 'cjs'; @@ -310,10 +311,35 @@ export function createRollupOptions( plugins, }; - return options.rollupConfig.reduce((currentConfig, plugin) => { - return require(plugin)(currentConfig, options); - }, rollupConfig); + const userDefinedRollupConfigs = options.rollupConfig.map((plugin) => + loadConfigFile(plugin) + ); + let finalConfig: rollup.InputOptions = rollupConfig; + for (const _config of userDefinedRollupConfigs) { + const config = await _config; + if (typeof config === 'function') { + finalConfig = config(finalConfig, options); + } else { + finalConfig = { + ...finalConfig, + ...config, + plugins: [ + ...(finalConfig.plugins?.length > 0 ? finalConfig.plugins : []), + ...(config.plugins?.length > 0 ? config.plugins : []), + ], + }; + } + } + + return finalConfig; }); + + const rollupOptions = []; + for (const rollupOption of _rollupOptions) { + rollupOptions.push(await rollupOption); + } + + return rollupOptions; } function createTsCompilerOptions(