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..b308a5304b47ae 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,7 @@ export async function addLint( setParserOptionsProject: options.setParserOptionsProject, rootProject: options.rootProject, addPlugin: options.addPlugin, + 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/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..4726fe88c094e2 100644 --- a/packages/rollup/src/executors/rollup/lib/normalize.ts +++ b/packages/rollup/src/executors/rollup/lib/normalize.ts @@ -1,52 +1,31 @@ -import { basename, dirname, join, relative, resolve } from 'path'; -import { statSync } from 'fs'; -import { ExecutorContext, normalizePath } from '@nx/devkit'; +import { dirname, join, 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, skipTypeCheck: options.skipTypeCheck || false, - additionalEntryPoints: createEntryPoints( - options.additionalEntryPoints, - context.root - ), }; } @@ -60,50 +39,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..afa9487d6e3907 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,60 @@ 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 { join } = require('node:path'); +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 {, + // external: ['@acme/foo'], + } +); +`); }); 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 { join } = require('node:path'); +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 {, + // external: ['@acme/foo'], + } +); +`); }); }); diff --git a/packages/rollup/src/generators/configuration/configuration.ts b/packages/rollup/src/generators/configuration/configuration.ts index aedfc2a521be75..acbb7eec4cc316 100644 --- a/packages/rollup/src/generators/configuration/configuration.ts +++ b/packages/rollup/src/generators/configuration/configuration.ts @@ -1,10 +1,12 @@ import { formatFiles, + GeneratorCallback, joinPathFragments, + offsetFromRoot, + readNxJson, readProjectConfiguration, - Tree, - GeneratorCallback, runTasksInSerial, + Tree, updateProjectConfiguration, writeJson, } from '@nx/devkit'; @@ -15,20 +17,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 +54,41 @@ 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'), + ` +const { join } = require('node:path'); +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 {, + // external: ['@acme/foo'], +});` + ); +} + function checkForTargetConflicts(tree: Tree, options: RollupProjectSchema) { if (options.skipValidation) return; const project = readProjectConfiguration(tree, options.project); @@ -47,8 +99,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 +111,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 +162,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/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 86% 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..19a85d517128c2 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,7 +102,10 @@ export function updatePackageJson( } } - writeJsonFile(`${options.outputPath}/package.json`, packageJson); + writeJsonFile( + join(workspaceRoot, options.outputPath, 'package.json'), + packageJson + ); } interface Exports { @@ -104,14 +114,12 @@ interface Exports { [name: string]: string; } -function getExports( - options: Pick< - NormalizedRollupExecutorOptions, - 'main' | 'projectRoot' | 'outputFileName' | 'additionalEntryPoints' - > & { - fileExt: string; - } -): Exports { +function getExports(options: { + main: string; + fileExt: string; + outputFileName?: string; + additionalEntryPoints?: string[]; +}): Exports { const mainFile = options.outputFileName ? options.outputFileName.replace(/\.[tj]s$/, '') : basename(options.main).replace(/\.[tj]s$/, ''); 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..09665d4cab9acf --- /dev/null +++ b/packages/rollup/src/plugins/with-nx/with-nx-options.ts @@ -0,0 +1,38 @@ +// 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 picked up automatically. */ + project?: string; + outputFileName?: string; + outputPath: string; + rollupConfig?: string | string[]; + skipTypeCheck?: boolean; + skipTypeField?: boolean; + tsConfig: string; + watch?: boolean; +} + +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..a73f4d4733f31d --- /dev/null +++ b/packages/rollup/src/plugins/with-nx/with-nx.ts @@ -0,0 +1,290 @@ +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, + readCachedProjectGraph, + readJsonFile, + workspaceRoot, +} from '@nx/devkit'; +import { + 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'; + +// 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); + + 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); + } + + // TODO: This should respect `project` option from executor + const packageJsonPath = join(projectRoot, 'package.json'); + if (!existsSync(packageJsonPath)) { + throw new Error(`Cannot find ${packageJsonPath}.`); + } + const 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']; + } + + const mainEntryFileName = options.outputFileName || options.main; + const nxInput: Record = {}; + nxInput[parse(mainEntryFileName).name] = join(workspaceRoot, options.main); + options.additionalEntryPoints?.forEach((entry) => { + nxInput[parse(entry).name] = join(workspaceRoot, entry); + }); + if ( + Array.isArray(rollupConfig.input) || + typeof rollupConfig.input === 'string' + ) { + throw new Error( + `Cannot use input as string or string[]. Use an object instead.` + ); + } + finalConfig.input = { + ...rollupConfig.input, + ...nxInput, + }; + + 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.` + ); + } + 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: join(workspaceRoot, options.outputPath), + })); + } + + // User may wish to customize how external behaves by overriding our default. + if (!rollupConfig.external && !global.NX_GRAPH_CREATION) { + const projectGraph = readCachedProjectGraph(); + 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({ + main: options.main, + 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 (options.deleteOutputPath) { + finalConfig.plugins.push( + deleteOutput({ + dirs: Array.isArray(finalConfig.output) + ? finalConfig.output.map((o) => o.dir) + : [finalConfig.output.dir], + }) + ); + } + } + + return finalConfig; +} + +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/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';