diff --git a/packages/react-dev-utils/FileSizeReporter.js b/packages/react-dev-utils/FileSizeReporter.js
index b2b4cc6904d..9e2d52f9b5e 100644
--- a/packages/react-dev-utils/FileSizeReporter.js
+++ b/packages/react-dev-utils/FileSizeReporter.js
@@ -17,9 +17,7 @@ var gzipSize = require('gzip-size').sync;
function canReadAsset(asset) {
return (
- /\.(js|css)$/.test(asset) &&
- !/service-worker\.js/.test(asset) &&
- !/precache-manifest\.[0-9a-f]+\.js/.test(asset)
+ /\.(js|css)$/.test(asset)
);
}
diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js
index 6a56f666aa1..499195e8d2c 100644
--- a/packages/react-dev-utils/WebpackDevServerUtils.js
+++ b/packages/react-dev-utils/WebpackDevServerUtils.js
@@ -113,9 +113,9 @@ function createCompiler({
}) {
// "Compiler" is a low-level interface to webpack.
// It lets us listen to some events and provide our own custom messages.
- let compiler;
+ let masterCompiler;
try {
- compiler = webpack(config);
+ masterCompiler = webpack(config);
} catch (err) {
console.log(chalk.red('Failed to compile.'));
console.log();
@@ -128,7 +128,7 @@ function createCompiler({
// recompiling a bundle. WebpackDevServer takes care to pause serving the
// bundle, so if you refresh, it'll wait instead of serving the old one.
// "invalid" is short for "bundle invalidated", it doesn't imply any errors.
- compiler.hooks.invalid.tap('invalid', () => {
+ masterCompiler.hooks.invalid.tap('invalid', () => {
if (isInteractive) {
clearConsole();
}
@@ -136,39 +136,52 @@ function createCompiler({
});
let isFirstCompile = true;
- let tsMessagesPromise;
- let tsMessagesResolver;
-
- if (useTypeScript) {
- compiler.hooks.beforeCompile.tap('beforeCompile', () => {
- tsMessagesPromise = new Promise(resolve => {
- tsMessagesResolver = msgs => resolve(msgs);
+ let tsMessagesPromises = [];
+
+ masterCompiler.compilers.forEach((compiler, compilerIndex) => {
+ let tsMessagesResolver;
+ if (useTypeScript) {
+ compiler.hooks.beforeCompile.tap('beforeCompile', () => {
+ tsMessagesPromises[compilerIndex] = new Promise(resolve => {
+ tsMessagesResolver = msgs => resolve(msgs);
+ });
});
- });
- forkTsCheckerWebpackPlugin
- .getCompilerHooks(compiler)
- .receive.tap('afterTypeScriptCheck', (diagnostics, lints) => {
- const allMsgs = [...diagnostics, ...lints];
- const format = message =>
- `${message.file}\n${typescriptFormatter(message, true)}`;
-
- tsMessagesResolver({
- errors: allMsgs.filter(msg => msg.severity === 'error').map(format),
- warnings: allMsgs
- .filter(msg => msg.severity === 'warning')
- .map(format),
+ forkTsCheckerWebpackPlugin
+ .getCompilerHooks(compiler)
+ .receive.tap('afterTypeScriptCheck', (diagnostics, lints) => {
+ const allMsgs = [...diagnostics, ...lints];
+ const format = message =>
+ `${message.file}\n${typescriptFormatter(message, true)}`;
+
+ tsMessagesResolver({
+ errors: allMsgs.filter(msg => msg.severity === 'error').map(format),
+ warnings: allMsgs
+ .filter(msg => msg.severity === 'warning')
+ .map(format),
+ });
});
- });
- }
+ }
+ });
// "done" event fires when webpack has finished recompiling the bundle.
// Whether or not you have warnings or errors, you will get this event.
- compiler.hooks.done.tap('done', async stats => {
+ masterCompiler.hooks.done.tap('done', async stats => {
if (isInteractive) {
clearConsole();
}
+ stats.compilation = {
+ errors: stats.stats.reduce(
+ (previousErrors, s) => [...previousErrors, ...s.compilation.errors],
+ []
+ ),
+ warnings: stats.stats.reduce(
+ (previousErrors, s) => [...previousErrors, ...s.compilation.warnings],
+ []
+ ),
+ };
+
// We have switched off the default webpack output in WebpackDevServer
// options so we are going to "massage" the warnings and errors and present
// them in a readable focused way.
@@ -189,7 +202,15 @@ function createCompiler({
);
}, 100);
- const messages = await tsMessagesPromise;
+ const masterMessages = await Promise.all(tsMessagesPromises);
+ const messages = masterMessages.reduce(
+ (previousMessages, currentMessages) => ({
+ errors: [...previousMessages.errors, ...currentMessages.errors],
+ warnings: [...previousMessages.warnings, ...currentMessages.warnings],
+ }),
+ { errors: [], warnings: [] }
+ );
+
clearTimeout(delayedMsg);
if (tscCompileOnError) {
statsData.warnings.push(...messages.errors);
@@ -269,12 +290,12 @@ function createCompiler({
arg => arg.indexOf('--smoke-test') > -1
);
if (isSmokeTest) {
- compiler.hooks.failed.tap('smokeTest', async () => {
- await tsMessagesPromise;
+ masterCompiler.hooks.failed.tap('smokeTest', async () => {
+ await Promise.all(tsMessagesPromises);
process.exit(1);
});
- compiler.hooks.done.tap('smokeTest', async stats => {
- await tsMessagesPromise;
+ masterCompiler.hooks.done.tap('smokeTest', async stats => {
+ await Promise.all(tsMessagesPromises);
if (stats.hasErrors() || stats.hasWarnings()) {
process.exit(1);
} else {
@@ -283,7 +304,7 @@ function createCompiler({
});
}
- return compiler;
+ return masterCompiler;
}
function resolveLoopback(proxy) {
diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json
index 77a41ebe588..93f3e42613e 100644
--- a/packages/react-dev-utils/package.json
+++ b/packages/react-dev-utils/package.json
@@ -1,6 +1,6 @@
{
- "name": "react-dev-utils",
- "version": "11.0.3",
+ "name": "@ouihelp/react-dev-utils",
+ "version": "11000003.0.2",
"description": "webpack utilities used by Create React App",
"repository": {
"type": "git",
diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js
index 67ba927fc80..e805c41953d 100644
--- a/packages/react-scripts/config/paths.js
+++ b/packages/react-scripts/config/paths.js
@@ -66,6 +66,7 @@ module.exports = {
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
+ appServiceWorkerJs: resolveModule(resolveApp, 'src/service-worker.entry'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
@@ -89,6 +90,7 @@ module.exports = {
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
+ appServiceWorkerJs: resolveModule(resolveApp, 'src/service-worker.entry'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
@@ -125,6 +127,10 @@ if (
appPublic: resolveOwn(`${templatePath}/public`),
appHtml: resolveOwn(`${templatePath}/public/index.html`),
appIndexJs: resolveModule(resolveOwn, `${templatePath}/src/index`),
+ appServiceWorkerJs: resolveModule(
+ resolveOwn,
+ `${templatePath}/src/service-worker.entry`
+ ),
appPackageJson: resolveOwn('package.json'),
appSrc: resolveOwn(`${templatePath}/src`),
appTsConfig: resolveOwn(`${templatePath}/tsconfig.json`),
diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js
index 26c2a65aad0..13605308a44 100644
--- a/packages/react-scripts/config/webpack.config.js
+++ b/packages/react-scripts/config/webpack.config.js
@@ -22,7 +22,6 @@ const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
-const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
@@ -51,6 +50,10 @@ const reactRefreshOverlayEntry = require.resolve(
'react-dev-utils/refreshOverlayInterop'
);
+// These two requirements are for our custom `InjectMainEntrypointManifestPlugin`.
+const { RawSource } = require('webpack-sources');
+const replaceAndUpdateSourceMap = require('workbox-build/build/lib/replace-and-update-source-map');
+
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
@@ -87,6 +90,44 @@ const hasJsxRuntime = (() => {
}
})();
+
+// Custom Webpack plugin to stamp the main entrypoint list of files
+// into our service worker.
+// Inspired by workbox's `InjectManifestPlugin`.
+let mainEntrypointFilesPromiseResolver;
+const mainEntrypointFilesPromise = new Promise((resolve) => {mainEntrypointFilesPromiseResolver = resolve});
+
+class InjectMainEntrypointManifestPlugin {
+ apply(compiler) {
+ compiler.hooks.emit.tapPromise(
+ this.constructor.name,
+ (compilation) => this.handleEmit(compilation).catch(
+ (error) => compilation.errors.push(error)),
+ );
+ }
+
+ async handleEmit(compilation) {
+ const mainEntryPointFiles = await mainEntrypointFilesPromise;
+
+ const swFileName = "sw.js";
+ const swSrcmapFileName = "sw.js.map";
+
+ const swAsset = compilation.assets[swFileName];
+ const initialSWAssetString = swAsset.source();
+ const sourcemapAsset = compilation.assets[swSrcmapFileName];
+ const {source, map} = await replaceAndUpdateSourceMap({
+ jsFilename: swFileName,
+ originalMap: JSON.parse(sourcemapAsset.source()),
+ originalSource: initialSWAssetString,
+ replaceString: JSON.stringify(Object.values(mainEntryPointFiles)),
+ searchString: "__MAIN_ENTRYPOINT_FILES",
+ });
+
+ compilation.assets[swSrcmapFileName] = new RawSource(map);
+ compilation.assets[swFileName] = new RawSource(source);
+ }
+}
+
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function (webpackEnv) {
@@ -168,7 +209,7 @@ module.exports = function (webpackEnv) {
return loaders;
};
- return {
+ const originalConfig = {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
@@ -359,6 +400,7 @@ module.exports = function (webpackEnv) {
reactRefreshOverlayEntry,
]),
],
+ symlinks: false,
},
resolveLoader: {
plugins: [
@@ -697,6 +739,9 @@ module.exports = function (webpackEnv) {
fileName => !fileName.endsWith('.map')
);
+ // Give all files to our resolver.
+ mainEntrypointFilesPromiseResolver(manifestFiles);
+
return {
files: manifestFiles,
entrypoints: entrypointFiles,
@@ -709,19 +754,6 @@ module.exports = function (webpackEnv) {
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
- // Generate a service worker script that will precache, and keep up to date,
- // the HTML & assets that are part of the webpack build.
- isEnvProduction &&
- fs.existsSync(swSrc) &&
- new WorkboxWebpackPlugin.InjectManifest({
- swSrc,
- dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./,
- exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/],
- // Bump up the default maximum size (2mb) that's precached,
- // to make lazy-loading failure scenarios less likely.
- // See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
- maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
- }),
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
@@ -795,4 +827,60 @@ module.exports = function (webpackEnv) {
// our own hints via the FileSizeReporter
performance: false,
};
+
+ const serviceWorkerConfig = {
+ target: 'webworker',
+ mode: originalConfig.mode,
+ bail: originalConfig.bail,
+ devtool: originalConfig.devtool,
+ entry: { 'service-worker': paths.appServiceWorkerJs },
+ output: {
+ path: originalConfig.output.path,
+ pathinfo: originalConfig.output.pathinfo,
+ filename: 'sw.js',
+ publicPath: originalConfig.output.publicPath,
+ devtoolModuleFilenameTemplate:
+ originalConfig.output.devtoolModuleFilenameTemplate,
+ },
+ optimization: {
+ minimize: originalConfig.optimization.minimize,
+ minimizer: originalConfig.optimization.minimizer,
+ },
+ resolve: originalConfig.resolve,
+ resolveLoader: originalConfig.resolveLoader,
+ module: originalConfig.module,
+ plugins: [
+ new webpack.DefinePlugin(env.stringified),
+ useTypeScript &&
+ new ForkTsCheckerWebpackPlugin({
+ typescript: resolve.sync('typescript', {
+ basedir: paths.appNodeModules,
+ }),
+ async: isEnvDevelopment,
+ useTypescriptIncrementalApi: true,
+ checkSyntacticErrors: true,
+ resolveModuleNameModule: process.versions.pnp
+ ? `${__dirname}/pnpTs.js`
+ : undefined,
+ resolveTypeReferenceDirectiveModule: process.versions.pnp
+ ? `${__dirname}/pnpTs.js`
+ : undefined,
+ tsconfig: paths.appTsConfig,
+ reportFiles: [
+ '**',
+ '!**/__tests__/**',
+ '!**/?(*.)(spec|test).*',
+ '!**/src/setupProxy.*',
+ '!**/src/setupTests.*',
+ ],
+ watch: paths.appSrc,
+ silent: true,
+ // The formatter is invoked directly in WebpackDevServerUtils during development
+ formatter: isEnvProduction ? typescriptFormatter : undefined,
+ }),
+ new InjectMainEntrypointManifestPlugin(),
+ ].filter(Boolean),
+ };
+
+ return [originalConfig, serviceWorkerConfig];
};
diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json
index ffae0fa304b..ad4fd85e683 100644
--- a/packages/react-scripts/package.json
+++ b/packages/react-scripts/package.json
@@ -1,6 +1,6 @@
{
- "name": "react-scripts",
- "version": "4.0.3",
+ "name": "@ouihelp/react-scripts",
+ "version": "4000003.0.4",
"description": "Configuration and scripts for Create React App.",
"repository": {
"type": "git",
@@ -29,6 +29,7 @@
"types": "./lib/react-app.d.ts",
"dependencies": {
"@babel/core": "7.12.3",
+ "@ouihelp/react-dev-utils": "11000003.0.2",
"@pmmmwh/react-refresh-webpack-plugin": "0.4.3",
"@svgr/webpack": "5.5.0",
"@typescript-eslint/eslint-plugin": "^4.5.0",
@@ -85,7 +86,7 @@
"webpack": "4.44.2",
"webpack-dev-server": "3.11.1",
"webpack-manifest-plugin": "2.2.0",
- "workbox-webpack-plugin": "5.1.4"
+ "workbox-build": "5.1.4"
},
"devDependencies": {
"react": "^17.0.1",
diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js
index de2ff505eb3..8bdde3054d7 100644
--- a/packages/react-scripts/scripts/build.js
+++ b/packages/react-scripts/scripts/build.js
@@ -41,7 +41,7 @@ const paths = require('../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
-const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
+const FileSizeReporter = require('@ouihelp/react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');
const measureFileSizesBeforeBuild =
@@ -115,7 +115,7 @@ checkBrowsers(paths.appPath, isInteractive)
const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrlOrPath;
- const publicPath = config.output.publicPath;
+ const publicPath = config[0].output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild);
printHostingInstructions(
appPackage,
diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js
index ffbb15d1204..8f2da8f1188 100644
--- a/packages/react-scripts/scripts/start.js
+++ b/packages/react-scripts/scripts/start.js
@@ -42,7 +42,7 @@ const {
createCompiler,
prepareProxy,
prepareUrls,
-} = require('react-dev-utils/WebpackDevServerUtils');
+} = require('@ouihelp/react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const semver = require('semver');
const paths = require('../config/paths');
diff --git a/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js b/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js
index 949f34ab7d2..3b6cc1c995a 100644
--- a/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js
+++ b/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js
@@ -218,7 +218,7 @@ function verifyTypeScriptSetup() {
if (appTsConfig.compilerOptions == null) {
appTsConfig.compilerOptions = {};
firstTimeSetup = true;
- }
+ }
for (const option of Object.keys(compilerOptions)) {
const { parsedValue, value, suggested, reason } = compilerOptions[option];
@@ -290,7 +290,7 @@ function verifyTypeScriptSetup() {
if (!fs.existsSync(paths.appTypeDeclarations)) {
fs.writeFileSync(
paths.appTypeDeclarations,
- `/// ${os.EOL}`
+ `/// ${os.EOL}`
);
}
}