Skip to content

Commit

Permalink
feat(react): add fast refresh support for webpack 5 (#6529)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo committed Jul 29, 2021
1 parent 7d6837c commit 70c0921
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 37 deletions.
@@ -0,0 +1,50 @@
import { readJson, Tree, updateJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { nodeMigrateToWebpack5Generator } from './migrate-to-webpack-5';

describe('nodeMigrateToWebpack5Generator', () => {
let tree: Tree;

beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
updateJson(tree, 'package.json', (json) => {
json.devDependencies = {
'@nrwl/cli': '100.0.0',
'@nrwl/jest': '100.0.0',
'@nrwl/node': '100.0.0',
'@nrwl/workspace': '100.0.0',
};
return json;
});
});

it('should add packages needed by Node', async () => {
await nodeMigrateToWebpack5Generator(tree, {});

const json = readJson(tree, '/package.json');

expect(json.devDependencies['webpack']).toMatch(/\^5/);
});

it('should add packages needed by Web if used', async () => {
updateJson(tree, 'package.json', (json) => {
json.devDependencies = {
'@nrwl/cli': '100.0.0',
'@nrwl/jest': '100.0.0',
'@nrwl/node': '100.0.0',
'@nrwl/react': '100.0.0',
'@nrwl/web': '100.0.0',
'@nrwl/workspace': '100.0.0',
};
return json;
});

await nodeMigrateToWebpack5Generator(tree, {});

const json = readJson(tree, '/package.json');

expect(
json.devDependencies['@pmmmwh/react-refresh-webpack-plugin']
).toMatch(/^0\.5/);
});
});
@@ -1,25 +1,58 @@
import {
addDependenciesToPackageJson,
convertNxGenerator,
GeneratorCallback,
logger,
readJson,
removeDependenciesFromPackageJson,
Tree,
} from '@nrwl/devkit';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';

