feat(react): add react fast refresh to webpack (#5612)

This commit is contained in:
Emily Xiong 2021-05-18 15:47:18 -04:00 committed by GitHub
parent 4a0dcbab40
commit b8584a6f18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 133 additions and 20 deletions

View File

@ -26,6 +26,14 @@ Type: `string`
Target which builds the application Target which builds the application
### hmr
Default: `false`
Type: `boolean`
Enable hot module replacement.
### host ### host
Default: `localhost` Default: `localhost`

View File

@ -34,6 +34,10 @@ The options below are common to the `serve` command used within an Nx workspace.
This option allows you to whitelist services that are allowed to access the dev server. This option allows you to whitelist services that are allowed to access the dev server.
### hmr
Enable hot module replacement.
### host ### host
Host to listen on. Host to listen on.
@ -140,10 +144,6 @@ Don't verify connected clients are part of allowed hosts.
Output in-file eval sourcemaps. Output in-file eval sourcemaps.
### hmr
Enable hot module replacement.
### hmr-warning ### hmr-warning
Show a warning when the `--hmr` option is enabled. Show a warning when the `--hmr` option is enabled.

View File

@ -27,6 +27,14 @@ Type: `string`
Target which builds the application Target which builds the application
### hmr
Default: `false`
Type: `boolean`
Enable hot module replacement.
### host ### host
Default: `localhost` Default: `localhost`

View File

@ -34,6 +34,10 @@ The options below are common to the `serve` command used within an Nx workspace.
This option allows you to whitelist services that are allowed to access the dev server. This option allows you to whitelist services that are allowed to access the dev server.
### hmr
Enable hot module replacement.
### host ### host
Host to listen on. Host to listen on.
@ -140,10 +144,6 @@ Don't verify connected clients are part of allowed hosts.
Output in-file eval sourcemaps. Output in-file eval sourcemaps.
### hmr
Enable hot module replacement.
### hmr-warning ### hmr-warning
Show a warning when the `--hmr` option is enabled. Show a warning when the `--hmr` option is enabled.

View File

@ -27,6 +27,14 @@ Type: `string`
Target which builds the application Target which builds the application
### hmr
Default: `false`
Type: `boolean`
Enable hot module replacement.
### host ### host
Default: `localhost` Default: `localhost`

View File

@ -34,6 +34,10 @@ The options below are common to the `serve` command used within an Nx workspace.
This option allows you to whitelist services that are allowed to access the dev server. This option allows you to whitelist services that are allowed to access the dev server.
### hmr
Enable hot module replacement.
### host ### host
Host to listen on. Host to listen on.
@ -140,10 +144,6 @@ Don't verify connected clients are part of allowed hosts.
Output in-file eval sourcemaps. Output in-file eval sourcemaps.
### hmr
Enable hot module replacement.
### hmr-warning ### hmr-warning
Show a warning when the `--hmr` option is enabled. Show a warning when the `--hmr` option is enabled.

View File

@ -64,6 +64,7 @@ Options:
--sslCert SSL certificate to use for serving HTTPS. --sslCert SSL certificate to use for serving HTTPS.
--watch Watches for changes and rebuilds application (default: true) --watch Watches for changes and rebuilds application (default: true)
--liveReload Whether to reload the page on change, using live-reload. (default: true) --liveReload Whether to reload the page on change, using live-reload. (default: true)
--hmr Enable hot module replacement.
--publicHost Public URL where the application will be served --publicHost Public URL where the application will be served
--open Open the application in the browser. --open Open the application in the browser.
--allowedHosts This option allows you to whitelist services that are allowed to access the dev server. --allowedHosts This option allows you to whitelist services that are allowed to access the dev server.

View File

@ -34,6 +34,10 @@ The options below are common to the `serve` command used within an Nx workspace.
This option allows you to whitelist services that are allowed to access the dev server. This option allows you to whitelist services that are allowed to access the dev server.
### hmr
Enable hot module replacement.
### host ### host
Host to listen on. Host to listen on.
@ -140,10 +144,6 @@ Don't verify connected clients are part of allowed hosts.
Output in-file eval sourcemaps. Output in-file eval sourcemaps.
### hmr
Enable hot module replacement.
### hmr-warning ### hmr-warning
Show a warning when the `--hmr` option is enabled. Show a warning when the `--hmr` option is enabled.

View File

@ -68,6 +68,7 @@
"@nrwl/tao": "12.3.0", "@nrwl/tao": "12.3.0",
"@nrwl/web": "12.3.0", "@nrwl/web": "12.3.0",
"@nrwl/workspace": "12.3.0", "@nrwl/workspace": "12.3.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@popperjs/core": "^2.9.2", "@popperjs/core": "^2.9.2",
"@reduxjs/toolkit": "1.5.0", "@reduxjs/toolkit": "1.5.0",
"@rollup/plugin-babel": "5.0.2", "@rollup/plugin-babel": "5.0.2",
@ -189,6 +190,7 @@
"protractor": "5.4.3", "protractor": "5.4.3",
"raw-loader": "3.1.0", "raw-loader": "3.1.0",
"react-redux": "7.2.3", "react-redux": "7.2.3",
"react-refresh": "^0.9.0",
"react-router-dom": "5.1.2", "react-router-dom": "5.1.2",
"regenerator-runtime": "0.13.7", "regenerator-runtime": "0.13.7",
"release-it": "^7.4.0", "release-it": "^7.4.0",

View File

@ -37,11 +37,13 @@
"@nrwl/storybook": "*", "@nrwl/storybook": "*",
"@nrwl/web": "*", "@nrwl/web": "*",
"@nrwl/workspace": "*", "@nrwl/workspace": "*",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@svgr/webpack": "^5.4.0", "@svgr/webpack": "^5.4.0",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.23.1", "eslint-plugin-react": "^7.23.1",
"eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-hooks": "^4.2.0",
"react-refresh": "^0.9.0",
"url-loader": "^3.0.0" "url-loader": "^3.0.0"
} }
} }

