From f8ffc05e8d7a11a922f695fdc8b95b19dc7dff66 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Fri, 21 Jun 2024 16:32:04 +0100 Subject: [PATCH] fix(bundling): convert to inferred should handle config file (#26619) ## Current Behavior ## Expected Behavior ## Related Issue(s) Fixes # --- .../convert-to-inferred.spec.ts | 46 +--- .../convert-to-inferred.ts | 185 ++++++++------ .../convert-to-inferred.spec.ts | 53 +--- .../convert-to-inferred.ts | 145 +++++++---- .../convert-to-inferred.ts | 19 +- ...ollup-config-from-executor-options.spec.ts | 241 ++++++++++++++++++ ...act-rollup-config-from-executor-options.ts | 103 ++++++-- ...build-post-target-transformer.spec.ts.snap | 128 ++++++++++ .../lib/build-post-target-transformer.spec.ts | 70 ++++- .../lib/build-post-target-transformer.ts | 70 +++-- .../lib/preview-post-target-transformer.ts | 4 - .../lib/serve-post-target-transformer.ts | 4 - .../lib/test-post-target-transformer.ts | 4 - 13 files changed, 820 insertions(+), 252 deletions(-) create mode 100644 packages/rollup/src/generators/convert-to-inferred/lib/extract-rollup-config-from-executor-options.spec.ts diff --git a/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.spec.ts b/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.spec.ts index 3597afaaed..7552b98c30 100644 --- a/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.spec.ts +++ b/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.spec.ts @@ -223,10 +223,6 @@ describe('Cypress - Convert Executors To Plugin', () => { await convertToInferred(tree, { project: 'myapp-e2e', skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - expect(targetKeys).not.toContain('test'); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -361,10 +357,6 @@ describe('Cypress - Convert Executors To Plugin', () => { await convertToInferred(tree, { skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - expect(targetKeys).not.toContain('e2e'); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -412,10 +404,6 @@ describe('Cypress - Convert Executors To Plugin', () => { await convertToInferred(tree, { skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - ['test'].forEach((key) => expect(targetKeys).not.toContain(key)); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -448,10 +436,6 @@ describe('Cypress - Convert Executors To Plugin', () => { await convertToInferred(tree, { skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - ['test'].forEach((key) => expect(targetKeys).not.toContain(key)); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -508,10 +492,6 @@ describe('Cypress - Convert Executors To Plugin', () => { await convertToInferred(tree, { skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - ['test'].forEach((key) => expect(targetKeys).not.toContain(key)); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -557,12 +537,13 @@ describe('Cypress - Convert Executors To Plugin', () => { // project.json modifications const updatedProject = readProjectConfiguration(tree, project.name); expect(updatedProject.targets.e2e).toMatchInlineSnapshot(` - { - "options": { - "runner-ui": true, - }, - } - `); + { + "options": { + "config-file": "./cypress.config.ts", + "runner-ui": true, + }, + } + `); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -603,12 +584,13 @@ describe('Cypress - Convert Executors To Plugin', () => { // project.json modifications const updatedProject = readProjectConfiguration(tree, project.name); expect(updatedProject.targets.e2e).toMatchInlineSnapshot(` - { - "options": { - "no-exit": true, - }, - } - `); + { + "options": { + "config-file": "./cypress.config.ts", + "no-exit": true, + }, + } + `); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; diff --git a/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.ts b/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.ts index 736a01aafd..9576840336 100644 --- a/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.ts +++ b/packages/cypress/src/generators/convert-to-inferred/convert-to-inferred.ts @@ -10,6 +10,10 @@ import { targetOptionsToCliMap } from './lib/target-options-map'; import { upsertBaseUrl } from './lib/upsert-baseUrl'; import { addDevServerTargetToConfig } from './lib/add-dev-server-target-to-config'; import { addExcludeSpecPattern } from './lib/add-exclude-spec-pattern'; +import { + processTargetOutputs, + toProjectRelativePath, +} from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils'; interface Schema { project?: string; @@ -61,83 +65,35 @@ export async function convertToInferred(tree: Tree, options: Schema) { function postTargetTransformer( target: TargetConfiguration, - tree: Tree + tree: Tree, + projectDetails: { projectName: string; root: string }, + inferredTargetConfiguration: TargetConfiguration ): TargetConfiguration { if (target.options) { - const configFilePath = target.options.cypressConfig; - - delete target.options.cypressConfig; - delete target.options.copyFiles; - delete target.options.skipServe; - - for (const key in targetOptionsToCliMap) { - if (target.options[key]) { - target.options[targetOptionsToCliMap[key]] = target.options[key]; - delete target.options[key]; - } - } - - if ('exit' in target.options && !target.options.exit) { - delete target.options.exit; - target.options['no-exit'] = true; - } - - if (target.options.testingType) { - delete target.options.testingType; - } - - if (target.options.watch) { - target.options.headed = true; - target.options['no-exit'] = true; - delete target.options.watch; - } - - if (target.options.baseUrl) { - upsertBaseUrl(tree, configFilePath, target.options.baseUrl); - delete target.options.baseUrl; - } - - if (target.options.devServerTarget) { - const webServerCommands: Record = { - default: `npx nx run ${target.options.devServerTarget}`, - }; - delete target.options.devServerTarget; - - if (target.configurations) { - for (const configuration in target.configurations) { - if (target.configurations[configuration]?.devServerTarget) { - webServerCommands[ - configuration - ] = `npx nx run ${target.configurations[configuration].devServerTarget}`; - delete target.configurations[configuration].devServerTarget; - } - } - } - - addDevServerTargetToConfig( - tree, - configFilePath, - webServerCommands, - webServerCommands?.['ci'] - ); - } - - if (target.options.ignoreTestFiles) { - addExcludeSpecPattern( - tree, - configFilePath, - target.options.ignoreTestFiles - ); - delete target.options.ignoreTestFiles; - } + handlePropertiesInOptions( + tree, + target.options, + projectDetails.root, + target + ); if (Object.keys(target.options).length === 0) { delete target.options; } - if ( - target.configurations && - Object.keys(target.configurations).length !== 0 - ) { + } + + if (target.configurations) { + for (const configurationName in target.configurations) { + const configuration = target.configurations[configurationName]; + handlePropertiesInOptions( + tree, + configuration, + projectDetails.root, + target + ); + } + + if (Object.keys(target.configurations).length !== 0) { for (const configuration in target.configurations) { if (Object.keys(target.configurations[configuration]).length === 0) { delete target.configurations[configuration]; @@ -149,7 +105,94 @@ function postTargetTransformer( } } + if (target.outputs) { + processTargetOutputs(target, [], inferredTargetConfiguration, { + projectName: projectDetails.projectName, + projectRoot: projectDetails.root, + }); + } + return target; } +function handlePropertiesInOptions( + tree: Tree, + options: Record, + projectRoot: string, + target: TargetConfiguration +) { + let configFilePath: string; + if ('cypressConfig' in options) { + configFilePath = options.cypressConfig; + options['config-file'] = toProjectRelativePath(configFilePath, projectRoot); + delete options.cypressConfig; + } + + if ('copyFiles' in options) { + delete options.copyFiles; + } + + if ('skipServe' in options) { + delete options.skipServe; + } + + for (const key in targetOptionsToCliMap) { + if (options[key]) { + const prevValue = options[key]; + delete options[key]; + options[targetOptionsToCliMap[key]] = prevValue; + } + } + + if ('exit' in options && !options.exit) { + delete options.exit; + options['no-exit'] = true; + } + + if ('testingType' in options) { + delete options.testingType; + } + + if ('watch' in options) { + options.headed = true; + options['no-exit'] = true; + delete options.watch; + } + + if (options.baseUrl && configFilePath) { + upsertBaseUrl(tree, configFilePath, options.baseUrl); + delete options.baseUrl; + } + + if (options.devServerTarget && configFilePath) { + const webServerCommands: Record = { + default: `npx nx run ${options.devServerTarget}`, + }; + delete options.devServerTarget; + + if (target.configurations && configFilePath) { + for (const configuration in target.configurations) { + if (target.configurations[configuration]?.devServerTarget) { + webServerCommands[ + configuration + ] = `npx nx run ${target.configurations[configuration].devServerTarget}`; + delete target.configurations[configuration].devServerTarget; + } + } + } + + addDevServerTargetToConfig( + tree, + configFilePath, + webServerCommands, + webServerCommands?.['ci'] + ); + } + + if ('ignoreTestFiles' in options) { + addExcludeSpecPattern(tree, configFilePath, options.ignoreTestFiles); + delete options.ignoreTestFiles; + } +} + export default convertToInferred; diff --git a/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.spec.ts b/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.spec.ts index 1de9a03d5d..f1bb20f0de 100644 --- a/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.spec.ts +++ b/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.spec.ts @@ -354,10 +354,6 @@ describe('Eslint - Convert Executors To Plugin', () => { await convertToInferred(tree, { project: 'myapp', skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - expect(targetKeys).not.toContain('lint'); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -426,17 +422,10 @@ describe('Eslint - Convert Executors To Plugin', () => { await convertToInferred(tree, { project: 'myapp', skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - expect(targetKeys).not.toContain('lint'); const projectJsonForProject = readJson( tree, `${project.root}/project.json` ); - expect(projectJsonForProject['// targets']).toEqual( - 'to see all targets run: nx show project myapp --web' - ); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; const addedTestEslintPlugin = nxJsonPlugins.find((plugin) => { @@ -580,10 +569,6 @@ describe('Eslint - Convert Executors To Plugin', () => { await convertToInferred(tree, { skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - expect(targetKeys).not.toContain('lint'); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -610,10 +595,6 @@ describe('Eslint - Convert Executors To Plugin', () => { await convertToInferred(tree, { skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - ['eslint'].forEach((key) => expect(targetKeys).not.toContain(key)); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -641,10 +622,6 @@ describe('Eslint - Convert Executors To Plugin', () => { await convertToInferred(tree, { skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - expect(targetKeys).not.toContain('eslint'); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -695,10 +672,6 @@ describe('Eslint - Convert Executors To Plugin', () => { await convertToInferred(tree, { skipFormat: true }); // ASSERT - // project.json modifications - const updatedProject = readProjectConfiguration(tree, project.name); - const targetKeys = Object.keys(updatedProject.targets); - expect(targetKeys).not.toContain('eslint'); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -744,12 +717,13 @@ describe('Eslint - Convert Executors To Plugin', () => { // project.json modifications const updatedProject = readProjectConfiguration(tree, project.name); expect(updatedProject.targets.lint).toMatchInlineSnapshot(` - { - "options": { - "cache-location": "cache-dir", - }, - } - `); + { + "options": { + "cache-location": "cache-dir", + "config": ".eslintrc.json", + }, + } + `); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; @@ -785,12 +759,13 @@ describe('Eslint - Convert Executors To Plugin', () => { // project.json modifications const updatedProject = readProjectConfiguration(tree, project.name); expect(updatedProject.targets.lint).toMatchInlineSnapshot(` - { - "options": { - "max-warnings": 10, - }, - } - `); + { + "options": { + "config": ".eslintrc.json", + "max-warnings": 10, + }, + } + `); // nx.json modifications const nxJsonPlugins = readNxJson(tree).plugins; diff --git a/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.ts b/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.ts index faa9f264e4..db4abfb969 100644 --- a/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.ts +++ b/packages/eslint/src/generators/convert-to-inferred/convert-to-inferred.ts @@ -9,6 +9,10 @@ import { createNodesV2, EslintPluginOptions } from '../../plugins/plugin'; import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { targetOptionsToCliMap } from './lib/target-options-map'; import { interpolate } from 'nx/src/tasks-runner/utils'; +import { + processTargetOutputs, + toProjectRelativePath, +} from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils'; interface Schema { project?: string; @@ -56,7 +60,8 @@ export async function convertToInferred(tree: Tree, options: Schema) { function postTargetTransformer( target: TargetConfiguration, tree: Tree, - projectDetails: { projectName: string; root: string } + projectDetails: { projectName: string; root: string }, + inferredTargetConfiguration: TargetConfiguration ): TargetConfiguration { if (target.inputs) { const inputs = target.inputs.filter( @@ -75,63 +80,101 @@ function postTargetTransformer( } if (target.options) { - if ('eslintConfig' in target.options) { - delete target.options.eslintConfig; + handlePropertiesInOptions(target.options, projectDetails, target); + } + + if (target.configurations) { + for (const configurationName in target.configurations) { + const configuration = target.configurations[configurationName]; + handlePropertiesInOptions(configuration, projectDetails, target); } - if ('force' in target.options) { - delete target.options.force; - } - - if ('silent' in target.options) { - delete target.options.silent; - } - - if ('hasTypeAwareRules' in target.options) { - delete target.options.hasTypeAwareRules; - } - - if ('errorOnUnmatchedPattern' in target.options) { - if (!target.options.errorOnUnmatchedPattern) { - target.options['no-error-on-unmatched-pattern'] = true; - } - delete target.options.errorOnUnmatchedPattern; - } - - if ('outputFile' in target.options) { - target.outputs ??= []; - target.outputs.push(target.options.outputFile); - } - - for (const key in targetOptionsToCliMap) { - if (target.options[key]) { - target.options[targetOptionsToCliMap[key]] = target.options[key]; - delete target.options[key]; - } - } - - if ('lintFilePatterns' in target.options) { - const normalizedLintFilePatterns = target.options.lintFilePatterns.map( - (pattern) => { - return interpolate(pattern, { - workspaceRoot: '', - projectRoot: projectDetails.root, - projectName: projectDetails.projectName, - }); + if (Object.keys(target.configurations).length !== 0) { + for (const configuration in target.configurations) { + if (Object.keys(target.configurations[configuration]).length === 0) { + delete target.configurations[configuration]; } - ); - - target.options.args = normalizedLintFilePatterns.map((pattern) => - pattern.startsWith(projectDetails.root) - ? pattern.replace(new RegExp(`^${projectDetails.root}/`), './') - : pattern - ); - - delete target.options.lintFilePatterns; + } + if (Object.keys(target.configurations).length === 0) { + delete target.configurations; + } } } + if (target.outputs) { + processTargetOutputs(target, [], inferredTargetConfiguration, { + projectName: projectDetails.projectName, + projectRoot: projectDetails.root, + }); + } + return target; } +function handlePropertiesInOptions( + options: Record, + projectDetails: { projectName: string; root: string }, + target: TargetConfiguration +) { + if ('eslintConfig' in options) { + options.config = toProjectRelativePath( + options.eslintConfig, + projectDetails.root + ); + delete options.eslintConfig; + } + + if ('force' in options) { + delete options.force; + } + + if ('silent' in options) { + delete options.silent; + } + + if ('hasTypeAwareRules' in options) { + delete options.hasTypeAwareRules; + } + + if ('errorOnUnmatchedPattern' in options) { + if (!options.errorOnUnmatchedPattern) { + options['no-error-on-unmatched-pattern'] = true; + } + delete options.errorOnUnmatchedPattern; + } + + if ('outputFile' in options) { + target.outputs ??= []; + target.outputs.push(options.outputFile); + } + + for (const key in targetOptionsToCliMap) { + if (options[key]) { + const prevValue = options[key]; + delete options[key]; + options[targetOptionsToCliMap[key]] = prevValue; + } + } + + if ('lintFilePatterns' in options) { + const normalizedLintFilePatterns = options.lintFilePatterns.map( + (pattern) => { + return interpolate(pattern, { + workspaceRoot: '', + projectRoot: projectDetails.root, + projectName: projectDetails.projectName, + }); + } + ); + + options.args = normalizedLintFilePatterns.map((pattern) => + pattern.startsWith(projectDetails.root) + ? pattern.replace(new RegExp(`^${projectDetails.root}/`), './') + : pattern + ); + + delete options.lintFilePatterns; + } +} + export default convertToInferred; diff --git a/packages/playwright/src/generators/convert-to-inferred/convert-to-inferred.ts b/packages/playwright/src/generators/convert-to-inferred/convert-to-inferred.ts index 48f64569c2..114944beba 100644 --- a/packages/playwright/src/generators/convert-to-inferred/convert-to-inferred.ts +++ b/packages/playwright/src/generators/convert-to-inferred/convert-to-inferred.ts @@ -45,14 +45,25 @@ function postTargetTransformer( delete target.options.config; } - for (const [key, value] of Object.entries(target.options)) { - const newKeyName = names(key).fileName; - delete target.options[key]; - target.options[newKeyName] = value; + handleRenameOfProperties(target.options); + } + + if (target.configurations) { + for (const configurationName in target.configurations) { + const configuration = target.configurations[configurationName]; + handleRenameOfProperties(configuration); } } return target; } +function handleRenameOfProperties(options: Record) { + for (const [key, value] of Object.entries(options)) { + const newKeyName = names(key).fileName; + delete options[key]; + options[newKeyName] = value; + } +} + export default convertToInferred; diff --git a/packages/rollup/src/generators/convert-to-inferred/lib/extract-rollup-config-from-executor-options.spec.ts b/packages/rollup/src/generators/convert-to-inferred/lib/extract-rollup-config-from-executor-options.spec.ts new file mode 100644 index 0000000000..a55fc4b3ec --- /dev/null +++ b/packages/rollup/src/generators/convert-to-inferred/lib/extract-rollup-config-from-executor-options.spec.ts @@ -0,0 +1,241 @@ +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; + +import { extractRollupConfigFromExecutorOptions } from './extract-rollup-config-from-executor-options'; + +describe('extract-rollup-config-from-executor-options', () => { + it('should extract the options correctly', () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + tree.write( + `apps/myapp/rollup.config.js`, + `export default (config) => {return config;}` + ); + // ACT + const defaultValues = extractRollupConfigFromExecutorOptions( + tree, + { + outputPath: 'dist/apps/myapp', + tsConfig: 'apps/myapp/tsconfig.json', + project: '', + main: 'src/lib/index.ts', + rollupConfig: 'apps/myapp/rollup.config.js', + watch: true, + format: ['esm', 'cjs'], + }, + {}, + 'apps/myapp' + ); + + // ASSERT + const configFile = tree.read('apps/myapp/rollup.config.js', 'utf-8'); + expect(configFile).toMatchInlineSnapshot(` + "const { withNx } = require('@nx/rollup/with-nx'); + + // These options were migrated by @nx/rollup:convert-to-inferred from project.json + const options = { + "outputPath": "../../dist/apps/myapp", + "tsConfig": "./tsconfig.json", + "project": "", + "main": "../../src/lib/index.ts", + "format": [ + "esm", + "cjs" + ] + }; + + let config = withNx(options, { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + // e.g. + // output: { sourcemap: true }, + }); + + config = require('./rollup.migrated.config.js')(config, options); + + module.exports = config;" + `); + expect(tree.exists('apps/myapp/rollup.migrated.config.js')).toBeTruthy(); + expect(defaultValues).toMatchInlineSnapshot(` + { + "format": [ + "esm", + "cjs", + ], + "main": "../../src/lib/index.ts", + "outputPath": "../../dist/apps/myapp", + "project": "", + "tsConfig": "./tsconfig.json", + } + `); + }); + + it('should extract configurations that do not defined a rollupConfig into the rollup.config.js file', () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + tree.write( + `apps/myapp/rollup.config.js`, + `export default (config) => {return config;}` + ); + tree.write( + `apps/myapp/rollup.dev.config.js`, + `export default (config) => {return config;}` + ); + // ACT + const defaultValues = extractRollupConfigFromExecutorOptions( + tree, + { + outputPath: 'dist/apps/myapp', + tsConfig: 'apps/myapp/tsconfig.json', + project: '', + main: 'src/lib/index.ts', + rollupConfig: 'apps/myapp/rollup.config.js', + watch: true, + format: ['esm', 'cjs'], + }, + { + dev: { + format: ['esm'], + }, + }, + 'apps/myapp' + ); + + // ASSERT + const configFile = tree.read('apps/myapp/rollup.config.js', 'utf-8'); + expect(configFile).toMatchInlineSnapshot(` + "const { withNx } = require('@nx/rollup/with-nx'); + + // These options were migrated by @nx/rollup:convert-to-inferred from project.json + const configValues = { + "default": { + "outputPath": "../../dist/apps/myapp", + "tsConfig": "./tsconfig.json", + "project": "", + "main": "../../src/lib/index.ts", + "format": [ + "esm", + "cjs" + ] + }, + "dev": { + "format": [ + "esm" + ] + } + }; + + // Determine the correct configValue to use based on the configuration + const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default'; + + const options = { + ...configValues.default, + ...configValues[nxConfiguration], + }; + + let config = withNx(options, { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + // e.g. + // output: { sourcemap: true }, + }); + + config = require('./rollup.migrated.config.js')(config, options); + + module.exports = config;" + `); + expect(tree.exists('apps/myapp/rollup.migrated.config.js')).toBeTruthy(); + expect(defaultValues).toMatchInlineSnapshot(` + { + "format": [ + "esm", + "cjs", + ], + "main": "../../src/lib/index.ts", + "outputPath": "../../dist/apps/myapp", + "project": "", + "tsConfig": "./tsconfig.json", + } + `); + }); + + it('should extract configurations that do defined a rollupConfig into their own rollup.{configName}.config.js file', () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + tree.write( + `apps/myapp/rollup.config.js`, + `export default (config) => {return config;}` + ); + tree.write( + `apps/myapp/rollup.dev.config.js`, + `export default (config) => {return config;}` + ); + // ACT + const defaultValues = extractRollupConfigFromExecutorOptions( + tree, + { + outputPath: 'dist/apps/myapp', + tsConfig: 'apps/myapp/tsconfig.json', + project: '', + main: 'src/lib/index.ts', + rollupConfig: 'apps/myapp/rollup.config.js', + watch: true, + format: ['esm', 'cjs'], + }, + { + dev: { + rollupConfig: 'rollup.dev.config.js', + format: ['esm'], + }, + }, + 'apps/myapp' + ); + + // ASSERT + const configFile = tree.read('apps/myapp/rollup.dev.config.js', 'utf-8'); + expect(configFile).toMatchInlineSnapshot(` + "const { withNx } = require('@nx/rollup/with-nx'); + + // These options were migrated by @nx/rollup:convert-to-inferred from project.json + const configValues = { + "outputPath": "../../dist/apps/myapp", + "tsConfig": "./tsconfig.json", + "project": "", + "main": "../../src/lib/index.ts", + "format": [ + "esm" + ] + }; + + // Determine the correct configValue to use based on the configuration + const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default'; + + const options = { + ...configValues.default, + ...configValues[nxConfiguration], + }; + + let config = withNx(options, { + // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options + // e.g. + // output: { sourcemap: true }, + }); + + config = require('./rollup.dev.migrated.config.js')(config, options); + + module.exports = config;" + `); + expect( + tree.exists('apps/myapp/rollup.dev.migrated.config.js') + ).toBeTruthy(); + expect(defaultValues).toMatchInlineSnapshot(` + { + "format": [ + "esm", + "cjs", + ], + "main": "../../src/lib/index.ts", + "outputPath": "../../dist/apps/myapp", + "project": "", + "tsConfig": "./tsconfig.json", + } + `); + }); +}); diff --git a/packages/rollup/src/generators/convert-to-inferred/lib/extract-rollup-config-from-executor-options.ts b/packages/rollup/src/generators/convert-to-inferred/lib/extract-rollup-config-from-executor-options.ts index 49727b9fcb..5eb1bbbd46 100644 --- a/packages/rollup/src/generators/convert-to-inferred/lib/extract-rollup-config-from-executor-options.ts +++ b/packages/rollup/src/generators/convert-to-inferred/lib/extract-rollup-config-from-executor-options.ts @@ -10,8 +10,6 @@ export function extractRollupConfigFromExecutorOptions( ) { normalizePathOptions(projectRoot, options); - let newRollupConfigContent: string; - const hasConfigurations = !!configurations && Object.keys(configurations).length > 0; @@ -40,26 +38,104 @@ export function extractRollupConfigFromExecutorOptions( delete options[key]; defaultOptions[key] = value; } + let configurationOptions: Record>; if (hasConfigurations) { - const configurationOptions: Record> = {}; + configurationOptions = {}; for (const [key, value] of Object.entries(configurations)) { + let newConfigFileName: string; + let oldRollupConfigForConfiguration: string[]; for (const [optionKey, optionValue] of Object.entries(value)) { if (optionKey === 'watch') continue; + + /** + * If a configuration lists rollupConfig as an option + * Collect the options and set up a new file to point to + * Set the `--config` option to the new file + */ + if (optionKey === 'rollupConfig') { + oldRollupConfigForConfiguration = Array.isArray(optionValue) + ? optionValue + : optionValue + ? [optionValue] + : []; + newConfigFileName = `rollup.${key}.config.js`; + for (let i = 0; i < oldRollupConfigForConfiguration.length; i++) { + const file = oldRollupConfigForConfiguration[i]; + if (file === newConfigFileName) { + tree.rename( + joinPathFragments(projectRoot, newConfigFileName), + joinPathFragments( + projectRoot, + `rollup.${key}.migrated.config.js` + ) + ); + oldRollupConfigForConfiguration.splice( + i, + 1, + `./rollup.${key}.migrated.config.js` + ); + } + } + + delete value[optionKey]; + value['config'] = newConfigFileName; + continue; + } + delete value[optionKey]; configurationOptions[key] ??= {}; configurationOptions[key][optionKey] = optionValue; } - } - newRollupConfigContent = stripIndents` + /** + * Only if we encountered a rollupConfig in the current configuration + * should we write a new config file, containing all the config values + */ + if (newConfigFileName) { + tree.write( + joinPathFragments(projectRoot, newConfigFileName), + createNewRollupConfig( + oldRollupConfigForConfiguration, + defaultOptions, + configurationOptions[key], + true + ) + ); + } + } + } + + tree.write( + joinPathFragments(projectRoot, `rollup.config.js`), + createNewRollupConfig(oldRollupConfig, defaultOptions, configurationOptions) + ); + + return defaultOptions; +} + +function createNewRollupConfig( + oldRollupConfig: string[], + defaultOptions: Record, + configurationOptions?: + | Record> + | Record, + singleConfiguration = false +) { + if (configurationOptions) { + return stripIndents` const { withNx } = require('@nx/rollup/with-nx'); // These options were migrated by @nx/rollup:convert-to-inferred from project.json const configValues = ${JSON.stringify( - { - default: defaultOptions, - ...configurationOptions, - }, + singleConfiguration + ? { + ...defaultOptions, + ...configurationOptions, + } + : { + default: defaultOptions, + ...configurationOptions, + }, null, 2 )}; @@ -86,7 +162,7 @@ export function extractRollupConfigFromExecutorOptions( module.exports = config; `; } else { - newRollupConfigContent = stripIndents` + return stripIndents` const { withNx } = require('@nx/rollup/with-nx'); // These options were migrated by @nx/rollup:convert-to-inferred from project.json @@ -106,11 +182,4 @@ export function extractRollupConfigFromExecutorOptions( module.exports = config; `; } - - tree.write( - joinPathFragments(projectRoot, `rollup.config.js`), - newRollupConfigContent - ); - - return defaultOptions; } diff --git a/packages/vite/src/generators/convert-to-inferred/lib/__snapshots__/build-post-target-transformer.spec.ts.snap b/packages/vite/src/generators/convert-to-inferred/lib/__snapshots__/build-post-target-transformer.spec.ts.snap index b91d6c1abb..5c38f56416 100644 --- a/packages/vite/src/generators/convert-to-inferred/lib/__snapshots__/build-post-target-transformer.spec.ts.snap +++ b/packages/vite/src/generators/convert-to-inferred/lib/__snapshots__/build-post-target-transformer.spec.ts.snap @@ -206,6 +206,134 @@ export default defineConfig({plugins: [nxViteTsPaths({ buildLibsFromSource: opti });" `; +exports[`buildPostTargetTransformer should move the AST options to each vite config file correctly for configurations 1`] = ` +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + // These options were migrated by @nx/vite:convert-to-inferred from the project.json file. + const configValues = {"default":{"buildLibsFromSource":true},"dev":{"buildLibsFromSource":true}}; + + // Determine the correct configValue to use based on the configuration + const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default'; + + const options = { + ...configValues.default, + ...(configValues[nxConfiguration] ?? {}) + } + + +export default defineConfig({ + root: __dirname, + cacheDir: '../../node_modules/.vite/apps/myapp', + + server: { + port: 4200, + host: 'localhost', + }, + + preview: { + port: 4300, + host: 'localhost', + }, + + plugins: [react(), nxViteTsPaths({ buildLibsFromSource: options.buildLibsFromSource }),], + + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + + build: { + outDir: '../../dist/apps/myapp', + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + + test: { + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/apps/myapp', + provider: 'v8', + }, + }, +});" +`; + +exports[`buildPostTargetTransformer should move the AST options to each vite config file correctly for configurations 2`] = ` +"/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + // These options were migrated by @nx/vite:convert-to-inferred from the project.json file. + const configValues = {"default":{},"dev":{"buildLibsFromSource":true}}; + + // Determine the correct configValue to use based on the configuration + const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default'; + + const options = { + ...configValues.default, + ...(configValues[nxConfiguration] ?? {}) + } + + +export default defineConfig({ + root: __dirname, + cacheDir: '../../node_modules/.vite/apps/myapp', + + server: { + port: 4200, + host: 'localhost', + }, + + preview: { + port: 4300, + host: 'localhost', + }, + + plugins: [react(), nxViteTsPaths({ buildLibsFromSource: options.buildLibsFromSource }),], + + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + + build: { + outDir: '../../dist/apps/myapp', + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + + test: { + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/apps/myapp', + provider: 'v8', + }, + }, +});" +`; + exports[`buildPostTargetTransformer should remove the correct options and move the AST options to the vite config file correctly and remove outputs when they match inferred 1`] = ` "/// import { defineConfig } from 'vite'; diff --git a/packages/vite/src/generators/convert-to-inferred/lib/build-post-target-transformer.spec.ts b/packages/vite/src/generators/convert-to-inferred/lib/build-post-target-transformer.spec.ts index b204f4e94d..380a3e85d8 100644 --- a/packages/vite/src/generators/convert-to-inferred/lib/build-post-target-transformer.spec.ts +++ b/packages/vite/src/generators/convert-to-inferred/lib/build-post-target-transformer.spec.ts @@ -13,7 +13,7 @@ describe('buildPostTargetTransformer', () => { outputs: ['{options.outputPath}'], options: { outputPath: 'build/apps/myapp', - configFile: 'vite.config.ts', + configFile: 'apps/myapp/vite.config.ts', buildLibsFromSource: true, skipTypeCheck: false, watch: true, @@ -27,7 +27,7 @@ describe('buildPostTargetTransformer', () => { outputs: ['{projectRoot}/{options.outDir}'], }; - tree.write('vite.config.ts', viteConfigFileV17); + tree.write('apps/myapp/vite.config.ts', viteConfigFileV17); // ACT const target = buildPostTargetTransformer( @@ -41,12 +41,74 @@ describe('buildPostTargetTransformer', () => { ); // ASSERT - const configFile = tree.read('vite.config.ts', 'utf-8'); + const configFile = tree.read('apps/myapp/vite.config.ts', 'utf-8'); expect(configFile).toMatchSnapshot(); expect(target).toMatchInlineSnapshot(` { "options": { - "config": "../../vite.config.ts", + "config": "./vite.config.ts", + "outDir": "../../build/apps/myapp", + "watch": true, + }, + } + `); + }); + + it('should move the AST options to each vite config file correctly for configurations', () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + + const targetConfiguration = { + outputs: ['{options.outputPath}'], + options: { + outputPath: 'build/apps/myapp', + configFile: 'apps/myapp/vite.config.ts', + buildLibsFromSource: true, + skipTypeCheck: false, + watch: true, + generatePackageJson: true, + includeDevDependenciesInPackageJson: false, + tsConfig: 'apps/myapp/tsconfig.json', + }, + configurations: { + dev: { + configFile: 'apps/myapp/vite.dev.config.ts', + }, + }, + }; + + const inferredTargetConfiguration = { + outputs: ['{projectRoot}/{options.outDir}'], + }; + + tree.write('apps/myapp/vite.config.ts', viteConfigFileV17); + tree.write('apps/myapp/vite.dev.config.ts', viteConfigFileV17); + + // ACT + const target = buildPostTargetTransformer( + targetConfiguration, + tree, + { + projectName: 'myapp', + root: 'apps/myapp', + }, + inferredTargetConfiguration + ); + + // ASSERT + const configFile = tree.read('apps/myapp/vite.config.ts', 'utf-8'); + expect(configFile).toMatchSnapshot(); + const devConfigFile = tree.read('apps/myapp/vite.dev.config.ts', 'utf-8'); + expect(devConfigFile).toMatchSnapshot(); + expect(target).toMatchInlineSnapshot(` + { + "configurations": { + "dev": { + "config": "./vite.dev.config.ts", + }, + }, + "options": { + "config": "./vite.config.ts", "outDir": "../../build/apps/myapp", "watch": true, }, diff --git a/packages/vite/src/generators/convert-to-inferred/lib/build-post-target-transformer.ts b/packages/vite/src/generators/convert-to-inferred/lib/build-post-target-transformer.ts index 0e2ebda78a..210c6f23fe 100644 --- a/packages/vite/src/generators/convert-to-inferred/lib/build-post-target-transformer.ts +++ b/packages/vite/src/generators/convert-to-inferred/lib/build-post-target-transformer.ts @@ -1,4 +1,8 @@ -import { type TargetConfiguration, type Tree } from '@nx/devkit'; +import { + joinPathFragments, + type TargetConfiguration, + type Tree, +} from '@nx/devkit'; import { tsquery } from '@phenomnomnominal/tsquery'; import { extname } from 'path/posix'; import { @@ -20,35 +24,42 @@ export function buildPostTargetTransformer( default: {}, }; - if (target.options) { - if (target.options.configFile) { - viteConfigPath = target.options.configFile; - } - - removePropertiesFromTargetOptions( - tree, - target.options, - viteConfigPath, - projectDetails.root, - configValues['default'], - true - ); - } - if (target.configurations) { for (const configurationName in target.configurations) { const configuration = target.configurations[configurationName]; configValues[configurationName] = {}; + let configurationConfigFile = viteConfigPath; + if (configuration.configFile) { + if ('buildLibsFromSource' in target.options) { + configuration.buildLibsFromSource = + target.options.buildLibsFromSource; + } + + configurationConfigFile = configuration.configFile; + } + removePropertiesFromTargetOptions( tree, configuration, - viteConfigPath, + configurationConfigFile, projectDetails.root, - configValues[configurationName] + configValues[configurationName], + configuration.configFile && configuration.configFile !== viteConfigPath ); + } - if (Object.keys(configuration).length === 0) { - delete target.configurations[configurationName]; + for (const configurationName in target.configurations) { + const configuration = target.configurations[configurationName]; + if ( + configuration.config && + configuration.config !== + toProjectRelativePath(viteConfigPath, projectDetails.root) + ) { + const configFilePath = joinPathFragments( + projectDetails.root, + configuration.config + ); + addConfigValuesToViteConfig(tree, configFilePath, configValues); } } @@ -67,6 +78,21 @@ export function buildPostTargetTransformer( } } + if (target.options) { + if (target.options.configFile) { + viteConfigPath = target.options.configFile; + } + + removePropertiesFromTargetOptions( + tree, + target.options, + viteConfigPath, + projectDetails.root, + configValues['default'], + true + ); + } + if (target.outputs) { processTargetOutputs( target, @@ -97,7 +123,7 @@ function removePropertiesFromTargetOptions( viteConfigPath: string, projectRoot: string, configValues: Record, - defaultOptions = false + needsAstTransform = false ) { if ('configFile' in targetOptions) { targetOptions.config = toProjectRelativePath( @@ -117,7 +143,7 @@ function removePropertiesFromTargetOptions( if ('buildLibsFromSource' in targetOptions) { configValues['buildLibsFromSource'] = targetOptions.buildLibsFromSource; - if (defaultOptions) { + if (needsAstTransform) { moveBuildLibsFromSourceToViteConfig(tree, viteConfigPath); } delete targetOptions.buildLibsFromSource; diff --git a/packages/vite/src/generators/convert-to-inferred/lib/preview-post-target-transformer.ts b/packages/vite/src/generators/convert-to-inferred/lib/preview-post-target-transformer.ts index c0bee4801c..017ff89091 100644 --- a/packages/vite/src/generators/convert-to-inferred/lib/preview-post-target-transformer.ts +++ b/packages/vite/src/generators/convert-to-inferred/lib/preview-post-target-transformer.ts @@ -27,10 +27,6 @@ export function previewPostTargetTransformer(migrationLogs: AggregatedLog) { projectDetails.projectName, migrationLogs ); - - if (Object.keys(configuration).length === 0) { - delete target.configurations[configurationName]; - } } if (Object.keys(target.configurations).length === 0) { diff --git a/packages/vite/src/generators/convert-to-inferred/lib/serve-post-target-transformer.ts b/packages/vite/src/generators/convert-to-inferred/lib/serve-post-target-transformer.ts index 6bdf366499..61db1f753a 100644 --- a/packages/vite/src/generators/convert-to-inferred/lib/serve-post-target-transformer.ts +++ b/packages/vite/src/generators/convert-to-inferred/lib/serve-post-target-transformer.ts @@ -34,10 +34,6 @@ export function servePostTargetTransformer(migrationLogs: AggregatedLog) { projectDetails.projectName, migrationLogs ); - - if (Object.keys(configuration).length === 0) { - delete target.configurations[configurationName]; - } } if (Object.keys(target.configurations).length === 0) { diff --git a/packages/vite/src/generators/convert-to-inferred/lib/test-post-target-transformer.ts b/packages/vite/src/generators/convert-to-inferred/lib/test-post-target-transformer.ts index e99fd0458b..8e85aba0ec 100644 --- a/packages/vite/src/generators/convert-to-inferred/lib/test-post-target-transformer.ts +++ b/packages/vite/src/generators/convert-to-inferred/lib/test-post-target-transformer.ts @@ -16,10 +16,6 @@ export function testPostTargetTransformer( for (const configurationName in target.configurations) { const configuration = target.configurations[configurationName]; removePropertiesFromTargetOptions(configuration, projectDetails.root); - - if (Object.keys(configuration).length === 0) { - delete target.configurations[configurationName]; - } } if (Object.keys(target.configurations).length === 0) {