const webpack5Packages = {
'copy-webpack-plugin': '^9.0.0',
'mini-css-extract-plugin': '^1.6.0',
'source-map-loader': '^2.0.1',
const basePackages = {
'copy-webpack-plugin': '^9.0.1',
webpack: '^5.47.0',
'webpack-merge': '^5.8.0',
'webpack-node-externals': '^3.0.0',
};

const webPackages = {
'mini-css-extract-plugin': '^2.1.0',
'source-map-loader': '^3.0.0',
'terser-webpack-plugin': '^5.1.1',
webpack: '^5.39.1',
'webpack-dev-server': '^3.11.2',
'webpack-merge': '^5.7.3',
'webpack-node-externals': '^2.5.2',
'webpack-sources': '^2.2.0',
'webpack-dev-server': '4.0.0-rc.0',
'webpack-sources': '^3.0.2',
'react-refresh': '^0.10.0',
'@pmmmwh/react-refresh-webpack-plugin': '0.5.0-rc.2',
};

export async function nodeMigrateToWebpack5Generator(tree: Tree, schema: {}) {
let packages = basePackages;
const tasks: GeneratorCallback[] = [];

const packageJson = readJson(tree, 'package.json');
const deps = [
...Object.keys(packageJson.dependencies), // just in case someone installed it here
...Object.keys(packageJson.devDependencies),
];

if (deps.includes('@nrwl/web')) {
packages = {
...packages,
...webPackages,
};
}

logger.info(`NX Adding webpack 5 to workspace.`);
return addDependenciesToPackageJson(tree, {}, webpack5Packages);

// Removing the packages ensures that the versions will be updated when adding them after
tasks.push(
removeDependenciesFromPackageJson(tree, [], Object.keys(packages))
);

tasks.push(addDependenciesToPackageJson(tree, {}, packages));

return runTasksInSerial(...tasks);
}

export default nodeMigrateToWebpack5Generator;
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Expand Up @@ -39,6 +39,7 @@
"@nrwl/workspace": "*",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@svgr/webpack": "^5.5.0",
"chalk": "4.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.23.1",
Expand Down
10 changes: 2 additions & 8 deletions packages/react/plugins/webpack.ts
@@ -1,10 +1,9 @@
import type { Configuration } from 'webpack';
import * as ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin';

// Add React-specific configuration
function getWebpackConfig(config: Configuration) {
// TODO(jack): Remove in Nx 13
const { isWebpack5 } = require('@nrwl/web/src/webpack/entry');
const { ReactRefreshPlugin, isWebpack5 } = require('../src/webpack/entry');
config.module.rules.push(
{
test: /\.(png|jpe?g|gif|webp)$/,
Expand Down Expand Up @@ -55,12 +54,7 @@ function getWebpackConfig(config: Configuration) {
}
);

// TODO(jack): support webpack 5
if (
!isWebpack5 &&
config.mode === 'development' &&
config['devServer']?.hot
) {
if (config.mode === 'development' && config['devServer']?.hot) {
// add `react-refresh/babel` to babel loader plugin
const babelLoader = config.module.rules.find((rule) =>
rule.loader.toString().includes('babel-loader')
Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/webpack/bundle4.ts
@@ -0,0 +1,9 @@
module.exports = function (useShim = true) {
const webpack = require('webpack');
webpack.webpack = webpack;

return {
ReactRefreshPlugin: require('@pmmmwh/react-refresh-webpack-plugin'),
webpack,
};
};
38 changes: 38 additions & 0 deletions packages/react/src/webpack/bundle5.ts
@@ -0,0 +1,38 @@
import { logger, stripIndents } from '@nrwl/devkit';
import chalk = require('chalk');

import { requireShim } from './require-shim';
import packageJson = require('../../package.json');

function validateVersion(path) {
if (
packageJson.dependencies[path] ===
requireShim(`${path}/package.json`).version
) {
logger.warn(`Found an outdated version of ${chalk.bold(path)}\n`);

logger.info(stripIndents`
If you want to use webpack 5, try installing compatible versions of the plugins.
See: https://nx.dev/guides/webpack-5
`);

throw new Error('Incompatible version');
}
}

module.exports = function (onFallback) {
try {
validateVersion('@pmmmwh/react-refresh-webpack-plugin');
} catch {
logger.info(
`NX Falling back to webpack 4 due to incompatible plugin versions`
);
onFallback();
return require('./bundle4')();
}

return {
ReactRefreshPlugin: requireShim('@pmmmwh/react-refresh-webpack-plugin'),
webpack: requireShim('webpack'),
};
};
1 change: 1 addition & 0 deletions packages/react/src/webpack/delete-in-nx-13.txt
@@ -0,0 +1 @@
TODO(jack): Delete for Nx 13
21 changes: 21 additions & 0 deletions packages/react/src/webpack/entry.ts
@@ -0,0 +1,21 @@
import { requireShim } from './require-shim';

const result = requireShim('webpack/package.json');
const version = result?.version;

exports.default = undefined;

const forceWebpack4 = process.env.NX_FORCE_WEBPACK_4;

exports.isWebpack5 = !forceWebpack4 && /^5\./.test(version);

if (exports.isWebpack5) {
Object.assign(
exports,
require('./bundle5')(() => {
exports.isWebpack5 = false;
})
);
} else {
Object.assign(exports, require('./bundle4')());
}
10 changes: 10 additions & 0 deletions packages/react/src/webpack/require-shim.ts
@@ -0,0 +1,10 @@
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
import { join } from 'path';

export function requireShim(path: string) {
try {
return require(join(appRootPath, 'node_modules', path));
} catch {
return require(path);
}
}
35 changes: 35 additions & 0 deletions packages/web/migrations.json
Expand Up @@ -33,5 +33,40 @@
"description": "Update existing .babelrc files to add missing '@nrwl/web/babel' preset if necessary.",
"factory": "./src/migrations/update-11-5-2/update-existing-babelrc-files"
}
},
"packageJsonUpdates": {
"12.6.3": {
"version": "12.6.3-beta.1",
"packages": {
"mini-css-extract-plugin": {
"version": "^2.1.0",
"alwaysAddToPackageJson": false
},
"source-map-loader": {
"version": "^3.0.0",
"alwaysAddToPackageJson": false
},
"terser-webpack-plugin": {
"version": "^5.1.1",
"alwaysAddToPackageJson": false
},
"webpack-dev-server": {
"version": "4.0.0-rc.0",
"alwajysAddToPackageJson": false
},
"webpack-sources": {
"version": "^3.0.2",
"alwaysAddToPjackageJson": false
},
"react-refresh": {
"version": "^0.10.0",
"alwaysAddToPackageJson": false
},
"@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.0-rc.2",
"alwaysAddToPackageJson": false
}
}
}
}
}
@@ -0,0 +1,34 @@
import { readJson, Tree, updateJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { webMigrateToWebpack5Generator } from './migrate-to-webpack-5';

describe('webMigrateToWebpack5Generator', () => {
let tree: Tree;

beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});

it('should add packages needed by Web ', async () => {
updateJson(tree, 'package.json', (json) => {
json.devDependencies = {
'@nrwl/cli': '100.0.0',
'@nrwl/jest': '100.0.0',
'@nrwl/node': '100.0.0',
'@nrwl/react': '100.0.0',
'@nrwl/web': '100.0.0',
'@nrwl/workspace': '100.0.0',
};
return json;
});

await webMigrateToWebpack5Generator(tree, {});

const json = readJson(tree, '/package.json');

expect(json.devDependencies['webpack']).toMatch(/\^5/);
expect(
json.devDependencies['@pmmmwh/react-refresh-webpack-plugin']
).toMatch(/^0\.5/);
});
});
@@ -1,28 +1,43 @@
import type { Tree } from '@nrwl/devkit';
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import {
addDependenciesToPackageJson,
convertNxGenerator,
logger,
removeDependenciesFromPackageJson,
} from '@nrwl/devkit';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';

const webpack5Packages = {
'copy-webpack-plugin': '^9.0.0',
'mini-css-extract-plugin': '^1.6.0',
'source-map-loader': '^2.0.1',
const basePackages = {
'copy-webpack-plugin': '^9.0.1',
webpack: '^5.47.0',
'webpack-merge': '^5.8.0',
'webpack-node-externals': '^3.0.0',
};

const webPackages = {
'mini-css-extract-plugin': '^2.1.0',
'source-map-loader': '^3.0.0',
'terser-webpack-plugin': '^5.1.1',
webpack: '^5.39.1',
'webpack-dev-server': '^3.11.2',
'webpack-merge': '^5.7.3',
'webpack-node-externals': '^2.5.2',
'webpack-sources': '^2.2.0',
'webpack-dev-server': '4.0.0-rc.0',
'webpack-sources': '^3.0.2',
'react-refresh': '^0.10.0',
'@pmmmwh/react-refresh-webpack-plugin': '0.5.0-rc.2',
};

export async function webMigrateToWebpack5Generator(tree: Tree, schema: {}) {
const packages = { ...basePackages, ...webPackages };
const tasks: GeneratorCallback[] = [];

logger.info(`NX Adding webpack 5 to workspace.`);

// Removing the packages ensures that the versions will be updated when adding them after
removeDependenciesFromPackageJson(tree, [], Object.keys(webpack5Packages));
return addDependenciesToPackageJson(tree, {}, webpack5Packages);
tasks.push(
removeDependenciesFromPackageJson(tree, [], Object.keys(packages))
);

tasks.push(addDependenciesToPackageJson(tree, {}, packages));

return runTasksInSerial(...tasks);
}

export default webMigrateToWebpack5Generator;
Expand Down
2 changes: 2 additions & 0 deletions packages/web/src/utils/config.ts
Expand Up @@ -40,6 +40,8 @@ export function getBaseWebpackPartial(
const mode = isScriptOptimizeOn ? 'production' : 'development';

const webpackConfig: Configuration = {
target: 'web', // webpack defaults to 'browserslist' which breaks Fast Refresh

entry: {
main: [options.main],
},
Expand Down

0 comments on commit 70c0921

Please sign in to comment.