View File

@ -1,4 +1,5 @@
import { Configuration } from 'webpack'; import { Configuration } from 'webpack';
import * as ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin';
// Add React-specific configuration // Add React-specific configuration
function getWebpackConfig(config: Configuration) { function getWebpackConfig(config: Configuration) {
@ -54,6 +55,27 @@ function getWebpackConfig(config: Configuration) {
} }
); );
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')
);
if (babelLoader) {
babelLoader.options['plugins'] = [
...(babelLoader.options['plugins'] || []),
[
require.resolve('react-refresh/babel'),
{
skipEnvCheck: true,
},
],
];
}
// add https://github.com/pmmmwh/react-refresh-webpack-plugin to webpack plugin
config.plugins.push(new ReactRefreshPlugin());
}
return config; return config;
} }

View File

@ -278,9 +278,11 @@ describe('app', () => {
expect(targetConfig.serve.executor).toEqual('@nrwl/web:dev-server'); expect(targetConfig.serve.executor).toEqual('@nrwl/web:dev-server');
expect(targetConfig.serve.options).toEqual({ expect(targetConfig.serve.options).toEqual({
buildTarget: 'my-app:build', buildTarget: 'my-app:build',
hmr: true,
}); });
expect(targetConfig.serve.configurations.production).toEqual({ expect(targetConfig.serve.configurations.production).toEqual({
buildTarget: 'my-app:build:production', buildTarget: 'my-app:build:production',
hmr: false,
}); });
}); });

View File

@ -112,10 +112,12 @@ function createServeTarget(options: NormalizedSchema): TargetConfiguration {
executor: '@nrwl/web:dev-server', executor: '@nrwl/web:dev-server',
options: { options: {
buildTarget: `${options.projectName}:build`, buildTarget: `${options.projectName}:build`,
hmr: true,
}, },
configurations: { configurations: {
production: { production: {
buildTarget: `${options.projectName}:build:production`, buildTarget: `${options.projectName}:build:production`,
hmr: false,
}, },
}, },
}; };

View File

