From 84e1a88f05626e2d31b8888c003fa893e667edfd Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 27 May 2024 16:03:39 -0400 Subject: [PATCH] feat(bundling): extract rollup plugins into withNx function for use with run-commands --- e2e/react/src/react-package-legacy.test.ts | 202 +++++++++++ e2e/react/src/react-package.test.ts | 62 +--- e2e/rollup/src/rollup-legacy.test.ts | 201 +++++++++++ e2e/rollup/src/rollup.test.ts | 167 ++++----- .../generators/lint-project/lint-project.ts | 8 +- .../js/src/generators/library/library.spec.ts | 12 - packages/js/src/generators/library/library.ts | 78 ++-- .../generators/setup-build/generator.spec.ts | 14 +- .../js/src/plugins/rollup/type-definitions.ts | 5 +- .../library/lib/add-rollup-build-target.ts | 107 ++++-- .../src/generators/library/library.spec.ts | 116 ++++-- .../executors/rollup/lib/normalize.spec.ts | 17 +- .../src/executors/rollup/lib/normalize.ts | 83 +---- .../src/executors/rollup/rollup.impl.ts | 274 +------------- .../configuration.legacy.spec.ts | 148 ++++++++ .../configuration/configuration.spec.ts | 124 +++---- .../generators/configuration/configuration.ts | 80 ++++- .../src/generators/configuration/schema.d.ts | 1 + .../rollup/src/generators/init/init.spec.ts | 4 +- packages/rollup/src/generators/init/init.ts | 11 +- .../analyze-plugin.ts => plugins/analyze.ts} | 0 packages/rollup/src/plugins/delete-output.ts | 14 + .../package-json/generate-package-json.ts | 25 ++ .../package-json}/update-package-json.spec.ts | 0 .../package-json}/update-package-json.ts | 51 +-- .../lib/swc-plugin.ts => plugins/swc.ts} | 0 .../src/plugins/with-nx/get-project-node.ts | 18 + .../plugins/with-nx/normalize-options.spec.ts | 85 +++++ .../src/plugins/with-nx/normalize-options.ts | 107 ++++++ .../src/plugins/with-nx/with-nx-options.ts | 37 ++ .../rollup/src/plugins/with-nx/with-nx.ts | 333 ++++++++++++++++++ packages/rollup/src/utils/has-plugin.ts | 10 + packages/rollup/src/utils/versions.ts | 4 +- packages/rollup/with-nx.ts | 2 + 34 files changed, 1650 insertions(+), 750 deletions(-) create mode 100644 e2e/react/src/react-package-legacy.test.ts create mode 100644 e2e/rollup/src/rollup-legacy.test.ts create mode 100644 packages/rollup/src/generators/configuration/configuration.legacy.spec.ts rename packages/rollup/src/{executors/rollup/lib/analyze-plugin.ts => plugins/analyze.ts} (100%) create mode 100644 packages/rollup/src/plugins/delete-output.ts create mode 100644 packages/rollup/src/plugins/package-json/generate-package-json.ts rename packages/rollup/src/{executors/rollup/lib => plugins/package-json}/update-package-json.spec.ts (100%) rename packages/rollup/src/{executors/rollup/lib => plugins/package-json}/update-package-json.ts (79%) rename packages/rollup/src/{executors/rollup/lib/swc-plugin.ts => plugins/swc.ts} (100%) create mode 100644 packages/rollup/src/plugins/with-nx/get-project-node.ts create mode 100644 packages/rollup/src/plugins/with-nx/normalize-options.spec.ts create mode 100644 packages/rollup/src/plugins/with-nx/normalize-options.ts create mode 100644 packages/rollup/src/plugins/with-nx/with-nx-options.ts create mode 100644 packages/rollup/src/plugins/with-nx/with-nx.ts create mode 100644 packages/rollup/src/utils/has-plugin.ts create mode 100644 packages/rollup/with-nx.ts diff --git a/e2e/react/src/react-package-legacy.test.ts b/e2e/react/src/react-package-legacy.test.ts new file mode 100644 index 00000000000000..071c7c4da445fb --- /dev/null +++ b/e2e/react/src/react-package-legacy.test.ts @@ -0,0 +1,202 @@ +import { + checkFilesDoNotExist, + checkFilesExist, + cleanupProject, + getSize, + killPorts, + newProject, + readFile, + readJson, + rmDist, + runCLI, + runCLIAsync, + tmpProjPath, + uniq, + updateFile, + updateJson, +} from '@nx/e2e/utils'; +import { names } from '@nx/devkit'; +import { join } from 'path'; + +describe('Build React libraries and apps', () => { + /** + * Graph: + * + * childLib + * / + * app => parentLib => + * \ + * childLib2 + * + */ + let app: string; + let parentLib: string; + let childLib: string; + let childLib2: string; + + let proj: string; + + beforeEach(async () => { + process.env.NX_ADD_PLUGINS = 'false'; + app = uniq('app'); + parentLib = uniq('parentlib'); + childLib = uniq('childlib'); + childLib2 = uniq('childlib2'); + + proj = newProject({ packages: ['@nx/react'] }); + + // create dependencies by importing + const createDep = (parent, children: string[]) => { + updateFile( + `libs/${parent}/src/index.ts`, + ` + export * from './lib/${parent}'; + + ${children + .map( + (entry) => + `import { ${ + names(entry).className + } } from '@${proj}/${entry}'; console.log(${ + names(entry).className + });` + ) + .join('\n')} + ` + ); + }; + + runCLI(`generate @nx/react:app ${app} `); + updateJson('nx.json', (json) => ({ + ...json, + generators: { + ...json.generators, + '@nx/react': { + library: { + unitTestRunner: 'none', + }, + }, + }, + })); + // generate buildable libs + runCLI( + `generate @nx/react:library ${parentLib} --bundler=rollup --importPath=@${proj}/${parentLib} --no-interactive --unitTestRunner=jest --skipFormat` + ); + runCLI( + `generate @nx/react:library ${childLib} --bundler=rollup --importPath=@${proj}/${childLib} --no-interactive --unitTestRunner=jest --skipFormat` + ); + runCLI( + `generate @nx/react:library ${childLib2} --bundler=rollup --importPath=@${proj}/${childLib2} --no-interactive --unitTestRunner=jest --skipFormat` + ); + + createDep(parentLib, [childLib, childLib2]); + + updateFile( + `apps/${app}/src/main.tsx`, + ` + import {${names(parentLib).className}} from "@${proj}/${parentLib}"; + console.log(${names(parentLib).className}); + ` + ); + + // Add assets to child lib + updateJson(join('libs', childLib, 'project.json'), (json) => { + json.targets.build.options.assets = [`libs/${childLib}/src/assets`]; + return json; + }); + updateFile(`libs/${childLib}/src/assets/hello.txt`, 'Hello World!'); + }); + + afterEach(() => { + killPorts(); + cleanupProject(); + delete process.env.NX_ADD_PLUGINS; + }); + + describe('Buildable libraries', () => { + it('should build libraries with and without dependencies', () => { + /* + * 1. Without dependencies + */ + runCLI(`build ${childLib}`); + runCLI(`build ${childLib2}`); + + checkFilesExist(`dist/libs/${childLib}/index.esm.js`); + + checkFilesExist(`dist/libs/${childLib2}/index.esm.js`); + + checkFilesExist(`dist/libs/${childLib}/assets/hello.txt`); + checkFilesExist(`dist/libs/${childLib2}/README.md`); + + /* + * 2. With dependencies without existing dist + */ + rmDist(); + + runCLI(`build ${parentLib} --skip-nx-cache`); + + checkFilesExist(`dist/libs/${parentLib}/index.esm.js`); + checkFilesExist(`dist/libs/${childLib}/index.esm.js`); + checkFilesExist(`dist/libs/${childLib2}/index.esm.js`); + + expect(readFile(`dist/libs/${childLib}/index.esm.js`)).not.toContain( + 'react/jsx-dev-runtime' + ); + expect(readFile(`dist/libs/${childLib}/index.esm.js`)).toContain( + 'react/jsx-runtime' + ); + }); + + it('should support --format option', () => { + updateFile( + `libs/${childLib}/src/index.ts`, + (s) => `${s} +export async function f() { return 'a'; } +export async function g() { return 'b'; } +export async function h() { return 'c'; } +` + ); + + runCLI(`build ${childLib} --format cjs,esm`); + + checkFilesExist(`dist/libs/${childLib}/index.cjs.js`); + checkFilesExist(`dist/libs/${childLib}/index.esm.js`); + + const cjsPackageSize = getSize( + tmpProjPath(`dist/libs/${childLib}/index.cjs.js`) + ); + const esmPackageSize = getSize( + tmpProjPath(`dist/libs/${childLib}/index.esm.js`) + ); + + // This is a loose requirement that ESM should be smaller than CJS output. + expect(esmPackageSize).toBeLessThanOrEqual(cjsPackageSize); + }); + + it('should build an app composed out of buildable libs', () => { + const buildFromSource = runCLI( + `build ${app} --buildLibsFromSource=false` + ); + expect(buildFromSource).toContain('Successfully ran target build'); + checkFilesDoNotExist(`apps/${app}/tsconfig/tsconfig.nx-tmp`); + }, 1000000); + + it('should not create a dist folder if there is an error', async () => { + const libName = uniq('lib'); + + runCLI( + `generate @nx/react:lib ${libName} --bundler=rollup --importPath=@${proj}/${libName} --no-interactive --unitTestRunner=jest` + ); + + const mainPath = `libs/${libName}/src/lib/${libName}.tsx`; + updateFile(mainPath, `${readFile(mainPath)}\n console.log(a);`); // should error - "a" will be undefined + + await expect(runCLIAsync(`build ${libName}`)).rejects.toThrow( + /Bundle failed/ + ); + expect(() => { + checkFilesExist(`dist/libs/${libName}/package.json`); + }).toThrow(); + }, 250000); + }); +}); diff --git a/e2e/react/src/react-package.test.ts b/e2e/react/src/react-package.test.ts index 1c79468fc17922..89ff57a22da42d 100644 --- a/e2e/react/src/react-package.test.ts +++ b/e2e/react/src/react-package.test.ts @@ -1,16 +1,12 @@ import { - checkFilesDoNotExist, checkFilesExist, cleanupProject, - getSize, killPorts, newProject, readFile, - readJson, rmDist, runCLI, runCLIAsync, - tmpProjPath, uniq, updateFile, updateJson, @@ -37,7 +33,6 @@ describe('Build React libraries and apps', () => { let proj: string; beforeEach(async () => { - process.env.NX_ADD_PLUGINS = 'false'; app = uniq('app'); parentLib = uniq('parentlib'); childLib = uniq('childlib'); @@ -100,17 +95,28 @@ describe('Build React libraries and apps', () => { ); // Add assets to child lib - updateJson(join('libs', childLib, 'project.json'), (json) => { - json.targets.build.options.assets = [`libs/${childLib}/src/assets`]; - return json; - }); + updateFile( + join('libs', childLib, 'rollup.config.js'), + `const { withNx } = require('@nx/rollup/with-nx'); +module.exports = withNx( + { + main: './src/index.ts', + outputPath: '../../dist/libs/${childLib}', + tsConfig: './tsconfig.lib.json', + compiler: 'babel', + external: ['react', 'react-dom', 'react/jsx-runtime'], + format: ['esm'], + assets: ['./src/assets'], + } +); +` + ); updateFile(`libs/${childLib}/src/assets/hello.txt`, 'Hello World!'); }); afterEach(() => { killPorts(); cleanupProject(); - delete process.env.NX_ADD_PLUGINS; }); describe('Buildable libraries', () => { @@ -147,32 +153,6 @@ describe('Build React libraries and apps', () => { ); }); - it('should support --format option', () => { - updateFile( - `libs/${childLib}/src/index.ts`, - (s) => `${s} -export async function f() { return 'a'; } -export async function g() { return 'b'; } -export async function h() { return 'c'; } -` - ); - - runCLI(`build ${childLib} --format cjs,esm`); - - checkFilesExist(`dist/libs/${childLib}/index.cjs.js`); - checkFilesExist(`dist/libs/${childLib}/index.esm.js`); - - const cjsPackageSize = getSize( - tmpProjPath(`dist/libs/${childLib}/index.cjs.js`) - ); - const esmPackageSize = getSize( - tmpProjPath(`dist/libs/${childLib}/index.esm.js`) - ); - - // This is a loose requirement that ESM should be smaller than CJS output. - expect(esmPackageSize).toBeLessThanOrEqual(cjsPackageSize); - }); - it('should preserve the tsconfig target set by user', () => { // Setup const myLib = uniq('my-lib'); @@ -224,14 +204,6 @@ export async function h() { return 'c'; } expect(content).toContain('function __generator(thisArg, body) {'); }); - it('should build an app composed out of buildable libs', () => { - const buildFromSource = runCLI( - `build ${app} --buildLibsFromSource=false` - ); - expect(buildFromSource).toContain('Successfully ran target build'); - checkFilesDoNotExist(`apps/${app}/tsconfig/tsconfig.nx-tmp`); - }, 1000000); - it('should not create a dist folder if there is an error', async () => { const libName = uniq('lib'); @@ -243,7 +215,7 @@ export async function h() { return 'c'; } updateFile(mainPath, `${readFile(mainPath)}\n console.log(a);`); // should error - "a" will be undefined await expect(runCLIAsync(`build ${libName}`)).rejects.toThrow( - /Bundle failed/ + /Command failed/ ); expect(() => { checkFilesExist(`dist/libs/${libName}/package.json`); diff --git a/e2e/rollup/src/rollup-legacy.test.ts b/e2e/rollup/src/rollup-legacy.test.ts new file mode 100644 index 00000000000000..d2170774d61975 --- /dev/null +++ b/e2e/rollup/src/rollup-legacy.test.ts @@ -0,0 +1,201 @@ +import { + checkFilesExist, + cleanupProject, + newProject, + packageInstall, + readJson, + rmDist, + runCLI, + runCommand, + uniq, + updateFile, + updateJson, +} from '@nx/e2e/utils'; +import { join } from 'path'; + +describe('Rollup Plugin', () => { + let originalAddPluginsEnv: string | undefined; + + beforeAll(() => { + originalAddPluginsEnv = process.env.NX_ADD_PLUGINS; + process.env.NX_ADD_PLUGINS = 'false'; + newProject({ packages: ['@nx/rollup', '@nx/js'] }); + }); + + afterAll(() => { + process.env.NX_ADD_PLUGINS = originalAddPluginsEnv; + cleanupProject(); + }); + + it('should be able to setup project to build node programs with rollup and different compilers', async () => { + const myPkg = uniq('my-pkg'); + runCLI(`generate @nx/js:lib ${myPkg} --bundler=none`); + updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`); + + // babel (default) + runCLI( + `generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts` + ); + rmDist(); + runCLI(`build ${myPkg} --format=cjs,esm --generateExportsField`); + checkFilesExist(`dist/libs/${myPkg}/index.cjs.d.ts`); + expect(readJson(`dist/libs/${myPkg}/package.json`).exports).toEqual({ + '.': { + module: './index.esm.js', + import: './index.cjs.mjs', + default: './index.cjs.js', + }, + './package.json': './package.json', + }); + let output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`); + expect(output).toMatch(/Hello/); + + updateJson(join('libs', myPkg, 'project.json'), (config) => { + delete config.targets.build; + return config; + }); + + // swc + runCLI( + `generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=swc` + ); + rmDist(); + runCLI(`build ${myPkg} --format=cjs,esm --generateExportsField`); + output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`); + expect(output).toMatch(/Hello/); + + updateJson(join('libs', myPkg, 'project.json'), (config) => { + delete config.targets.build; + return config; + }); + + // tsc + runCLI( + `generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=tsc` + ); + rmDist(); + runCLI(`build ${myPkg} --format=cjs,esm --generateExportsField`); + output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`); + expect(output).toMatch(/Hello/); + }, 500000); + + it('should support additional entry-points', async () => { + const myPkg = uniq('my-pkg'); + runCLI(`generate @nx/js:lib ${myPkg} --bundler=none`); + runCLI( + `generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=tsc` + ); + updateJson(join('libs', myPkg, 'project.json'), (config) => { + config.targets.build.options.format = ['cjs', 'esm']; + config.targets.build.options.generateExportsField = true; + config.targets.build.options.additionalEntryPoints = [ + `libs/${myPkg}/src/{foo,bar}.ts`, + ]; + return config; + }); + updateFile(`libs/${myPkg}/src/foo.ts`, `export const foo = 'foo';`); + updateFile(`libs/${myPkg}/src/bar.ts`, `export const bar = 'bar';`); + + runCLI(`build ${myPkg}`); + + checkFilesExist(`dist/libs/${myPkg}/index.esm.js`); + checkFilesExist(`dist/libs/${myPkg}/index.cjs.js`); + checkFilesExist(`dist/libs/${myPkg}/index.cjs.d.ts`); + checkFilesExist(`dist/libs/${myPkg}/foo.esm.js`); + checkFilesExist(`dist/libs/${myPkg}/foo.cjs.js`); + checkFilesExist(`dist/libs/${myPkg}/bar.esm.js`); + checkFilesExist(`dist/libs/${myPkg}/bar.cjs.js`); + expect(readJson(`dist/libs/${myPkg}/package.json`).exports).toEqual({ + './package.json': './package.json', + '.': { + module: './index.esm.js', + import: './index.cjs.mjs', + default: './index.cjs.js', + }, + './bar': { + module: './bar.esm.js', + import: './bar.cjs.mjs', + default: './bar.cjs.js', + }, + './foo': { + module: './foo.esm.js', + import: './foo.cjs.mjs', + default: './foo.cjs.js', + }, + }); + }); + + it('should be able to build libs generated with @nx/js:lib --bundler rollup', () => { + const jsLib = uniq('jslib'); + 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`); + }); + + it('should support array config from rollup.config.js', () => { + const jsLib = uniq('jslib'); + runCLI(`generate @nx/js:lib ${jsLib} --bundler rollup --verbose`); + updateFile( + `libs/${jsLib}/rollup.config.js`, + `module.exports = (config) => [{ + ...config, + output: { + format: "esm", + dir: "dist/test", + name: "Mylib", + entryFileNames: "[name].js", + chunkFileNames: "[name].js" + } + }]` + ); + updateJson(join('libs', jsLib, 'project.json'), (config) => { + config.targets.build.options.rollupConfig = `libs/${jsLib}/rollup.config.js`; + return config; + }); + + expect(() => runCLI(`build ${jsLib} --format=esm`)).not.toThrow(); + + checkFilesExist(`dist/test/index.js`); + }); +}); diff --git a/e2e/rollup/src/rollup.test.ts b/e2e/rollup/src/rollup.test.ts index 4352b7ceea3363..9b8cb11c40cfa6 100644 --- a/e2e/rollup/src/rollup.test.ts +++ b/e2e/rollup/src/rollup.test.ts @@ -9,9 +9,7 @@ import { runCommand, uniq, updateFile, - updateJson, } from '@nx/e2e/utils'; -import { join } from 'path'; describe('Rollup Plugin', () => { beforeAll(() => newProject({ packages: ['@nx/rollup', '@nx/js'] })); @@ -19,15 +17,30 @@ describe('Rollup Plugin', () => { it('should be able to setup project to build node programs with rollup and different compilers', async () => { const myPkg = uniq('my-pkg'); - runCLI(`generate @nx/js:lib ${myPkg} --bundler=none`); + runCLI(`generate @nx/js:lib ${myPkg} --bundler=rollup`); updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`); // babel (default) runCLI( - `generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts` + `generate @nx/rollup:configuration ${myPkg} --tsConfig=./tsconfig.lib.json --main=./src/index.ts` + ); + updateFile( + `libs/${myPkg}/rollup.config.js`, + ` + const { withNx } = require('@nx/rollup/with-nx'); + module.exports = withNx({ + outputPath: '../../dist/libs/${myPkg}', + main: './src/index.ts', + tsConfig: './tsconfig.lib.json', + compiler: 'babel', + generateExportsField: true, + additionalEntryPoints: ['./src/{foo,bar}.ts'], + format: ['cjs', 'esm'] + }); + ` ); rmDist(); - runCLI(`build ${myPkg} --format=cjs,esm --generateExportsField`); + runCLI(`build ${myPkg}`); checkFilesExist(`dist/libs/${myPkg}/index.cjs.d.ts`); expect(readJson(`dist/libs/${myPkg}/package.json`).exports).toEqual({ '.': { @@ -40,31 +53,51 @@ describe('Rollup Plugin', () => { let output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`); expect(output).toMatch(/Hello/); - updateJson(join('libs', myPkg, 'project.json'), (config) => { - delete config.targets.build; - return config; - }); - // swc runCLI( - `generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=swc` + `generate @nx/rollup:configuration ${myPkg} --tsConfig=./tsconfig.lib.json --main=./src/index.ts --compiler=swc` + ); + updateFile( + `libs/${myPkg}/rollup.config.js`, + ` + const { withNx } = require('@nx/rollup/with-nx'); + module.exports = withNx({ + outputPath: '../../dist/libs/${myPkg}', + main: './src/index.ts', + tsConfig: './tsconfig.lib.json', + compiler: 'swc', + generateExportsField: true, + additionalEntryPoints: ['./src/{foo,bar}.ts'], + format: ['cjs', 'esm'] + }); + ` ); rmDist(); - runCLI(`build ${myPkg} --format=cjs,esm --generateExportsField`); + runCLI(`build ${myPkg}`); output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`); expect(output).toMatch(/Hello/); - updateJson(join('libs', myPkg, 'project.json'), (config) => { - delete config.targets.build; - return config; - }); - // tsc runCLI( - `generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=tsc` + `generate @nx/rollup:configuration ${myPkg} --tsConfig=./tsconfig.lib.json --main=./src/index.ts --compiler=tsc` + ); + updateFile( + `libs/${myPkg}/rollup.config.js`, + ` + const { withNx } = require('@nx/rollup/with-nx'); + module.exports = withNx({ + outputPath: '../../dist/libs/${myPkg}', + main: './src/index.ts', + tsConfig: './tsconfig.lib.json', + compiler: 'tsc', + generateExportsField: true, + additionalEntryPoints: ['./src/{foo,bar}.ts'], + format: ['cjs', 'esm'] + }); + ` ); rmDist(); - runCLI(`build ${myPkg} --format=cjs,esm --generateExportsField`); + runCLI(`build ${myPkg}`); output = runCommand(`node dist/libs/${myPkg}/index.cjs.js`); expect(output).toMatch(/Hello/); }, 500000); @@ -73,16 +106,24 @@ describe('Rollup Plugin', () => { const myPkg = uniq('my-pkg'); runCLI(`generate @nx/js:lib ${myPkg} --bundler=none`); runCLI( - `generate @nx/rollup:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=tsc` + `generate @nx/rollup:configuration ${myPkg} --tsConfig=./tsconfig.lib.json --main=./src/index.ts --compiler=tsc` ); - updateJson(join('libs', myPkg, 'project.json'), (config) => { - config.targets.build.options.format = ['cjs', 'esm']; - config.targets.build.options.generateExportsField = true; - config.targets.build.options.additionalEntryPoints = [ - `libs/${myPkg}/src/{foo,bar}.ts`, - ]; - return config; - }); + updateFile( + `libs/${myPkg}/rollup.config.js`, + ` + const { withNx } = require('@nx/rollup/with-nx'); + module.exports = withNx({ + outputPath: '../../dist/libs/${myPkg}', + main: './src/index.ts', + tsConfig: './tsconfig.lib.json', + compiler: 'tsc', + generateExportsField: true, + additionalEntryPoints: ['./src/{foo,bar}.ts'], + format: ['cjs', 'esm'] + }); + ` + ); + updateFile(`libs/${myPkg}/src/foo.ts`, `export const foo = 'foo';`); updateFile(`libs/${myPkg}/src/bar.ts`, `export const bar = 'bar';`); @@ -121,49 +162,7 @@ describe('Rollup Plugin', () => { 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`); - }); - - it('should build correctly with crystal', () => { + it('should work correctly with custom, non-Nx rollup config', () => { // ARRANGE packageInstall('@rollup/plugin-babel', undefined, '5.3.0', 'prod'); packageInstall('@rollup/plugin-commonjs', undefined, '25.0.7', 'prod'); @@ -216,30 +215,4 @@ export default config; checkFilesExist(`libs/test/dist/bundle.js`); checkFilesExist(`libs/test/dist/bundle.es.js`); }); - - it('should support array config from rollup.config.js', () => { - const jsLib = uniq('jslib'); - runCLI(`generate @nx/js:lib ${jsLib} --bundler rollup --verbose`); - updateFile( - `libs/${jsLib}/rollup.config.js`, - `module.exports = (config) => [{ - ...config, - output: { - format: "esm", - dir: "dist/test", - name: "Mylib", - entryFileNames: "[name].js", - chunkFileNames: "[name].js" - } - }]` - ); - updateJson(join('libs', jsLib, 'project.json'), (config) => { - config.targets.build.options.rollupConfig = `libs/${jsLib}/rollup.config.js`; - return config; - }); - - expect(() => runCLI(`build ${jsLib} --format=esm`)).not.toThrow(); - - checkFilesExist(`dist/test/index.js`); - }); }); diff --git a/packages/eslint/src/generators/lint-project/lint-project.ts b/packages/eslint/src/generators/lint-project/lint-project.ts index f0ae852923405e..dba6c0a9f4b89e 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.ts @@ -58,6 +58,7 @@ interface LintProjectOptions { * @internal */ addExplicitTargets?: boolean; + addPackageJsonDependencyChecks?: boolean; } export function lintProjectGenerator(tree: Tree, options: LintProjectOptions) { @@ -158,6 +159,7 @@ export async function lintProjectGeneratorInternal( if (!options.rootProject || projectConfig.root !== '.') { createEsLintConfiguration( tree, + options, projectConfig, options.setParserOptionsProject, options.rootProject @@ -188,6 +190,7 @@ export async function lintProjectGeneratorInternal( function createEsLintConfiguration( tree: Tree, + options: LintProjectOptions, projectConfig: ProjectConfiguration, setParserOptionsProject: boolean, rootProject: boolean @@ -236,7 +239,10 @@ function createEsLintConfiguration( }, ]; - if (isBuildableLibraryProject(projectConfig)) { + if ( + options.addPackageJsonDependencyChecks || + isBuildableLibraryProject(projectConfig) + ) { overrides.push({ files: ['*.json'], parser: 'jsonc-eslint-parser', diff --git a/packages/js/src/generators/library/library.spec.ts b/packages/js/src/generators/library/library.spec.ts index 2941a6172d2106..dc440992a71e82 100644 --- a/packages/js/src/generators/library/library.spec.ts +++ b/packages/js/src/generators/library/library.spec.ts @@ -974,11 +974,6 @@ describe('lib', () => { projectNameAndRootFormat: 'as-provided', }); - const config = readProjectConfiguration(tree, 'my-lib'); - expect(config.targets.build.options.project).toEqual( - `my-lib/package.json` - ); - const pkgJson = readJson(tree, 'my-lib/package.json'); expect(pkgJson.type).not.toBeDefined(); }); @@ -990,9 +985,6 @@ describe('lib', () => { bundler: 'rollup', projectNameAndRootFormat: 'as-provided', }); - - const config = readProjectConfiguration(tree, 'my-lib'); - expect(config.targets.build.options.compiler).toEqual('swc'); }); }); @@ -1509,10 +1501,6 @@ describe('lib', () => { projectNameAndRootFormat: 'as-provided', }); - const project = readProjectConfiguration(tree, 'my-lib'); - expect(project.targets.build).toMatchObject({ - executor: '@nx/rollup:rollup', - }); expect(readJson(tree, 'my-lib/.eslintrc.json').overrides).toContainEqual({ files: ['*.json'], parser: 'jsonc-eslint-parser', diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index e491cd8b800ff2..6ec990008bcfbe 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -89,6 +89,15 @@ export async function libraryGeneratorInternal( tasks.push(await setupVerdaccio(tree, { ...options, skipFormat: true })); } + if (options.bundler === 'rollup') { + const { configurationGenerator } = ensurePackage('@nx/rollup', nxVersion); + await configurationGenerator(tree, { + project: options.name, + compiler: 'swc', + format: ['cjs', 'esm'], + }); + } + if (options.bundler === 'vite') { const { viteConfigurationGenerator, createOrEditViteConfig } = ensurePackage('@nx/vite', nxVersion); @@ -207,47 +216,38 @@ async function addProject(tree: Tree, options: NormalizedSchema) { options.bundler !== 'none' && options.config !== 'npm-scripts' ) { - const outputPath = getOutputPath(options); - - const executor = getBuildExecutor(options.bundler); - - addBuildTargetDefaults(tree, executor); - - projectConfiguration.targets.build = { - executor, - outputs: ['{options.outputPath}'], - options: { - outputPath, - main: `${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'), - tsConfig: `${options.projectRoot}/tsconfig.lib.json`, - assets: [], - }, - }; - - if (options.bundler === 'esbuild') { - projectConfiguration.targets.build.options.generatePackageJson = true; - projectConfiguration.targets.build.options.format = ['cjs']; - } + if (options.bundler !== 'rollup') { + const outputPath = getOutputPath(options); + const executor = getBuildExecutor(options.bundler); + addBuildTargetDefaults(tree, executor); + + projectConfiguration.targets.build = { + executor, + outputs: ['{options.outputPath}'], + options: { + outputPath, + main: + `${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'), + tsConfig: `${options.projectRoot}/tsconfig.lib.json`, + assets: [], + }, + }; - if (options.bundler === 'rollup') { - projectConfiguration.targets.build.options.project = `${options.projectRoot}/package.json`; - projectConfiguration.targets.build.options.compiler = 'swc'; - projectConfiguration.targets.build.options.format = ['cjs', 'esm']; - } + if (options.bundler === 'esbuild') { + projectConfiguration.targets.build.options.generatePackageJson = true; + projectConfiguration.targets.build.options.format = ['cjs']; + } - if (options.bundler === 'swc' && options.skipTypeCheck) { - projectConfiguration.targets.build.options.skipTypeCheck = true; - } + if (options.bundler === 'swc' && options.skipTypeCheck) { + projectConfiguration.targets.build.options.skipTypeCheck = true; + } - if ( - !options.minimal && - // TODO(jack): assets for rollup have validation that we need to fix (assets must be under /src) - options.bundler !== 'rollup' - ) { - projectConfiguration.targets.build.options.assets ??= []; - projectConfiguration.targets.build.options.assets.push( - joinPathFragments(options.projectRoot, '*.md') - ); + if (!options.minimal) { + projectConfiguration.targets.build.options.assets ??= []; + projectConfiguration.targets.build.options.assets.push( + joinPathFragments(options.projectRoot, '*.md') + ); + } } if (options.publishable) { @@ -321,6 +321,8 @@ export async function addLint( setParserOptionsProject: options.setParserOptionsProject, rootProject: options.rootProject, addPlugin: options.addPlugin, + // Since the build target is inferred now, we need to let the generator know to add @nx/dependency-checks regardless. + addPackageJsonDependencyChecks: options.bundler !== 'none', }); const { addOverrideToLintConfig, diff --git a/packages/js/src/generators/setup-build/generator.spec.ts b/packages/js/src/generators/setup-build/generator.spec.ts index 66655b65b7df84..aec3504f8447a1 100644 --- a/packages/js/src/generators/setup-build/generator.spec.ts +++ b/packages/js/src/generators/setup-build/generator.spec.ts @@ -138,19 +138,7 @@ describe('setup-build generator', () => { bundler: 'rollup', }); - const config = readProjectConfiguration(tree, 'mypkg'); - expect(config).toMatchObject({ - targets: { - build: { - executor: '@nx/rollup:rollup', - options: { - outputPath: 'dist/packages/mypkg', - main: 'packages/mypkg/src/main.ts', - tsConfig: 'packages/mypkg/tsconfig.lib.json', - }, - }, - }, - }); + expect(tree.exists('packages/mypkg/rollup.config.js')).toBe(true); }); it('should support --bundler=esbuild', async () => { diff --git a/packages/js/src/plugins/rollup/type-definitions.ts b/packages/js/src/plugins/rollup/type-definitions.ts index 08b62d6fb1c1a7..f82ddf505bab5d 100644 --- a/packages/js/src/plugins/rollup/type-definitions.ts +++ b/packages/js/src/plugins/rollup/type-definitions.ts @@ -16,10 +16,7 @@ import { stripIndents } from '@nx/devkit'; * We want a third file: `dist/index.d.ts` that re-exports from `src/index.d.ts`. * That way, when TSC or IDEs look for types, it will find them in the right place. */ -export function typeDefinitions(options: { - projectRoot: string; - main: string; -}) { +export function typeDefinitions(options: { projectRoot: string }) { return { name: 'dts-bundle', async generateBundle(_opts: unknown, bundle: OutputBundle): Promise { diff --git a/packages/react/src/generators/library/lib/add-rollup-build-target.ts b/packages/react/src/generators/library/lib/add-rollup-build-target.ts index dcb1d7a5ce4715..a7cb9ff47bebc0 100644 --- a/packages/react/src/generators/library/lib/add-rollup-build-target.ts +++ b/packages/react/src/generators/library/lib/add-rollup-build-target.ts @@ -1,11 +1,14 @@ import { Tree } from 'nx/src/generators/tree'; import { - GeneratorCallback, addDependenciesToPackageJson, ensurePackage, + GeneratorCallback, joinPathFragments, + offsetFromRoot, + readNxJson, readProjectConfiguration, runTasksInSerial, + stripIndents, updateProjectConfiguration, } from '@nx/devkit'; @@ -49,8 +52,6 @@ export async function addRollupBuildTarget( ); } - const { targets } = readProjectConfiguration(host, options.name); - const external: string[] = ['react', 'react-dom']; if (options.style === '@emotion/styled') { @@ -59,34 +60,80 @@ export async function addRollupBuildTarget( external.push('react/jsx-runtime'); } - targets.build = { - executor: '@nx/rollup:rollup', - outputs: ['{options.outputPath}'], - options: { - outputPath: joinPathFragments('dist', options.projectRoot), - tsConfig: `${options.projectRoot}/tsconfig.lib.json`, - project: `${options.projectRoot}/package.json`, - entryFile: maybeJs(options, `${options.projectRoot}/src/index.ts`), - external, - rollupConfig: `@nx/react/plugins/bundle-rollup`, - compiler: options.compiler ?? 'babel', - assets: [ - { - glob: `${options.projectRoot}/README.md`, - input: '.', - output: '.', - }, - ], - }, - }; + const nxJson = readNxJson(host); + const hasRollupPlugin = !!nxJson.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/rollup/plugin' + : p.plugin === '@nx/rollup/plugin' + ); + if (hasRollupPlugin) { + // New behavior, using rollup config file and inferred target. + host.write( + joinPathFragments(options.projectRoot, 'rollup.config.js'), + stripIndents` + const { withNx } = require('@nx/rollup/with-nx'); + const url = require('@rollup/plugin-url'); + const svg = require('@svgr/rollup'); + + module.exports = withNx({ + main: '${maybeJs(options, './src/index.ts')}', + outputPath: '${joinPathFragments( + offsetFromRoot(options.projectRoot), + 'dist', + options.projectRoot + )}', + tsConfig: './tsconfig.lib.json', + compiler: '${options.compiler ?? 'babel'}', + external: ${JSON.stringify(external)}, + format: ['esm'], + assets:[{ input: '.', output: '.', glob: 'README.md'}], + }, { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + plugins: [ + svg({ + svgo: false, + titleProp: true, + ref: true, + }), + url({ + limit: 10000, // 10kB + }), + ], + }); + ` + ); + } else { + // Legacy behavior, there is a target in project.json using rollup executor. + const { targets } = readProjectConfiguration(host, options.name); + targets.build = { + executor: '@nx/rollup:rollup', + outputs: ['{options.outputPath}'], + options: { + outputPath: joinPathFragments('dist', options.projectRoot), + tsConfig: `${options.projectRoot}/tsconfig.lib.json`, + project: `${options.projectRoot}/package.json`, + entryFile: maybeJs(options, `${options.projectRoot}/src/index.ts`), + external, + rollupConfig: `@nx/react/plugins/bundle-rollup`, + compiler: options.compiler ?? 'babel', + assets: [ + { + glob: `${options.projectRoot}/README.md`, + input: '.', + output: '.', + }, + ], + }, + }; - updateProjectConfiguration(host, options.name, { - root: options.projectRoot, - sourceRoot: joinPathFragments(options.projectRoot, 'src'), - projectType: 'library', - tags: options.parsedTags, - targets, - }); + updateProjectConfiguration(host, options.name, { + root: options.projectRoot, + sourceRoot: joinPathFragments(options.projectRoot, 'src'), + projectType: 'library', + tags: options.parsedTags, + targets, + }); + } return runTasksInSerial(...tasks); } diff --git a/packages/react/src/generators/library/library.spec.ts b/packages/react/src/generators/library/library.spec.ts index 80de7cf2311d11..36954e4728669c 100644 --- a/packages/react/src/generators/library/library.spec.ts +++ b/packages/react/src/generators/library/library.spec.ts @@ -496,21 +496,89 @@ describe('lib', () => { }); describe('--buildable', () => { - it('should have a builder defined', async () => { + it('should default to rollup bundler', async () => { await libraryGenerator(tree, { ...defaultSchema, buildable: true, }); - const projectsConfigurations = getProjects(tree); - expect(projectsConfigurations.get('my-lib').targets.build).toBeDefined(); + expect(tree.exists('my-lib/rollup.config.js')).toBeTruthy(); }); }); describe('--publishable', () => { - it('should add build targets', async () => { + it('should fail if no importPath is provided with publishable', async () => { + expect.assertions(1); + + try { + await libraryGenerator(tree, { + ...defaultSchema, + directory: 'myDir', + publishable: true, + }); + } catch (e) { + expect(e.message).toContain( + 'For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)' + ); + } + }); + + it('should add package.json and .babelrc', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + publishable: true, + importPath: '@proj/my-lib', + }); + + const packageJson = readJson(tree, '/my-lib/package.json'); + expect(packageJson.name).toEqual('@proj/my-lib'); + expect(tree.exists('/my-lib/.babelrc')); + }); + + it('should add rollup config file', async () => { await libraryGenerator(tree, { ...defaultSchema, + skipFormat: false, + publishable: true, + importPath: '@proj/my-lib', + }); + + expect(tree.read('my-lib/rollup.config.js', 'utf-8')) + .toEqual(`const { withNx } = require('@nx/rollup/with-nx'); +const url = require('@rollup/plugin-url'); +const svg = require('@svgr/rollup'); + +module.exports = withNx( + { + main: './src/index.ts', + outputPath: '../dist/my-lib', + tsConfig: './tsconfig.lib.json', + compiler: 'babel', + external: ['react', 'react-dom', 'react/jsx-runtime'], + format: ['esm'], + assets: [{ input: '.', output: '.', glob: 'README.md' }], + }, + { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + plugins: [ + svg({ + svgo: false, + titleProp: true, + ref: true, + }), + url({ + limit: 10000, // 10kB + }), + ], + } +); +`); + }); + + it('should add build targets (legacy)', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + addPlugin: false, publishable: true, importPath: '@proj/my-lib', }); @@ -531,25 +599,10 @@ describe('lib', () => { }); }); - it('should fail if no importPath is provided with publishable', async () => { - expect.assertions(1); - - try { - await libraryGenerator(tree, { - ...defaultSchema, - directory: 'myDir', - publishable: true, - }); - } catch (e) { - expect(e.message).toContain( - 'For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)' - ); - } - }); - - it('should support styled-components', async () => { + it('should support styled-components (legacy)', async () => { await libraryGenerator(tree, { ...defaultSchema, + addPlugin: false, publishable: true, importPath: '@proj/my-lib', style: 'styled-components', @@ -568,9 +621,10 @@ describe('lib', () => { ]); }); - it('should support @emotion/styled', async () => { + it('should support @emotion/styled (legacy)', async () => { await libraryGenerator(tree, { ...defaultSchema, + addPlugin: false, publishable: true, importPath: '@proj/my-lib', style: '@emotion/styled', @@ -591,9 +645,10 @@ describe('lib', () => { ); }); - it('should support styled-jsx', async () => { + it('should support styled-jsx (legacy)', async () => { await libraryGenerator(tree, { ...defaultSchema, + addPlugin: false, publishable: true, importPath: '@proj/my-lib', style: 'styled-jsx', @@ -610,9 +665,10 @@ describe('lib', () => { expect(babelrc.plugins).toEqual(['styled-jsx/babel']); }); - it('should support style none', async () => { + it('should support style none (legacy)', async () => { await libraryGenerator(tree, { ...defaultSchema, + addPlugin: false, publishable: true, importPath: '@proj/my-lib', style: 'none', @@ -626,18 +682,6 @@ describe('lib', () => { }, }); }); - - it('should add package.json and .babelrc', async () => { - await libraryGenerator(tree, { - ...defaultSchema, - publishable: true, - importPath: '@proj/my-lib', - }); - - const packageJson = readJson(tree, '/my-lib/package.json'); - expect(packageJson.name).toEqual('@proj/my-lib'); - expect(tree.exists('/my-lib/.babelrc')); - }); }); describe('--js', () => { diff --git a/packages/rollup/src/executors/rollup/lib/normalize.spec.ts b/packages/rollup/src/executors/rollup/lib/normalize.spec.ts index 6aa7ff0dd2d069..0dff49955fb9d8 100644 --- a/packages/rollup/src/executors/rollup/lib/normalize.spec.ts +++ b/packages/rollup/src/executors/rollup/lib/normalize.spec.ts @@ -4,7 +4,6 @@ import { RollupExecutorOptions } from '../schema'; describe('normalizeRollupExecutorOptions', () => { let testOptions: RollupExecutorOptions; let root: string; - let sourceRoot: string; beforeEach(() => { testOptions = { @@ -16,15 +15,10 @@ describe('normalizeRollupExecutorOptions', () => { format: ['esm'], }; root = '/root'; - sourceRoot = 'apps/nodeapp/src'; }); it('should resolve both node modules and relative path for rollupConfig', () => { - let result = normalizeRollupExecutorOptions( - testOptions, - { root } as any, - sourceRoot - ); + let result = normalizeRollupExecutorOptions(testOptions, { root } as any); expect(result.rollupConfig).toEqual(['/root/apps/nodeapp/rollup.config']); result = normalizeRollupExecutorOptions( @@ -33,8 +27,7 @@ describe('normalizeRollupExecutorOptions', () => { // something that exists in node_modules rollupConfig: 'react', }, - { root } as any, - sourceRoot + { root } as any ); expect(result.rollupConfig).toHaveLength(1); expect(result.rollupConfig[0]).toMatch('react'); @@ -45,11 +38,7 @@ describe('normalizeRollupExecutorOptions', () => { it('should handle rollupConfig being undefined', () => { delete testOptions.rollupConfig; - const result = normalizeRollupExecutorOptions( - testOptions, - { root } as any, - sourceRoot - ); + const result = normalizeRollupExecutorOptions(testOptions, { root } as any); expect(result.rollupConfig).toEqual([]); }); }); diff --git a/packages/rollup/src/executors/rollup/lib/normalize.ts b/packages/rollup/src/executors/rollup/lib/normalize.ts index a406db8d1272f9..f9445c22669f1a 100644 --- a/packages/rollup/src/executors/rollup/lib/normalize.ts +++ b/packages/rollup/src/executors/rollup/lib/normalize.ts @@ -1,52 +1,26 @@ -import { basename, dirname, join, relative, resolve } from 'path'; -import { statSync } from 'fs'; -import { ExecutorContext, normalizePath } from '@nx/devkit'; +import { resolve } from 'path'; +import { ExecutorContext } from '@nx/devkit'; -import type { AssetGlobPattern, RollupExecutorOptions } from '../schema'; -import { createEntryPoints } from '@nx/js'; +import type { RollupExecutorOptions } from '../schema'; export interface NormalizedRollupExecutorOptions extends RollupExecutorOptions { - entryRoot: string; projectRoot: string; - assets: AssetGlobPattern[]; rollupConfig: string[]; } export function normalizeRollupExecutorOptions( options: RollupExecutorOptions, - context: ExecutorContext, - sourceRoot: string + context: ExecutorContext ): NormalizedRollupExecutorOptions { const { root } = context; - const main = `${root}/${options.main}`; - const entryRoot = dirname(main); - const project = options.project - ? `${root}/${options.project}` - : join(root, 'package.json'); - const projectRoot = dirname(project); - const outputPath = `${root}/${options.outputPath}`; - return { ...options, - // de-dupe formats - format: Array.from(new Set(options.format)), rollupConfig: [] .concat(options.rollupConfig) .filter(Boolean) .map((p) => normalizePluginPath(p, root)), - assets: options.assets - ? normalizeAssets(options.assets, root, sourceRoot) - : undefined, - main, - entryRoot, - project, - projectRoot, - outputPath, + projectRoot: context.projectGraph.nodes[context.projectName].data.root, skipTypeCheck: options.skipTypeCheck || false, - additionalEntryPoints: createEntryPoints( - options.additionalEntryPoints, - context.root - ), }; } @@ -60,50 +34,3 @@ export function normalizePluginPath(pluginPath: void | string, root: string) { return resolve(root, pluginPath); } } - -export function normalizeAssets( - assets: any[], - root: string, - sourceRoot: string -): AssetGlobPattern[] { - return assets.map((asset) => { - if (typeof asset === 'string') { - const assetPath = normalizePath(asset); - const resolvedAssetPath = resolve(root, assetPath); - const resolvedSourceRoot = resolve(root, sourceRoot); - - if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) { - throw new Error( - `The ${resolvedAssetPath} asset path must start with the project source root: ${sourceRoot}` - ); - } - - const isDirectory = statSync(resolvedAssetPath).isDirectory(); - const input = isDirectory - ? resolvedAssetPath - : dirname(resolvedAssetPath); - const output = relative(resolvedSourceRoot, resolve(root, input)); - const glob = isDirectory ? '**/*' : basename(resolvedAssetPath); - return { - input, - output, - glob, - }; - } else { - if (asset.output.startsWith('..')) { - throw new Error( - 'An asset cannot be written to a location outside of the output path.' - ); - } - - const assetPath = normalizePath(asset.input); - const resolvedAssetPath = resolve(root, assetPath); - return { - ...asset, - input: resolvedAssetPath, - // Now we remove starting slash to make Webpack place it from the output root. - output: asset.output.replace(/^\//, ''), - }; - } - }); -} diff --git a/packages/rollup/src/executors/rollup/rollup.impl.ts b/packages/rollup/src/executors/rollup/rollup.impl.ts index f3c657027b20bd..ddedb0e32c7112 100644 --- a/packages/rollup/src/executors/rollup/rollup.impl.ts +++ b/packages/rollup/src/executors/rollup/rollup.impl.ts @@ -1,85 +1,24 @@ -import * as ts from 'typescript'; import * as rollup from 'rollup'; -import { getBabelInputPlugin } from '@rollup/plugin-babel'; -import { dirname, join, parse, resolve } from 'path'; -import * as autoprefixer from 'autoprefixer'; -import { - type ExecutorContext, - joinPathFragments, - logger, - names, - readJsonFile, -} from '@nx/devkit'; -import { - calculateProjectBuildableDependencies, - computeCompilerOptionsPaths, - DependentBuildableProjectNode, -} from '@nx/js/src/utils/buildable-libs-utils'; -import nodeResolve from '@rollup/plugin-node-resolve'; -import type { PackageJson } from 'nx/src/utils/package-json'; -import { typeDefinitions } from '@nx/js/src/plugins/rollup/type-definitions'; +import { parse, resolve } from 'path'; +import { type ExecutorContext, logger } from '@nx/devkit'; -import { AssetGlobPattern, RollupExecutorOptions } from './schema'; +import { RollupExecutorOptions } from './schema'; import { NormalizedRollupExecutorOptions, normalizeRollupExecutorOptions, } from './lib/normalize'; -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'; import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable'; - -// These use require because the ES import isn't correct. -const commonjs = require('@rollup/plugin-commonjs'); -const image = require('@rollup/plugin-image'); - -const json = require('@rollup/plugin-json'); -const copy = require('rollup-plugin-copy'); -const postcss = require('rollup-plugin-postcss'); - -const fileExtensions = ['.js', '.jsx', '.ts', '.tsx']; +import { withNx } from '../../plugins/with-nx/with-nx'; +import { calculateProjectBuildableDependencies } from '@nx/js/src/utils/buildable-libs-utils'; export async function* rollupExecutor( rawOptions: RollupExecutorOptions, context: ExecutorContext ) { process.env.NODE_ENV ??= 'production'; - - const project = context.projectsConfigurations.projects[context.projectName]; - const sourceRoot = project.sourceRoot; - const { dependencies } = calculateProjectBuildableDependencies( - context.taskGraph, - context.projectGraph, - context.root, - context.projectName, - context.targetName, - context.configurationName, - true - ); - - const options = normalizeRollupExecutorOptions( - rawOptions, - context, - sourceRoot - ); - - const packageJson = readJsonFile(options.project); - - const npmDeps = (context.projectGraph.dependencies[context.projectName] ?? []) - .filter((d) => d.target.startsWith('npm:')) - .map((d) => d.target.slice(4)); - - const rollupOptions = await createRollupOptions( - options, - dependencies, - context, - packageJson, - sourceRoot, - npmDeps - ); - + const options = normalizeRollupExecutorOptions(rawOptions, context); + const rollupOptions = await createRollupOptions(options, context); const outfile = resolveOutfile(context, options); if (options.watch) { @@ -90,7 +29,6 @@ export async function* rollupExecutor( if (data.code === 'START') { logger.info(`Bundling ${context.projectName}...`); } else if (data.code === 'END') { - updatePackageJson(options, packageJson); logger.info('Bundle complete. Watching for file changes...'); next({ success: true, outfile }); } else if (data.code === 'ERROR') { @@ -111,11 +49,6 @@ export async function* rollupExecutor( try { logger.info(`Bundling ${context.projectName}...`); - // Delete output path before bundling - if (options.deleteOutputPath) { - deleteOutputDir(context.root, options.outputPath); - } - const start = process.hrtime.bigint(); const allRollupOptions = Array.isArray(rollupOptions) ? rollupOptions @@ -133,7 +66,6 @@ export async function* rollupExecutor( const end = process.hrtime.bigint(); const duration = `${(Number(end - start) / 1_000_000_000).toFixed(2)}s`; - updatePackageJson(options, packageJson); logger.info(`⚡ Done in ${duration}`); return { success: true, outfile }; } catch (e) { @@ -154,140 +86,19 @@ export async function* rollupExecutor( export async function createRollupOptions( options: NormalizedRollupExecutorOptions, - dependencies: DependentBuildableProjectNode[], - context: ExecutorContext, - packageJson: PackageJson, - sourceRoot: string, - npmDeps: string[] + context: ExecutorContext ): Promise { - const useBabel = options.compiler === 'babel'; - const useSwc = options.compiler === 'swc'; - - const tsConfigPath = joinPathFragments(context.root, options.tsConfig); - const configFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile); - const config = ts.parseJsonConfigFileContent( - configFile.config, - ts.sys, - dirname(tsConfigPath) + const { dependencies } = calculateProjectBuildableDependencies( + context.taskGraph, + context.projectGraph, + context.root, + context.projectName, + context.targetName, + context.configurationName, + true ); - if (!options.format || !options.format.length) { - options.format = readCompatibleFormats(config); - } - - if (packageJson.type === 'module') { - if (options.format.includes('cjs')) { - logger.warn( - `Package type is set to "module" but "cjs" format is included. Going to use "esm" format instead. You can change the package type to "commonjs" or remove type in the package.json file.` - ); - } - options.format = ['esm']; - } else if (packageJson.type === 'commonjs') { - if (options.format.includes('esm')) { - logger.warn( - `Package type is set to "commonjs" but "esm" format is included. Going to use "cjs" format instead. You can change the package type to "module" or remove type in the package.json file.` - ); - } - options.format = ['cjs']; - } - - const plugins = [ - copy({ - targets: convertCopyAssetsToRollupOptions( - options.outputPath, - options.assets - ), - }), - image(), - json(), - // Needed to generate type definitions, even if we're using babel or swc. - require('rollup-plugin-typescript2')({ - check: !options.skipTypeCheck, - tsconfig: options.tsConfig, - tsconfigOverride: { - compilerOptions: createTsCompilerOptions(config, dependencies, options), - }, - }), - - typeDefinitions({ - main: options.main, - projectRoot: options.projectRoot, - }), - postcss({ - inject: true, - extract: options.extractCss, - autoModules: true, - plugins: [autoprefixer], - use: { - less: { - javascriptEnabled: options.javascriptEnabled, - }, - }, - }), - nodeResolve({ - preferBuiltins: true, - extensions: fileExtensions, - }), - useSwc && swc(), - useBabel && - getBabelInputPlugin({ - // Lets `@nx/js/babel` preset know that we are packaging. - caller: { - // @ts-ignore - // Ignoring type checks for caller since we have custom attributes - isNxPackage: true, - // Always target esnext and let rollup handle cjs - supportsStaticESM: true, - isModern: true, - }, - cwd: join(context.root, sourceRoot), - rootMode: options.babelUpwardRootMode ? 'upward' : undefined, - babelrc: true, - extensions: fileExtensions, - babelHelpers: 'bundled', - skipPreflightCheck: true, // pre-flight check may yield false positives and also slows down the build - exclude: /node_modules/, - }), - commonjs(), - analyze(), - ]; - - let externalPackages = [ - ...Object.keys(packageJson.dependencies || {}), - ...Object.keys(packageJson.peerDependencies || {}), - ]; // If external is set to none, include all dependencies and peerDependencies in externalPackages - if (options.external === 'all') { - externalPackages = externalPackages - .concat(dependencies.map((d) => d.name)) - .concat(npmDeps); - } else if (Array.isArray(options.external) && options.external.length > 0) { - externalPackages = externalPackages.concat(options.external); - } - externalPackages = [...new Set(externalPackages)]; - - const mainEntryFileName = options.outputFileName || options.main; - const input: Record = {}; - input[parse(mainEntryFileName).name] = options.main; - options.additionalEntryPoints.forEach((entry) => { - input[parse(entry).name] = entry; - }); - - const rollupConfig = { - input, - output: options.format.map((format) => ({ - format, - dir: `${options.outputPath}`, - name: names(context.projectName).className, - entryFileNames: `[name].${format}.js`, - chunkFileNames: `[name].${format}.js`, - })), - external: (id: string) => { - return externalPackages.some( - (name) => id === name || id.startsWith(`${name}/`) - ); // Could be a deep import - }, - plugins, - }; + const rollupConfig = withNx(options, {}, dependencies); const userDefinedRollupConfigs = options.rollupConfig.map((plugin) => loadConfigFile(plugin) @@ -314,57 +125,6 @@ export async function createRollupOptions( return finalConfig; } -function createTsCompilerOptions( - config: ts.ParsedCommandLine, - dependencies: DependentBuildableProjectNode[], - options: NormalizedRollupExecutorOptions -) { - const compilerOptionPaths = computeCompilerOptionsPaths(config, dependencies); - const compilerOptions = { - rootDir: options.projectRoot, - allowJs: options.allowJs, - declaration: true, - paths: compilerOptionPaths, - }; - if (config.options.module === ts.ModuleKind.CommonJS) { - compilerOptions['module'] = 'ESNext'; - } - if (options.compiler === 'swc') { - compilerOptions['emitDeclarationOnly'] = true; - } - return compilerOptions; -} - -interface RollupCopyAssetOption { - src: string; - dest: string; -} - -function convertCopyAssetsToRollupOptions( - outputPath: string, - assets: AssetGlobPattern[] -): RollupCopyAssetOption[] { - return assets - ? assets.map((a) => ({ - src: join(a.input, a.glob).replace(/\\/g, '/'), - dest: join(outputPath, a.output).replace(/\\/g, '/'), - })) - : undefined; -} - -function readCompatibleFormats( - config: ts.ParsedCommandLine -): ('cjs' | 'esm')[] { - switch (config.options.module) { - case ts.ModuleKind.CommonJS: - case ts.ModuleKind.UMD: - case ts.ModuleKind.AMD: - return ['cjs']; - default: - return ['esm']; - } -} - function resolveOutfile( context: ExecutorContext, options: NormalizedRollupExecutorOptions diff --git a/packages/rollup/src/generators/configuration/configuration.legacy.spec.ts b/packages/rollup/src/generators/configuration/configuration.legacy.spec.ts new file mode 100644 index 00000000000000..82ab4ec6a65b97 --- /dev/null +++ b/packages/rollup/src/generators/configuration/configuration.legacy.spec.ts @@ -0,0 +1,148 @@ +import 'nx/src/internal-testing-utils/mock-project-graph'; + +import { + addProjectConfiguration, + readJson, + readProjectConfiguration, + Tree, + updateProjectConfiguration, + writeJson, +} from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; + +import configurationGenerator from './configuration'; + +describe('configurationGenerator', () => { + let tree: Tree; + + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + addProjectConfiguration(tree, 'mypkg', { + root: 'libs/mypkg', + sourceRoot: 'libs/mypkg/src', + targets: {}, + }); + }); + + it('should generate files', async () => { + await configurationGenerator(tree, { + addPlugin: false, + project: 'mypkg', + }); + + const project = readProjectConfiguration(tree, 'mypkg'); + + expect(project.targets).toMatchObject({ + build: { + executor: '@nx/rollup:rollup', + outputs: ['{options.outputPath}'], + options: { + main: 'libs/mypkg/src/index.ts', + }, + }, + }); + + expect(readJson(tree, 'libs/mypkg/package.json')).toEqual({ + name: '@proj/mypkg', + version: '0.0.1', + }); + }); + + it('should respect existing package.json file', async () => { + writeJson(tree, 'libs/mypkg/package.json', { + name: '@acme/mypkg', + version: '1.0.0', + }); + await configurationGenerator(tree, { + addPlugin: false, + project: 'mypkg', + }); + + expect(readJson(tree, 'libs/mypkg/package.json')).toEqual({ + name: '@acme/mypkg', + version: '1.0.0', + }); + }); + + it('should support --main option', async () => { + await configurationGenerator(tree, { + addPlugin: false, + project: 'mypkg', + main: 'libs/mypkg/index.ts', + }); + + const project = readProjectConfiguration(tree, 'mypkg'); + + expect(project.targets).toMatchObject({ + build: { + executor: '@nx/rollup:rollup', + outputs: ['{options.outputPath}'], + options: { + main: 'libs/mypkg/index.ts', + }, + }, + }); + }); + + it('should support --tsConfig option', async () => { + await configurationGenerator(tree, { + addPlugin: false, + project: 'mypkg', + tsConfig: 'libs/mypkg/tsconfig.custom.json', + }); + + const project = readProjectConfiguration(tree, 'mypkg'); + + expect(project.targets).toMatchObject({ + build: { + executor: '@nx/rollup:rollup', + outputs: ['{options.outputPath}'], + options: { + tsConfig: 'libs/mypkg/tsconfig.custom.json', + }, + }, + }); + }); + + it('should carry over known executor options from existing build target', async () => { + updateProjectConfiguration(tree, 'mypkg', { + root: 'libs/mypkg', + sourceRoot: 'libs/mypkg/src', + targets: { + build: { + executor: '@nx/js:tsc', + options: { + main: 'libs/mypkg/src/custom.ts', + outputPath: 'dist/custom', + tsConfig: 'libs/mypkg/src/tsconfig.custom.json', + additionalEntryPoints: ['libs/mypkg/src/extra.ts'], + generateExportsField: true, + }, + }, + }, + }); + + await configurationGenerator(tree, { + addPlugin: false, + project: 'mypkg', + buildTarget: 'build', + skipValidation: true, + }); + + const project = readProjectConfiguration(tree, 'mypkg'); + + expect(project.targets).toMatchObject({ + build: { + executor: '@nx/rollup:rollup', + outputs: ['{options.outputPath}'], + options: { + main: 'libs/mypkg/src/custom.ts', + outputPath: 'dist/custom', + tsConfig: 'libs/mypkg/src/tsconfig.custom.json', + additionalEntryPoints: ['libs/mypkg/src/extra.ts'], + generateExportsField: true, + }, + }, + }); + }); +}); diff --git a/packages/rollup/src/generators/configuration/configuration.spec.ts b/packages/rollup/src/generators/configuration/configuration.spec.ts index 4f04fc2ae070fb..d16b98d28f82ea 100644 --- a/packages/rollup/src/generators/configuration/configuration.spec.ts +++ b/packages/rollup/src/generators/configuration/configuration.spec.ts @@ -5,7 +5,6 @@ import { readJson, readProjectConfiguration, Tree, - updateProjectConfiguration, writeJson, } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; @@ -31,16 +30,7 @@ describe('configurationGenerator', () => { const project = readProjectConfiguration(tree, 'mypkg'); - expect(project.targets).toMatchObject({ - build: { - executor: '@nx/rollup:rollup', - outputs: ['{options.outputPath}'], - defaultConfiguration: 'production', - options: { - main: 'libs/mypkg/src/main.ts', - }, - }, - }); + expect(project.targets?.build).toBeUndefined(); expect(readJson(tree, 'libs/mypkg/package.json')).toEqual({ name: '@proj/mypkg', @@ -66,82 +56,58 @@ describe('configurationGenerator', () => { it('should support --main option', async () => { await configurationGenerator(tree, { project: 'mypkg', - main: 'libs/mypkg/index.ts', + main: './src/index.ts', }); - const project = readProjectConfiguration(tree, 'mypkg'); - - expect(project.targets).toMatchObject({ - build: { - executor: '@nx/rollup:rollup', - outputs: ['{options.outputPath}'], - defaultConfiguration: 'production', - options: { - main: 'libs/mypkg/index.ts', - }, - }, - }); + const rollupConfig = tree.read('libs/mypkg/rollup.config.js', 'utf-8'); + + expect(rollupConfig) + .toEqual(`const { withNx } = require('@nx/rollup/with-nx'); + +module.exports = withNx( + { + main: './src/index.ts', + outputPath: '../../dist/libs/mypkg', + tsConfig: './tsconfig.lib.json', + compiler: 'babel', + format: ['esm'], + assets: [{ input: '.', output: '.', glob: '*.md' }], + }, + { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + // e.g. + // output: { sourcemap: true }, + } +); +`); }); it('should support --tsConfig option', async () => { await configurationGenerator(tree, { project: 'mypkg', - tsConfig: 'libs/mypkg/tsconfig.custom.json', - }); - - const project = readProjectConfiguration(tree, 'mypkg'); - - expect(project.targets).toMatchObject({ - build: { - executor: '@nx/rollup:rollup', - outputs: ['{options.outputPath}'], - defaultConfiguration: 'production', - options: { - tsConfig: 'libs/mypkg/tsconfig.custom.json', - }, - }, - }); - }); - - it('should carry over known executor options from existing build target', async () => { - updateProjectConfiguration(tree, 'mypkg', { - root: 'libs/mypkg', - sourceRoot: 'libs/mypkg/src', - targets: { - build: { - executor: '@nx/js:tsc', - options: { - main: 'libs/mypkg/src/custom.ts', - outputPath: 'dist/custom', - tsConfig: 'libs/mypkg/src/tsconfig.custom.json', - additionalEntryPoints: ['libs/mypkg/src/extra.ts'], - generateExportsField: true, - }, - }, - }, - }); - - await configurationGenerator(tree, { - project: 'mypkg', - buildTarget: 'build', - skipValidation: true, + tsConfig: './tsconfig.custom.json', }); - const project = readProjectConfiguration(tree, 'mypkg'); - - expect(project.targets).toMatchObject({ - build: { - executor: '@nx/rollup:rollup', - outputs: ['{options.outputPath}'], - defaultConfiguration: 'production', - options: { - main: 'libs/mypkg/src/custom.ts', - outputPath: 'dist/custom', - tsConfig: 'libs/mypkg/src/tsconfig.custom.json', - additionalEntryPoints: ['libs/mypkg/src/extra.ts'], - generateExportsField: true, - }, - }, - }); + const rollupConfig = tree.read('libs/mypkg/rollup.config.js', 'utf-8'); + + expect(rollupConfig) + .toEqual(`const { withNx } = require('@nx/rollup/with-nx'); + +module.exports = withNx( + { + main: './src/index.ts', + outputPath: '../../dist/libs/mypkg', + tsConfig: './tsconfig.custom.json', + compiler: 'babel', + format: ['esm'], + assets: [{ input: '.', output: '.', glob: '*.md' }], + }, + { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + // e.g. + // output: { sourcemap: true }, + } +); +`); }); }); diff --git a/packages/rollup/src/generators/configuration/configuration.ts b/packages/rollup/src/generators/configuration/configuration.ts index aedfc2a521be75..e28a07b3be0e0e 100644 --- a/packages/rollup/src/generators/configuration/configuration.ts +++ b/packages/rollup/src/generators/configuration/configuration.ts @@ -1,10 +1,13 @@ import { formatFiles, + GeneratorCallback, joinPathFragments, + offsetFromRoot, + readNxJson, readProjectConfiguration, - Tree, - GeneratorCallback, runTasksInSerial, + stripIndents, + Tree, updateProjectConfiguration, writeJson, } from '@nx/devkit'; @@ -15,20 +18,35 @@ import { RollupExecutorOptions } from '../../executors/rollup/schema'; import { RollupProjectSchema } from './schema'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults'; import { ensureDependencies } from '../../utils/ensure-dependencies'; +import { hasPlugin } from '../../utils/has-plugin'; +import { RollupWithNxPluginOptions } from '../../plugins/with-nx/with-nx-options'; export async function configurationGenerator( tree: Tree, options: RollupProjectSchema ) { const tasks: GeneratorCallback[] = []; + const nxJson = readNxJson(tree); + const addPluginDefault = + process.env.NX_ADD_PLUGINS !== 'false' && + nxJson.useInferencePlugins !== false; + options.addPlugin ??= addPluginDefault; + tasks.push(await rollupInitGenerator(tree, { ...options, skipFormat: true })); + if (!options.skipPackageJson) { tasks.push(ensureDependencies(tree, options)); } - options.buildTarget ??= 'build'; - checkForTargetConflicts(tree, options); - addBuildTarget(tree, options); + if (hasPlugin(tree)) { + createRollupConfig(tree, options); + } else { + options.buildTarget ??= 'build'; + checkForTargetConflicts(tree, options); + addBuildTarget(tree, options); + } + + addPackageJson(tree, options); if (!options.skipFormat) { await formatFiles(tree); @@ -37,6 +55,39 @@ export async function configurationGenerator( return runTasksInSerial(...tasks); } +function createRollupConfig(tree: Tree, options: RollupProjectSchema) { + const project = readProjectConfiguration(tree, options.project); + const buildOptions: RollupWithNxPluginOptions = { + outputPath: joinPathFragments( + offsetFromRoot(project.root), + 'dist', + project.root === '.' ? project.name : project.root + ), + compiler: options.compiler ?? 'babel', + main: options.main ?? './src/index.ts', + tsConfig: options.tsConfig ?? './tsconfig.lib.json', + }; + + tree.write( + joinPathFragments(project.root, 'rollup.config.js'), + stripIndents` + const { withNx } = require('@nx/rollup/with-nx'); + + module.exports = withNx({ + main: '${buildOptions.main}', + outputPath: '${buildOptions.outputPath}', + tsConfig: '${buildOptions.tsConfig}', + compiler: '${buildOptions.compiler}', + format: ${JSON.stringify(options.format ?? ['esm'])}, + assets:[{ input: '.', output: '.', glob:'*.md'}], + }, { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + // e.g. + // output: { sourcemap: true }, + });` + ); +} + function checkForTargetConflicts(tree: Tree, options: RollupProjectSchema) { if (options.skipValidation) return; const project = readProjectConfiguration(tree, options.project); @@ -47,8 +98,7 @@ function checkForTargetConflicts(tree: Tree, options: RollupProjectSchema) { } } -function addBuildTarget(tree: Tree, options: RollupProjectSchema) { - addBuildTargetDefaults(tree, '@nx/rollup:rollup', options.buildTarget); +function addPackageJson(tree: Tree, options: RollupProjectSchema) { const project = readProjectConfiguration(tree, options.project); const packageJsonPath = joinPathFragments(project.root, 'package.json'); @@ -60,14 +110,18 @@ function addBuildTarget(tree: Tree, options: RollupProjectSchema) { version: '0.0.1', }); } +} +function addBuildTarget(tree: Tree, options: RollupProjectSchema) { + addBuildTargetDefaults(tree, '@nx/rollup:rollup', options.buildTarget); + const project = readProjectConfiguration(tree, options.project); const prevBuildOptions = project.targets?.[options.buildTarget]?.options; const buildOptions: RollupExecutorOptions = { main: options.main ?? prevBuildOptions?.main ?? - joinPathFragments(project.root, 'src/main.ts'), + joinPathFragments(project.root, 'src/index.ts'), outputPath: prevBuildOptions?.outputPath ?? joinPathFragments( @@ -107,17 +161,7 @@ function addBuildTarget(tree: Tree, options: RollupProjectSchema) { [options.buildTarget]: { executor: '@nx/rollup:rollup', outputs: ['{options.outputPath}'], - defaultConfiguration: 'production', options: buildOptions, - configurations: { - production: { - optimization: true, - sourceMap: false, - namedChunks: false, - extractLicenses: true, - vendorChunk: false, - }, - }, }, }, }); diff --git a/packages/rollup/src/generators/configuration/schema.d.ts b/packages/rollup/src/generators/configuration/schema.d.ts index a8c334224420b3..f7f1090422f027 100644 --- a/packages/rollup/src/generators/configuration/schema.d.ts +++ b/packages/rollup/src/generators/configuration/schema.d.ts @@ -11,4 +11,5 @@ export interface RollupProjectSchema { rollupConfig?: string; buildTarget?: string; format?: ('cjs' | 'esm')[]; + addPlugin?: boolean; } diff --git a/packages/rollup/src/generators/init/init.spec.ts b/packages/rollup/src/generators/init/init.spec.ts index c01555d6892f81..6a8df0a15744f3 100644 --- a/packages/rollup/src/generators/init/init.spec.ts +++ b/packages/rollup/src/generators/init/init.spec.ts @@ -2,7 +2,7 @@ import 'nx/src/internal-testing-utils/mock-project-graph'; import { Tree, readJson, ProjectGraph } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; -import { nxVersion } from '../../utils/versions'; +import { nxVersion, rollupVersion } from '../../utils/versions'; import { rollupInitGenerator } from './init'; @@ -32,7 +32,7 @@ describe('rollupInitGenerator', () => { expect(packageJson).toEqual({ name: expect.any(String), dependencies: {}, - devDependencies: { '@nx/rollup': nxVersion }, + devDependencies: { '@nx/rollup': nxVersion, rollup: rollupVersion }, }); }); }); diff --git a/packages/rollup/src/generators/init/init.ts b/packages/rollup/src/generators/init/init.ts index 59be1a26d7e3af..a1b54243051545 100644 --- a/packages/rollup/src/generators/init/init.ts +++ b/packages/rollup/src/generators/init/init.ts @@ -5,25 +5,30 @@ import { GeneratorCallback, Tree, } from '@nx/devkit'; -import { nxVersion } from '../../utils/versions'; +import { nxVersion, rollupVersion } from '../../utils/versions'; import { Schema } from './schema'; import { addPlugin } from '@nx/devkit/src/utils/add-plugin'; import { createNodes } from '../../plugins/plugin'; export async function rollupInitGenerator(tree: Tree, schema: Schema) { let task: GeneratorCallback = () => {}; + schema.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false'; if (!schema.skipPackageJson) { + const devDependencies = { '@nx/rollup': nxVersion }; + if (schema.addPlugin) { + // Ensure user can run Rollup CLI. + devDependencies['rollup'] = rollupVersion; + } task = addDependenciesToPackageJson( tree, {}, - { '@nx/rollup': nxVersion }, + devDependencies, undefined, schema.keepExistingVersions ); } - schema.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false'; if (schema.addPlugin) { await addPlugin( tree, diff --git a/packages/rollup/src/executors/rollup/lib/analyze-plugin.ts b/packages/rollup/src/plugins/analyze.ts similarity index 100% rename from packages/rollup/src/executors/rollup/lib/analyze-plugin.ts rename to packages/rollup/src/plugins/analyze.ts diff --git a/packages/rollup/src/plugins/delete-output.ts b/packages/rollup/src/plugins/delete-output.ts new file mode 100644 index 00000000000000..ad497dc27d84b7 --- /dev/null +++ b/packages/rollup/src/plugins/delete-output.ts @@ -0,0 +1,14 @@ +import type { Plugin } from 'rollup'; +import { deleteOutputDir } from '../utils/fs'; + +export interface DeleteOutputOptions { + dirs: string[]; +} + +export function deleteOutput(options: DeleteOutputOptions): Plugin { + return { + name: 'rollup-plugin-nx-delete-output', + buildStart: () => + options.dirs.forEach((dir) => deleteOutputDir(process.cwd(), dir)), + }; +} diff --git a/packages/rollup/src/plugins/package-json/generate-package-json.ts b/packages/rollup/src/plugins/package-json/generate-package-json.ts new file mode 100644 index 00000000000000..e17ec8e2982275 --- /dev/null +++ b/packages/rollup/src/plugins/package-json/generate-package-json.ts @@ -0,0 +1,25 @@ +import type { Plugin } from 'rollup'; +import type { PackageJson } from 'nx/src/utils/package-json'; +import { updatePackageJson } from './update-package-json'; + +export interface GeneratePackageJsonOptions { + outputPath: string; + main: string; + format: string[]; + generateExportsField?: boolean; + skipTypeField?: boolean; + outputFileName?: string; + additionalEntryPoints?: string[]; +} + +export function generatePackageJson( + options: GeneratePackageJsonOptions, + packageJson: PackageJson +): Plugin { + return { + name: 'rollup-plugin-nx-generate-package-json', + writeBundle: () => { + updatePackageJson(options, packageJson); + }, + }; +} diff --git a/packages/rollup/src/executors/rollup/lib/update-package-json.spec.ts b/packages/rollup/src/plugins/package-json/update-package-json.spec.ts similarity index 100% rename from packages/rollup/src/executors/rollup/lib/update-package-json.spec.ts rename to packages/rollup/src/plugins/package-json/update-package-json.spec.ts diff --git a/packages/rollup/src/executors/rollup/lib/update-package-json.ts b/packages/rollup/src/plugins/package-json/update-package-json.ts similarity index 79% rename from packages/rollup/src/executors/rollup/lib/update-package-json.ts rename to packages/rollup/src/plugins/package-json/update-package-json.ts index d0f3cb83b33f84..ce10c36a69c6b1 100644 --- a/packages/rollup/src/executors/rollup/lib/update-package-json.ts +++ b/packages/rollup/src/plugins/package-json/update-package-json.ts @@ -2,12 +2,18 @@ import { basename, join, parse } from 'path'; import { writeJsonFile } from 'nx/src/utils/fileutils'; import { writeFileSync } from 'fs'; import { PackageJson } from 'nx/src/utils/package-json'; -import { NormalizedRollupExecutorOptions } from './normalize'; -import { stripIndents } from '@nx/devkit'; +import { stripIndents, workspaceRoot } from '@nx/devkit'; -// TODO(jack): Use updatePackageJson from @nx/js instead. export function updatePackageJson( - options: NormalizedRollupExecutorOptions, + options: { + outputPath: string; + main: string; + format: string[]; + generateExportsField?: boolean; + skipTypeField?: boolean; + outputFileName?: string; + additionalEntryPoints?: string[]; + }, packageJson: PackageJson ) { const hasEsmFormat = options.format.includes('esm'); @@ -72,13 +78,14 @@ export function updatePackageJson( // default import in Node will not work. writeFileSync( join( + workspaceRoot, options.outputPath, filePath.replace(/\.cjs\.js$/, '.cjs.default.js') ), `exports._default = require('./${parse(filePath).base}').default;` ); writeFileSync( - join(options.outputPath, fauxEsmFilePath), + join(workspaceRoot, options.outputPath, fauxEsmFilePath), // Re-export from relative CJS file, and Node will synthetically export it as ESM. stripIndents` export * from './${relativeFile}'; @@ -95,29 +102,31 @@ export function updatePackageJson( } } - writeJsonFile(`${options.outputPath}/package.json`, packageJson); + writeJsonFile( + join(workspaceRoot, options.outputPath, 'package.json'), + packageJson + ); } interface Exports { - '.': string; - [name: string]: string; } -function getExports( - options: Pick< - NormalizedRollupExecutorOptions, - 'main' | 'projectRoot' | 'outputFileName' | 'additionalEntryPoints' - > & { - fileExt: string; +function getExports(options: { + main?: string; + fileExt: string; + outputFileName?: string; + additionalEntryPoints?: string[]; +}): Exports { + const exports: Exports = {}; + + // Users may provide custom input option and skip the main field. + if (options.main) { + const mainFile = options.outputFileName + ? options.outputFileName.replace(/\.[tj]s$/, '') + : basename(options.main).replace(/\.[tj]s$/, ''); + exports['.'] = './' + mainFile + options.fileExt; } -): Exports { - const mainFile = options.outputFileName - ? options.outputFileName.replace(/\.[tj]s$/, '') - : basename(options.main).replace(/\.[tj]s$/, ''); - const exports: Exports = { - '.': './' + mainFile + options.fileExt, - }; if (options.additionalEntryPoints) { for (const file of options.additionalEntryPoints) { diff --git a/packages/rollup/src/executors/rollup/lib/swc-plugin.ts b/packages/rollup/src/plugins/swc.ts similarity index 100% rename from packages/rollup/src/executors/rollup/lib/swc-plugin.ts rename to packages/rollup/src/plugins/swc.ts diff --git a/packages/rollup/src/plugins/with-nx/get-project-node.ts b/packages/rollup/src/plugins/with-nx/get-project-node.ts new file mode 100644 index 00000000000000..f47e7546d5cf8d --- /dev/null +++ b/packages/rollup/src/plugins/with-nx/get-project-node.ts @@ -0,0 +1,18 @@ +import { ProjectGraphProjectNode, readCachedProjectGraph } from '@nx/devkit'; + +export function getProjectNode(): ProjectGraphProjectNode { + // During graph construction, project is not necessary. Return a stub. + if (global.NX_GRAPH_CREATION) { + return { + type: 'lib', + name: '', + data: { + root: '', + }, + }; + } else { + const projectGraph = readCachedProjectGraph(); + const projectName = process.env.NX_TASK_TARGET_PROJECT; + return projectGraph.nodes[projectName]; + } +} diff --git a/packages/rollup/src/plugins/with-nx/normalize-options.spec.ts b/packages/rollup/src/plugins/with-nx/normalize-options.spec.ts new file mode 100644 index 00000000000000..d5535e732971ae --- /dev/null +++ b/packages/rollup/src/plugins/with-nx/normalize-options.spec.ts @@ -0,0 +1,85 @@ +import { normalizeOptions } from './normalize-options'; + +jest.mock('@nx/js', () => ({ + ...jest.requireActual('@nx/js'), + createEntryPoints: (x: string) => x, +})); + +jest.mock('@nx/devkit', () => ({ + ...jest.requireActual('@nx/devkit'), + workspaceRoot: '/tmp', +})); + +jest.mock('node:fs', () => ({ + statSync: () => ({ isDirectory: () => true }), +})); + +describe('normalizeOptions', () => { + it('should provide defaults', () => { + const result = normalizeOptions('pkg', 'pkg', { + main: './src/main.ts', + tsConfig: './tsconfig.json', + outputPath: '../dist/pkg', + }); + + expect(result).toMatchObject({ + allowJs: false, + assets: [], + babelUpwardRootMode: false, + compiler: 'babel', + deleteOutputPath: true, + extractCss: true, + generateExportsField: false, + javascriptEnabled: false, + skipTypeCheck: false, + skipTypeField: false, + }); + }); + + it('should normalize relative paths', () => { + const result = normalizeOptions('pkg', 'pkg', { + main: './src/main.ts', + additionalEntryPoints: ['./src/worker1.ts', './src/worker2.ts'], + tsConfig: './tsconfig.json', + outputPath: '../dist/pkg', + }); + + expect(result).toMatchObject({ + additionalEntryPoints: ['pkg/src/worker1.ts', 'pkg/src/worker2.ts'], + main: 'pkg/src/main.ts', + outputPath: 'dist/pkg', + tsConfig: 'pkg/tsconfig.json', + }); + }); + + it('should normalize relative paths', () => { + const result = normalizeOptions('pkg', 'pkg', { + main: './src/main.ts', + tsConfig: './tsconfig.json', + outputPath: '../dist/pkg', + assets: [ + './src/assets', + { input: './docs', output: '.', glob: '**/*.md' }, + ], + }); + + expect(result).toMatchObject({ + assets: [ + { + glob: '**/*', + input: '/tmp/pkg/src/assets', + output: 'src/assets', + }, + { + glob: '**/*.md', + input: '/tmp/docs', + output: '.', + }, + ], + compiler: 'babel', + main: 'pkg/src/main.ts', + outputPath: 'dist/pkg', + tsConfig: 'pkg/tsconfig.json', + }); + }); +}); diff --git a/packages/rollup/src/plugins/with-nx/normalize-options.ts b/packages/rollup/src/plugins/with-nx/normalize-options.ts new file mode 100644 index 00000000000000..f47734814791ff --- /dev/null +++ b/packages/rollup/src/plugins/with-nx/normalize-options.ts @@ -0,0 +1,107 @@ +import { basename, dirname, join, relative, resolve } from 'node:path'; +import { statSync } from 'node:fs'; +import { normalizePath, workspaceRoot } from '@nx/devkit'; +import type { + AssetGlobPattern, + NormalizedRollupWithNxPluginOptions, + RollupWithNxPluginOptions, +} from './with-nx-options'; +import { createEntryPoints } from '@nx/js'; + +export function normalizeOptions( + projectRoot: string, + sourceRoot: string, + options: RollupWithNxPluginOptions +): NormalizedRollupWithNxPluginOptions { + if (global.NX_GRAPH_CREATION) + return options as NormalizedRollupWithNxPluginOptions; + normalizeRelativePaths(projectRoot, options); + return { + ...options, + additionalEntryPoints: createEntryPoints( + options.additionalEntryPoints, + workspaceRoot + ), + allowJs: options.allowJs ?? false, + assets: options.assets + ? normalizeAssets(options.assets, workspaceRoot, sourceRoot) + : [], + babelUpwardRootMode: options.babelUpwardRootMode ?? false, + compiler: options.compiler ?? 'babel', + deleteOutputPath: options.deleteOutputPath ?? true, + extractCss: options.extractCss ?? true, + format: options.format ? Array.from(new Set(options.format)) : undefined, + generateExportsField: options.generateExportsField ?? false, + javascriptEnabled: options.javascriptEnabled ?? false, + skipTypeCheck: options.skipTypeCheck ?? false, + skipTypeField: options.skipTypeField ?? false, + }; +} + +function normalizeAssets( + assets: any[], + root: string, + sourceRoot: string +): AssetGlobPattern[] { + return assets.map((asset) => { + if (typeof asset === 'string') { + const assetPath = normalizePath(asset); + const resolvedAssetPath = resolve(root, assetPath); + const resolvedSourceRoot = resolve(root, sourceRoot); + + if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) { + throw new Error( + `The ${resolvedAssetPath} asset path must start with the project source root: ${sourceRoot}` + ); + } + + const isDirectory = statSync(resolvedAssetPath).isDirectory(); + const input = isDirectory + ? resolvedAssetPath + : dirname(resolvedAssetPath); + const output = relative(resolvedSourceRoot, resolve(root, input)); + const glob = isDirectory ? '**/*' : basename(resolvedAssetPath); + return { + input, + output, + glob, + }; + } else { + if (asset.output.startsWith('..')) { + throw new Error( + 'An asset cannot be written to a location outside of the output path.' + ); + } + + const assetPath = normalizePath(asset.input); + const resolvedAssetPath = resolve(root, assetPath); + return { + ...asset, + input: resolvedAssetPath, + // Now we remove starting slash to make Webpack place it from the output root. + output: asset.output.replace(/^\//, ''), + }; + } + }); +} + +function normalizeRelativePaths( + projectRoot: string, + options: RollupWithNxPluginOptions +): void { + for (const [fieldName, fieldValue] of Object.entries(options)) { + if (isRelativePath(fieldValue)) { + options[fieldName] = join(projectRoot, fieldValue); + } else if (Array.isArray(fieldValue)) { + for (let i = 0; i < fieldValue.length; i++) { + if (isRelativePath(fieldValue[i])) { + fieldValue[i] = join(projectRoot, fieldValue[i]); + } + } + } + } +} + +function isRelativePath(val: unknown): boolean { + return typeof val === 'string' && val.startsWith('.'); +} diff --git a/packages/rollup/src/plugins/with-nx/with-nx-options.ts b/packages/rollup/src/plugins/with-nx/with-nx-options.ts new file mode 100644 index 00000000000000..520b2bdc9d19bf --- /dev/null +++ b/packages/rollup/src/plugins/with-nx/with-nx-options.ts @@ -0,0 +1,37 @@ +// TODO: Add TSDoc +export interface RollupWithNxPluginOptions { + additionalEntryPoints?: string[]; + allowJs?: boolean; + assets?: any[]; + babelUpwardRootMode?: boolean; + compiler?: 'babel' | 'tsc' | 'swc'; + deleteOutputPath?: boolean; + external?: string[] | 'all' | 'none'; + extractCss?: boolean | string; + format?: ('cjs' | 'esm')[]; + generateExportsField?: boolean; + javascriptEnabled?: boolean; + main: string; + /** @deprecated Do not set this. The package.json file in project root is detected automatically. */ + project?: string; + outputFileName?: string; + outputPath: string; + rollupConfig?: string | string[]; + skipTypeCheck?: boolean; + skipTypeField?: boolean; + tsConfig: string; +} + +export interface AssetGlobPattern { + glob: string; + ignore?: string[]; + input: string; + output: string; +} + +export interface NormalizedRollupWithNxPluginOptions + extends RollupWithNxPluginOptions { + assets: AssetGlobPattern[]; + compiler: 'babel' | 'tsc' | 'swc'; + format: ('cjs' | 'esm')[]; +} diff --git a/packages/rollup/src/plugins/with-nx/with-nx.ts b/packages/rollup/src/plugins/with-nx/with-nx.ts new file mode 100644 index 00000000000000..63c5f7baff5257 --- /dev/null +++ b/packages/rollup/src/plugins/with-nx/with-nx.ts @@ -0,0 +1,333 @@ +import { existsSync } from 'node:fs'; +import { dirname, join, parse } from 'node:path'; +import * as ts from 'typescript'; +import * as rollup from 'rollup'; +import { getBabelInputPlugin } from '@rollup/plugin-babel'; +import * as autoprefixer from 'autoprefixer'; +import { + joinPathFragments, + logger, + type ProjectGraph, + readCachedProjectGraph, + readJsonFile, + workspaceRoot, +} from '@nx/devkit'; +import { + calculateProjectBuildableDependencies, + computeCompilerOptionsPaths, + DependentBuildableProjectNode, +} from '@nx/js/src/utils/buildable-libs-utils'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import { typeDefinitions } from '@nx/js/src/plugins/rollup/type-definitions'; + +import { analyze } from '../analyze'; +import { swc } from '../swc'; +import { generatePackageJson } from '../package-json/generate-package-json'; +import { getProjectNode } from './get-project-node'; +import { deleteOutput } from '../delete-output'; +import { AssetGlobPattern, RollupWithNxPluginOptions } from './with-nx-options'; +import { normalizeOptions } from './normalize-options'; +import { PackageJson } from 'nx/src/utils/package-json'; + +// These use require because the ES import isn't correct. +const commonjs = require('@rollup/plugin-commonjs'); +const image = require('@rollup/plugin-image'); + +const json = require('@rollup/plugin-json'); +const copy = require('rollup-plugin-copy'); +const postcss = require('rollup-plugin-postcss'); + +const fileExtensions = ['.js', '.jsx', '.ts', '.tsx']; + +export function withNx( + rawOptions: RollupWithNxPluginOptions, + rollupConfig: rollup.RollupOptions = {}, + // Passed by @nx/rollup:rollup executor to previous behavior of remapping tsconfig paths based on buildable dependencies remains intact. + dependencies?: DependentBuildableProjectNode[] +): rollup.RollupOptions { + const finalConfig: rollup.RollupOptions = { ...rollupConfig }; + + // Since this is invoked by the executor, the graph has already been created and cached. + const projectNode = getProjectNode(); + const projectRoot = join(workspaceRoot, projectNode.data.root); + + // Cannot read in graph during construction, but we only need it during build time. + const projectGraph: ProjectGraph | null = global.NX_GRAPH_CREATION + ? null + : readCachedProjectGraph(); + + // If dependencies are not passed from executor, calculate them from project graph. + if (!dependencies && !global.NX_GRAPH_CREATION) { + const result = calculateProjectBuildableDependencies( + undefined, + projectGraph, + workspaceRoot, + projectNode.name, + process.env.NX_TASK_TARGET_TARGET, + process.env.NX_TASK_TARGET_CONFIGURATION, + true + ); + dependencies = result.dependencies; + } + + const options = normalizeOptions( + projectNode.data.root, + projectNode.data.sourceRoot, + rawOptions + ); + + const useBabel = options.compiler === 'babel'; + const useSwc = options.compiler === 'swc'; + + const tsConfigPath = joinPathFragments(workspaceRoot, options.tsConfig); + const tsConfigFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile); + const tsConfig = ts.parseJsonConfigFileContent( + tsConfigFile.config, + ts.sys, + dirname(tsConfigPath) + ); + + if (!options.format || !options.format.length) { + options.format = readCompatibleFormats(tsConfig); + } + + if ( + rollupConfig.input && + (options.main || options.additionalEntryPoints.length > 0) + ) { + logger.warn( + `Setting "input" in rollup config overrides "main" and "additionalEntryPoints" options.` + ); + } + + // If user provides their own input, override our defaults. + finalConfig.input = rollupConfig.input || createInput(options); + + if (options.format) { + if (Array.isArray(rollupConfig.output)) { + throw new Error( + `Cannot use Rollup array output option and withNx format option together. Use an object instead.` + ); + } + if (rollupConfig.output?.format || rollupConfig.output?.dir) { + logger.warn( + `"output.dir" and "output.format" are overridden by "withNx".` + ); + } + + finalConfig.output = options.format.map((format) => ({ + // These options could be overridden by the user, especially if they use a single format. + entryFileNames: `[name].${format}.js`, + chunkFileNames: `[name].${format}.js`, + ...rollupConfig.output, + // Format and dir cannot be overridden by user or else the behavior will break. + format, + dir: global.NX_GRAPH_CREATION + ? // Options are not normalized with project root during graph creation due to the lack of project and project root. + // Cannot be joined with workspace root now, but will be handled by @nx/rollup/plugin. + options.outputPath + : join(workspaceRoot, options.outputPath), + })); + } + + let packageJson: PackageJson; + if (!global.NX_GRAPH_CREATION) { + const packageJsonPath = options.project + ? join(workspaceRoot, options.project) + : join(projectRoot, 'package.json'); + if (!existsSync(packageJsonPath)) { + throw new Error(`Cannot find ${packageJsonPath}.`); + } + + packageJson = readJsonFile(packageJsonPath); + + if (packageJson.type === 'module') { + if (options.format.includes('cjs')) { + logger.warn( + `Package type is set to "module" but "cjs" format is included. Going to use "esm" format instead. You can change the package type to "commonjs" or remove type in the package.json file.` + ); + } + options.format = ['esm']; + } else if (packageJson.type === 'commonjs') { + if (options.format.includes('esm')) { + logger.warn( + `Package type is set to "commonjs" but "esm" format is included. Going to use "cjs" format instead. You can change the package type to "module" or remove type in the package.json file.` + ); + } + options.format = ['cjs']; + } + } + + // User may wish to customize how external behaves by overriding our default. + if (!rollupConfig.external && !global.NX_GRAPH_CREATION) { + const npmDeps = (projectGraph.dependencies[projectNode.name] ?? []) + .filter((d) => d.target.startsWith('npm:')) + .map((d) => d.target.slice(4)); + let externalPackages = [ + ...Object.keys(packageJson.dependencies || {}), + ...Object.keys(packageJson.peerDependencies || {}), + ]; // If external is set to none, include all dependencies and peerDependencies in externalPackages + if (options.external === 'all') { + externalPackages = externalPackages.concat(npmDeps); + } else if (Array.isArray(options.external) && options.external.length > 0) { + externalPackages = externalPackages.concat(options.external); + } + externalPackages = [...new Set(externalPackages)]; + finalConfig.external = (id: string) => { + return externalPackages.some( + (name) => id === name || id.startsWith(`${name}/`) + ); + }; + } + + if (!global.NX_GRAPH_CREATION) { + finalConfig.plugins = [ + copy({ + targets: convertCopyAssetsToRollupOptions( + options.outputPath, + options.assets + ), + }), + image(), + json(), + // Needed to generate type definitions, even if we're using babel or swc. + require('rollup-plugin-typescript2')({ + check: !options.skipTypeCheck, + tsconfig: options.tsConfig, + tsconfigOverride: { + compilerOptions: createTsCompilerOptions( + projectRoot, + tsConfig, + options, + dependencies + ), + }, + }), + typeDefinitions({ + projectRoot, + }), + postcss({ + inject: true, + extract: options.extractCss, + autoModules: true, + plugins: [autoprefixer], + use: { + less: { + javascriptEnabled: options.javascriptEnabled, + }, + }, + }), + nodeResolve({ + preferBuiltins: true, + extensions: fileExtensions, + }), + useSwc && swc(), + useBabel && + getBabelInputPlugin({ + // Lets `@nx/js/babel` preset know that we are packaging. + caller: { + // @ts-ignore + // Ignoring type checks for caller since we have custom attributes + isNxPackage: true, + // Always target esnext and let rollup handle cjs + supportsStaticESM: true, + isModern: true, + }, + cwd: join( + workspaceRoot, + projectNode.data.sourceRoot ?? projectNode.data.root + ), + rootMode: options.babelUpwardRootMode ? 'upward' : undefined, + babelrc: true, + extensions: fileExtensions, + babelHelpers: 'bundled', + skipPreflightCheck: true, // pre-flight check may yield false positives and also slows down the build + exclude: /node_modules/, + }), + commonjs(), + analyze(), + generatePackageJson(options, packageJson), + ]; + if (Array.isArray(rollupConfig.plugins)) { + finalConfig.plugins.push(...rollupConfig.plugins); + } + if (options.deleteOutputPath) { + finalConfig.plugins.push( + deleteOutput({ + dirs: Array.isArray(finalConfig.output) + ? finalConfig.output.map((o) => o.dir) + : [finalConfig.output.dir], + }) + ); + } + } + + return finalConfig; +} + +function createInput( + options: RollupWithNxPluginOptions +): Record { + const mainEntryFileName = options.outputFileName || options.main; + const input: Record = {}; + input[parse(mainEntryFileName).name] = join(workspaceRoot, options.main); + options.additionalEntryPoints?.forEach((entry) => { + input[parse(entry).name] = join(workspaceRoot, entry); + }); + return input; +} + +function createTsCompilerOptions( + projectRoot: string, + config: ts.ParsedCommandLine, + options: RollupWithNxPluginOptions, + dependencies?: DependentBuildableProjectNode[] +) { + const compilerOptionPaths = computeCompilerOptionsPaths( + config, + dependencies ?? [] + ); + const compilerOptions = { + rootDir: projectRoot, + allowJs: options.allowJs, + declaration: true, + paths: compilerOptionPaths, + }; + if (config.options.module === ts.ModuleKind.CommonJS) { + compilerOptions['module'] = 'ESNext'; + } + if (options.compiler === 'swc') { + compilerOptions['emitDeclarationOnly'] = true; + } + return compilerOptions; +} + +interface RollupCopyAssetOption { + src: string; + dest: string; +} + +function convertCopyAssetsToRollupOptions( + outputPath: string, + assets: AssetGlobPattern[] +): RollupCopyAssetOption[] { + return assets + ? assets.map((a) => ({ + src: join(a.input, a.glob).replace(/\\/g, '/'), + dest: join(workspaceRoot, outputPath, a.output).replace(/\\/g, '/'), + })) + : undefined; +} + +function readCompatibleFormats( + config: ts.ParsedCommandLine +): ('cjs' | 'esm')[] { + switch (config.options.module) { + case ts.ModuleKind.CommonJS: + case ts.ModuleKind.UMD: + case ts.ModuleKind.AMD: + return ['cjs']; + default: + return ['esm']; + } +} diff --git a/packages/rollup/src/utils/has-plugin.ts b/packages/rollup/src/utils/has-plugin.ts new file mode 100644 index 00000000000000..1c072e44cb3a18 --- /dev/null +++ b/packages/rollup/src/utils/has-plugin.ts @@ -0,0 +1,10 @@ +import { readNxJson, Tree } from '@nx/devkit'; + +export function hasPlugin(tree: Tree) { + const nxJson = readNxJson(tree); + return !!nxJson.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/rollup/plugin' + : p.plugin === '@nx/rollup/plugin' + ); +} diff --git a/packages/rollup/src/utils/versions.ts b/packages/rollup/src/utils/versions.ts index b15e260a3ddaf1..a0ca678faa466b 100644 --- a/packages/rollup/src/utils/versions.ts +++ b/packages/rollup/src/utils/versions.ts @@ -1,5 +1,5 @@ export const nxVersion = require('../../package.json').version; +export const coreJsVersion = '^3.36.1'; +export const rollupVersion = '^4.14.0'; export const swcLoaderVersion = '0.1.15'; export const tsLibVersion = '^2.3.0'; - -export const coreJsVersion = '^3.36.1'; diff --git a/packages/rollup/with-nx.ts b/packages/rollup/with-nx.ts new file mode 100644 index 00000000000000..d60a41d2e7e3b4 --- /dev/null +++ b/packages/rollup/with-nx.ts @@ -0,0 +1,2 @@ +export { withNx } from './src/plugins/with-nx/with-nx'; +export type { RollupWithNxPluginOptions } from './src/plugins/with-nx/with-nx-options';