From dba4a065ca2dacb93c8f0d4362eec00b812d4918 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Fri, 27 Jan 2023 16:57:38 +0200 Subject: [PATCH] feat(react): migrate webpack config (#14663) --- packages/react/migrations.json | 6 + .../webpack-config-setup.spec.ts.snap | 57 ++++++ .../webpack-config-setup.spec.ts | 176 ++++++++++++++++++ .../update-15-6-3/webpack-config-setup.ts | 128 +++++++++++++ .../webpack-config-setup.spec.ts.snap | 4 +- .../webpack-config-setup.spec.ts | 29 +++ .../update-15-6-3/webpack-config-setup.ts | 20 +- 7 files changed, 409 insertions(+), 11 deletions(-) create mode 100644 packages/react/src/migrations/update-15-6-3/__snapshots__/webpack-config-setup.spec.ts.snap create mode 100644 packages/react/src/migrations/update-15-6-3/webpack-config-setup.spec.ts create mode 100644 packages/react/src/migrations/update-15-6-3/webpack-config-setup.ts diff --git a/packages/react/migrations.json b/packages/react/migrations.json index 982d7cb06f..97d9f0feb5 100644 --- a/packages/react/migrations.json +++ b/packages/react/migrations.json @@ -77,6 +77,12 @@ "version": "15.3.0-beta.0", "description": "Install new dependencies for React projects using Webpack or Rollup.", "factory": "./src/migrations/update-15-3-0/install-webpack-rollup-dependencies" + }, + "react-webpack-config-setup": { + "cli": "nx", + "version": "15.6.3-beta.0", + "description": "Creates or updates webpack.config.js file with the new options for webpack.", + "factory": "./src/migrations/update-15-6-3/webpack-config-setup" } }, "packageJsonUpdates": { diff --git a/packages/react/src/migrations/update-15-6-3/__snapshots__/webpack-config-setup.spec.ts.snap b/packages/react/src/migrations/update-15-6-3/__snapshots__/webpack-config-setup.spec.ts.snap new file mode 100644 index 0000000000..7f2fa04698 --- /dev/null +++ b/packages/react/src/migrations/update-15-6-3/__snapshots__/webpack-config-setup.spec.ts.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`15.6.3 migration (setup webpack.config file for React apps) should create webpack.config.js for React projects only 1`] = ` +" + const { composePlugins, withNx } = require('@nrwl/webpack'); + const { withReact } = require('@nrwl/react'); + + // Nx plugins for webpack. + module.exports = composePlugins(withNx(), withReact(), (config, { options, context }) => { + // Update the webpack config as needed here. + // e.g. config.plugins.push(new MyPlugin()) + return config; + }); + " +`; + +exports[`15.6.3 migration (setup webpack.config file for React apps) should create webpack.config.js for React projects only 2`] = ` +" + const { composePlugins, withNx } = require('@nrwl/webpack'); + const { withReact } = require('@nrwl/react'); + + // Nx plugins for webpack. + module.exports = composePlugins(withNx(), withReact(), (config, { options, context }) => { + // Update the webpack config as needed here. + // e.g. config.plugins.push(new MyPlugin()) + return config; + }); + " +`; + +exports[`15.6.3 migration (setup webpack.config file for React apps) should create webpack.config.js for React projects only 3`] = ` +" + const { composePlugins, withNx } = require('@nrwl/webpack'); + const { withReact } = require('@nrwl/react'); + + // Nx plugins for webpack. + module.exports = composePlugins(withNx(), withReact(), (config, { options, context }) => { + // Update the webpack config as needed here. + // e.g. config.plugins.push(new MyPlugin()) + return config; + }); + " +`; + +exports[`15.6.3 migration (setup webpack.config file for React apps) should create webpack.config.js for React projects only 4`] = ` +" + const { composePlugins, withNx } = require('@nrwl/webpack'); + const { withReact } = require('@nrwl/react'); + + // Nx plugins for webpack. + module.exports = composePlugins(withNx(), withReact(), (config, { options, context }) => { + // Note: This was added by an Nx migration. + // You should consider inlining the logic into this file. + return require('./webpack.something.old.ts')(config, context); + }); + " +`; diff --git a/packages/react/src/migrations/update-15-6-3/webpack-config-setup.spec.ts b/packages/react/src/migrations/update-15-6-3/webpack-config-setup.spec.ts new file mode 100644 index 0000000000..3bc6443f1b --- /dev/null +++ b/packages/react/src/migrations/update-15-6-3/webpack-config-setup.spec.ts @@ -0,0 +1,176 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + addProjectConfiguration, + readProjectConfiguration, + Tree, +} from '@nrwl/devkit'; +import webpackConfigSetup from './webpack-config-setup'; + +describe('15.6.3 migration (setup webpack.config file for React apps)', () => { + let tree: Tree; + + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + + addProjectConfiguration(tree, 'react1', { + root: 'apps/react1', + targets: { + build: { + executor: '@nrwl/webpack:webpack', + options: { + main: 'apps/react1/src/main.tsx', + webpackConfig: '@nrwl/react/plugins/webpack', + }, + }, + }, + }); + addProjectConfiguration(tree, 'react2', { + root: 'apps/react2', + targets: { + custom: { + executor: '@nrwl/webpack:webpack', + options: { + main: 'apps/react2/src/main.tsx', + }, + }, + }, + }); + + addProjectConfiguration(tree, 'react3', { + root: 'apps/react3', + targets: { + custom: { + executor: '@nrwl/webpack:webpack', + options: { + webpackConfig: '@nrwl/react/plugins/webpack', + }, + }, + }, + }); + + addProjectConfiguration(tree, 'react4', { + root: 'apps/react4', + targets: { + custom: { + executor: '@nrwl/webpack:webpack', + options: { + main: 'apps/react4/src/main.tsx', + webpackConfig: 'apps/react4/webpack.something.ts', + }, + }, + }, + }); + tree.write('apps/react4/webpack.something.ts', 'some content'); + + addProjectConfiguration(tree, 'app4', { + root: 'apps/app4', + targets: { + custom: { + executor: '@nrwl/webpack:webpack', + options: { + webpackConfig: 'some/random/path/webpack.something.ts', + }, + }, + }, + }); + + tree.write('some/random/path/webpack.something.ts', 'some content'); + + addProjectConfiguration(tree, 'app5', { + root: 'apps/app5', + targets: { + custom: { + executor: '@nrwl/webpack:webpack', + options: { + isolatedConfig: true, + }, + }, + }, + }); + + addProjectConfiguration(tree, 'app6', { + root: 'apps/app6', + targets: { + build: { + executor: '@nrwl/webpack:webpack', + options: { + main: 'apps/app6/src/main.ts', + }, + }, + }, + }); + + await webpackConfigSetup(tree); + }); + + it('should create webpack.config.js for React projects only', () => { + expect( + tree.read('apps/react1/webpack.config.js', 'utf-8') + ).toMatchSnapshot(); + expect( + tree.read('apps/react2/webpack.config.js', 'utf-8') + ).toMatchSnapshot(); + expect( + tree.read('apps/react3/webpack.config.js', 'utf-8') + ).toMatchSnapshot(); + + expect( + tree.read('apps/react4/webpack.something.ts', 'utf-8') + ).toMatchSnapshot(); + + expect( + tree.read('apps/react4/webpack.something.old.ts', 'utf-8') + ).toMatchInlineSnapshot(`"some content"`); + }); + + it('should ignore non-react projects or isolatedConfig', () => { + expect( + tree.read('some/random/path/webpack.something.ts', 'utf-8') + ).toMatchInlineSnapshot(`"some content"`); + expect( + tree.exists('some/random/path/webpack.something.old.ts') + ).toBeFalsy(); + expect(tree.exists('apps/app5/webpack.config.js')).toBeFalsy(); + expect(tree.exists('apps/app6/webpack.config.js')).toBeFalsy(); + }); + + it('should update the project configuration - executor options', () => { + expect( + readProjectConfiguration(tree, 'react1').targets.build.options + .webpackConfig + ).toBe('apps/react1/webpack.config.js'); + expect( + readProjectConfiguration(tree, 'react2').targets.custom.options + .webpackConfig + ).toBe('apps/react2/webpack.config.js'); + + expect( + readProjectConfiguration(tree, 'react3').targets.custom.options + .webpackConfig + ).toBe('apps/react3/webpack.config.js'); + + expect( + readProjectConfiguration(tree, 'react4').targets.custom.options + .webpackConfig + ).toBe('apps/react4/webpack.something.ts'); + + expect( + readProjectConfiguration(tree, 'react1').targets.build.options + .isolatedConfig + ).toBeTruthy(); + expect( + readProjectConfiguration(tree, 'react2').targets.custom.options + .isolatedConfig + ).toBeTruthy(); + + expect( + readProjectConfiguration(tree, 'react3').targets.custom.options + .isolatedConfig + ).toBeTruthy(); + + expect( + readProjectConfiguration(tree, 'react4').targets.custom.options + .isolatedConfig + ).toBeTruthy(); + }); +}); diff --git a/packages/react/src/migrations/update-15-6-3/webpack-config-setup.ts b/packages/react/src/migrations/update-15-6-3/webpack-config-setup.ts new file mode 100644 index 0000000000..9a8287249a --- /dev/null +++ b/packages/react/src/migrations/update-15-6-3/webpack-config-setup.ts @@ -0,0 +1,128 @@ +import { + formatFiles, + logger, + readProjectConfiguration, + Tree, + updateProjectConfiguration, +} from '@nrwl/devkit'; +import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; +import { basename } from 'path'; + +export default async function (tree: Tree) { + forEachExecutorOptions( + tree, + '@nrwl/webpack:webpack', + (options: {}, projectName, targetName, _configurationName) => { + // If isolatedConfig is set, we don't need to do anything + // If it is NOT React, we don't need to do anything + if ( + options?.['isolatedConfig'] || + !( + options?.['main']?.match(/main\.(t|j)sx$/) || + options?.['webpackConfig'] === '@nrwl/react/plugins/webpack' + ) + ) { + return; + } + + // If webpackConfig is set, update it with the new options + // If webpackConfig is not set, we need to create a new + // webpack.config.js file and set the path to it in the + // executor options + + if ( + options?.['webpackConfig'] && + options['webpackConfig'] !== '@nrwl/react/plugins/webpack' + ) { + let oldName = options['webpackConfig']; + if (options['webpackConfig'].endsWith('.js')) { + oldName = options['webpackConfig'].replace('.js', '.old.js'); + } + if (options['webpackConfig'].endsWith('.ts')) { + oldName = options['webpackConfig'].replace('.ts', '.old.ts'); + } + + renameFile(tree, options['webpackConfig'], oldName); + + const justTheFileName = basename(oldName); + tree.write( + options['webpackConfig'], + ` + const { composePlugins, withNx } = require('@nrwl/webpack'); + const { withReact } = require('@nrwl/react'); + + // Nx plugins for webpack. + module.exports = composePlugins(withNx(), withReact(), (config, { options, context }) => { + // Note: This was added by an Nx migration. + // You should consider inlining the logic into this file. + return require('./${justTheFileName}')(config, context); + }); + ` + ); + + options['isolatedConfig'] = true; + + const projectConfiguration = readProjectConfiguration( + tree, + projectName + ); + projectConfiguration.targets[targetName].options = options; + updateProjectConfiguration(tree, projectName, projectConfiguration); + + logger.info( + ` + ${options['webpackConfig']} has been renamed to ${oldName} and a new ${options['webpackConfig']} + has been created for your project ${projectName}. + You should consider inlining the logic from ${oldName} into ${options['webpackConfig']}. + You can read our guide on how to do this here: + + https://nx.dev/packages/webpack/documents/webpack-config-setup + ` + ); + } else { + const projectConfiguration = readProjectConfiguration( + tree, + projectName + ); + + if (!options) { + options = {}; + } + + options[ + 'webpackConfig' + ] = `${projectConfiguration.root}/webpack.config.js`; + options['isolatedConfig'] = true; + + tree.write( + options['webpackConfig'], + ` + const { composePlugins, withNx } = require('@nrwl/webpack'); + const { withReact } = require('@nrwl/react'); + + // Nx plugins for webpack. + module.exports = composePlugins(withNx(), withReact(), (config, { options, context }) => { + // Update the webpack config as needed here. + // e.g. config.plugins.push(new MyPlugin()) + return config; + }); + ` + ); + + projectConfiguration.targets[targetName].options = options; + updateProjectConfiguration(tree, projectName, projectConfiguration); + } + } + ); + + await formatFiles(tree); +} + +function renameFile(tree: Tree, from: string, to: string) { + const buffer = tree.read(from); + if (!buffer) { + return; + } + tree.write(to, buffer); + tree.delete(from); +} diff --git a/packages/webpack/src/migrations/update-15-6-3/__snapshots__/webpack-config-setup.spec.ts.snap b/packages/webpack/src/migrations/update-15-6-3/__snapshots__/webpack-config-setup.spec.ts.snap index 815655ebf5..6a7184f06e 100644 --- a/packages/webpack/src/migrations/update-15-6-3/__snapshots__/webpack-config-setup.spec.ts.snap +++ b/packages/webpack/src/migrations/update-15-6-3/__snapshots__/webpack-config-setup.spec.ts.snap @@ -34,7 +34,7 @@ exports[`15.6.3 migration (setup webpack.config file) should rename existing web module.exports = composePlugins(withNx(), (config, { options, context }) => { // Note: This was added by an Nx migration. // You should consider inlining the logic into this file. - return require('./webpack.config.js')(config, context); + return require('./webpack.config.old.js')(config, context); }); " `; @@ -47,7 +47,7 @@ exports[`15.6.3 migration (setup webpack.config file) should rename existing web module.exports = composePlugins(withNx(), (config, { options, context }) => { // Note: This was added by an Nx migration. // You should consider inlining the logic into this file. - return require('./webpack.something.ts')(config, context); + return require('./webpack.something.old.ts')(config, context); }); " `; diff --git a/packages/webpack/src/migrations/update-15-6-3/webpack-config-setup.spec.ts b/packages/webpack/src/migrations/update-15-6-3/webpack-config-setup.spec.ts index a40a5795d7..2ee0b3d4d2 100644 --- a/packages/webpack/src/migrations/update-15-6-3/webpack-config-setup.spec.ts +++ b/packages/webpack/src/migrations/update-15-6-3/webpack-config-setup.spec.ts @@ -71,6 +71,30 @@ describe('15.6.3 migration (setup webpack.config file)', () => { }, }); + addProjectConfiguration(tree, 'app6', { + root: 'apps/app6', + targets: { + custom: { + executor: '@nrwl/webpack:webpack', + options: { + webpackConfig: '@nrwl/react/plugins/webpack', + }, + }, + }, + }); + + addProjectConfiguration(tree, 'app7', { + root: 'apps/app7', + targets: { + custom: { + executor: '@nrwl/webpack:webpack', + options: { + main: 'apps/app7/src/main.tsx', + }, + }, + }, + }); + await webpackConfigSetup(tree); }); @@ -136,4 +160,9 @@ describe('15.6.3 migration (setup webpack.config file)', () => { it('should not do anything if isolatedConfig is true', () => { expect(tree.exists('apps/app5/webpack.config.js')).toBeFalsy(); }); + + it('should not do anything if project is react', () => { + expect(tree.exists('apps/app6/webpack.config.js')).toBeFalsy(); + expect(tree.exists('apps/app7/webpack.config.js')).toBeFalsy(); + }); }); diff --git a/packages/webpack/src/migrations/update-15-6-3/webpack-config-setup.ts b/packages/webpack/src/migrations/update-15-6-3/webpack-config-setup.ts index 024446adff..a29c375648 100644 --- a/packages/webpack/src/migrations/update-15-6-3/webpack-config-setup.ts +++ b/packages/webpack/src/migrations/update-15-6-3/webpack-config-setup.ts @@ -19,7 +19,13 @@ export default async function (tree: Tree) { targetName, _configurationName ) => { - if (options?.['isolatedConfig']) { + // If isolatedConfig is set, we don't need to do anything + // If project is React, we don't need to do anything + if ( + options?.isolatedConfig || + options?.main?.match(/main\.(t|j)sx$/) || + options?.webpackConfig === '@nrwl/react/plugins/webpack' + ) { return; } @@ -29,11 +35,7 @@ export default async function (tree: Tree) { // executor options if (options?.webpackConfig) { - if (options.webpackConfig === '@nrwl/react/plugin/webpack') { - return; - } - - let oldName = options?.webpackConfig; + let oldName = options.webpackConfig; if (options.webpackConfig.endsWith('.js')) { oldName = options.webpackConfig.replace('.js', '.old.js'); } @@ -41,8 +43,8 @@ export default async function (tree: Tree) { oldName = options.webpackConfig.replace('.ts', '.old.ts'); } - const justTheFileName = basename(options.webpackConfig); renameFile(tree, options.webpackConfig, oldName); + const justTheFileName = basename(oldName); tree.write( options.webpackConfig, @@ -58,7 +60,7 @@ export default async function (tree: Tree) { ` ); - options['isolatedConfig'] = true; + options.isolatedConfig = true; const projectConfiguration = readProjectConfiguration( tree, @@ -88,7 +90,7 @@ export default async function (tree: Tree) { } options.webpackConfig = `${projectConfiguration.root}/webpack.config.js`; - options['isolatedConfig'] = true; + options.isolatedConfig = true; tree.write( options.webpackConfig,