@ -35,6 +35,7 @@ export interface WebDevServerOptions {
buildTarget: string; buildTarget: string;
open: boolean; open: boolean;
liveReload: boolean; liveReload: boolean;
hmr: boolean;
watch: boolean; watch: boolean;
allowedHosts: string; allowedHosts: string;
maxWorkers?: number; maxWorkers?: number;

View File

@ -41,6 +41,11 @@
"description": "Whether to reload the page on change, using live-reload.", "description": "Whether to reload the page on change, using live-reload.",
"default": true "default": true
}, },
"hmr": {
"type": "boolean",
"description": "Enable hot module replacement.",
"default": false
},
"publicHost": { "publicHost": {
"type": "string", "type": "string",
"description": "Public URL where the application will be served" "description": "Public URL where the application will be served"

View File

@ -2,6 +2,7 @@ import { getDevServerConfig } from './devserver.config';
import TsConfigPathsPlugin from 'tsconfig-paths-webpack-plugin'; import TsConfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import * as ts from 'typescript'; import * as ts from 'typescript';
import * as fs from 'fs'; import * as fs from 'fs';
import { HotModuleReplacementPlugin } from 'webpack';
import { WebBuildBuilderOptions } from '../builders/build/build.impl'; import { WebBuildBuilderOptions } from '../builders/build/build.impl';
import { WebDevServerOptions } from '../builders/dev-server/dev-server.impl'; import { WebDevServerOptions } from '../builders/dev-server/dev-server.impl';
import { join } from 'path'; import { join } from 'path';
@ -54,6 +55,7 @@ describe('getDevServerConfig', () => {
buildTarget: 'webapp:build', buildTarget: 'webapp:build',
ssl: false, ssl: false,
liveReload: true, liveReload: true,
hmr: true,
open: false, open: false,
watch: true, watch: true,
allowedHosts: null, allowedHosts: null,
@ -328,7 +330,7 @@ describe('getDevServerConfig', () => {
root, root,
sourceRoot, sourceRoot,
buildInput, buildInput,
serveInput { ...serveInput, hmr: false }
); );
expect(result.liveReload).toEqual(true); expect(result.liveReload).toEqual(true);
@ -339,13 +341,49 @@ describe('getDevServerConfig', () => {
root, root,
sourceRoot, sourceRoot,
buildInput, buildInput,
{ ...serveInput, liveReload: false } { ...serveInput, hmr: false, liveReload: false }
); );
expect(result.liveReload).toEqual(false); expect(result.liveReload).toEqual(false);
}); });
}); });
describe('hmr option', () => {
it('should set the correct value', () => {
const { devServer: result } = getDevServerConfig(
root,
sourceRoot,
buildInput,
{ ...serveInput, hmr: false }
);
expect(result.hot).toEqual(false);
});
it('should set the correct if true and disable live reload', () => {
const { devServer: result } = getDevServerConfig(
root,
sourceRoot,
buildInput,
serveInput
);
expect(result.liveReload).toEqual(false);
expect(result.hot).toEqual(true);
});
it('should add hot module replacement plugin', () => {
const { plugins } = getDevServerConfig(
root,
sourceRoot,
buildInput,
serveInput
);
expect(plugins).toContainEqual(new HotModuleReplacementPlugin());
});
});
describe('ssl option', () => { describe('ssl option', () => {
it('should set https to false if not on', () => { it('should set https to false if not on', () => {
const { devServer: result } = getDevServerConfig( const { devServer: result } = getDevServerConfig(

View File

@ -7,7 +7,7 @@ import { readFileSync } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { getWebConfig } from './web.config'; import { getWebConfig } from './web.config';
import { Configuration } from 'webpack'; import { Configuration, HotModuleReplacementPlugin } from 'webpack';
import { WebBuildBuilderOptions } from '../builders/build/build.impl'; import { WebBuildBuilderOptions } from '../builders/build/build.impl';
import { WebDevServerOptions } from '../builders/dev-server/dev-server.impl'; import { WebDevServerOptions } from '../builders/dev-server/dev-server.impl';
import { buildServePath } from './serve-path'; import { buildServePath } from './serve-path';
@ -31,6 +31,10 @@ export function getDevServerConfig(
serveOptions, serveOptions,
buildOptions buildOptions
); );
webpackConfig.plugins = [
...(webpackConfig.plugins || []),
getHmrPlugin(serveOptions),
].filter(Boolean);
return webpackConfig; return webpackConfig;
} }
@ -85,7 +89,8 @@ function getDevServerPartial(
publicPath: servePath, publicPath: servePath,
contentBase: false, contentBase: false,
allowedHosts: [], allowedHosts: [],
liveReload: options.liveReload, liveReload: options.hmr ? false : options.liveReload, // disable liveReload if hmr is enabled
hot: options.hmr,
}; };
if (options.ssl && options.sslKey && options.sslCert) { if (options.ssl && options.sslKey && options.sslCert) {
@ -114,3 +119,7 @@ function getProxyConfig(root: string, options: WebDevServerOptions) {
const proxyPath = path.resolve(root, options.proxyConfig as string); const proxyPath = path.resolve(root, options.proxyConfig as string);
return require(proxyPath); return require(proxyPath);
} }
function getHmrPlugin(options: WebDevServerOptions) {
return options.hmr && new HotModuleReplacementPlugin();
}

View File

@ -20519,6 +20519,11 @@ react-refresh@0.8.3, react-refresh@^0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
react-refresh@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf"
integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==
react-router-dom@5.1.2: react-router-dom@5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18"