From 0a0844f62039991017873ebc451976483e6316ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20N=2EO=2E=20N=C3=B8rgaard=20Henriksen?= Date: Wed, 4 Aug 2021 19:24:41 +0200 Subject: [PATCH] Webpack 5 (#11201) Co-authored-by: Ian Schmitz Co-authored-by: jasonwilliams Co-authored-by: Joseph Atkins-Turkish Co-authored-by: e-w-h <46170930+e-w-h@users.noreply.github.com> Co-authored-by: Shamprasad RH Co-authored-by: James George --- config/paths.js | 6 + config/pnpTs.js | 43 --- config/webpack.config.js | 310 ++++++++---------- .../persistentCache/createEnvironmentHash.js | 9 + config/webpackDevServer.config.js | 117 +++---- fixtures/kitchensink/template.json | 2 +- .../template/integration/webpack.test.js | 9 +- .../src/features/webpack/JsonInclusion.js | 4 +- package.json | 74 ++--- scripts/eject.js | 4 +- scripts/start.js | 9 - scripts/utils/verifyPackageTree.js | 2 - scripts/utils/verifyTypeScriptSetup.js | 2 +- 13 files changed, 243 insertions(+), 348 deletions(-) delete mode 100644 config/pnpTs.js create mode 100644 config/webpack/persistentCache/createEnvironmentHash.js diff --git a/config/paths.js b/config/paths.js index 67ba927fc80..f4470a02f63 100644 --- a/config/paths.js +++ b/config/paths.js @@ -74,6 +74,8 @@ module.exports = { testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), + appWebpackCache: resolveApp('node_modules/.cache'), + appTsBuildInfoFile: resolveApp('node_modules/.cache/tsconfig.tsbuildinfo'), swSrc: resolveModule(resolveApp, 'src/service-worker'), publicUrlOrPath, }; @@ -97,6 +99,8 @@ module.exports = { testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), + appWebpackCache: resolveApp('node_modules/.cache'), + appTsBuildInfoFile: resolveApp('node_modules/.cache/tsconfig.tsbuildinfo'), swSrc: resolveModule(resolveApp, 'src/service-worker'), publicUrlOrPath, // These properties only exist before ejecting: @@ -133,6 +137,8 @@ if ( testsSetup: resolveModule(resolveOwn, `${templatePath}/src/setupTests`), proxySetup: resolveOwn(`${templatePath}/src/setupProxy.js`), appNodeModules: resolveOwn('node_modules'), + appWebpackCache: resolveOwn('node_modules/.cache'), + appTsBuildInfoFile: resolveOwn('node_modules/.cache/tsconfig.tsbuildinfo'), swSrc: resolveModule(resolveOwn, `${templatePath}/src/service-worker`), publicUrlOrPath, // These properties only exist before ejecting: diff --git a/config/pnpTs.js b/config/pnpTs.js deleted file mode 100644 index ed810df6307..00000000000 --- a/config/pnpTs.js +++ /dev/null @@ -1,43 +0,0 @@ -// @remove-on-eject-begin -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -// @remove-on-eject-end -'use strict'; - -const { resolveModuleName } = require('ts-pnp'); - -exports.resolveModuleName = ( - typescript, - moduleName, - containingFile, - compilerOptions, - resolutionHost -) => { - return resolveModuleName( - moduleName, - containingFile, - compilerOptions, - resolutionHost, - typescript.resolveModuleName - ); -}; - -exports.resolveTypeReferenceDirective = ( - typescript, - moduleName, - containingFile, - compilerOptions, - resolutionHost -) => { - return resolveModuleName( - moduleName, - containingFile, - compilerOptions, - resolutionHost, - typescript.resolveTypeReferenceDirective - ); -}; diff --git a/config/webpack.config.js b/config/webpack.config.js index b0f9d3763aa..6bc8b2cee11 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -12,18 +12,15 @@ const fs = require('fs'); const path = require('path'); const webpack = require('webpack'); const resolve = require('resolve'); -const PnpWebpackPlugin = require('pnp-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin'); const TerserPlugin = require('terser-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); -const safePostCssParser = require('postcss-safe-parser'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const { WebpackManifestPlugin } = 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'); const ESLintPlugin = require('eslint-webpack-plugin'); @@ -31,25 +28,28 @@ const paths = require('./paths'); const modules = require('./modules'); const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); -const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin'); -const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); +const ForkTsCheckerWebpackPlugin = + process.env.TSC_COMPILE_ON_ERROR === 'true' + ? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin') + : require('react-dev-utils/ForkTsCheckerWebpackPlugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // @remove-on-eject-begin const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); // @remove-on-eject-end -const postcssNormalize = require('postcss-normalize'); - -const appPackageJson = require(paths.appPackageJson); +const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash'); // Source maps are resource heavy and can cause out of memory issue for large source files. const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; -const webpackDevClientEntry = require.resolve( - 'react-dev-utils/webpackHotDevClient' +const reactRefreshRuntimeEntry = require.resolve('react-refresh/runtime'); +const reactRefreshWebpackPluginRuntimeEntry = require.resolve( + '@pmmmwh/react-refresh-webpack-plugin' ); -const reactRefreshOverlayEntry = require.resolve( - 'react-dev-utils/refreshOverlayInterop' +const babelRuntimeEntry = require.resolve('babel-preset-react-app'); +const babelRuntimeEntryHelpers = require.resolve( + '@babel/runtime/helpers/esm/assertThisInitialized' ); +const babelRuntimeRegenerator = require.resolve('@babel/runtime/regenerator'); // Some apps do not need the benefits of saving a web request, so not inlining the chunk // makes for a smoother build process. @@ -129,10 +129,13 @@ module.exports = function (webpackEnv) { loader: require.resolve('postcss-loader'), options: { postcssOptions: { + // Necessary for external CSS imports to work + // https://github.com/facebook/create-react-app/issues/2677 + ident: 'postcss', plugins: [ - require('postcss-flexbugs-fixes'), + 'postcss-flexbugs-fixes', [ - require('postcss-preset-env'), + 'postcss-preset-env', { autoprefixer: { flexbox: 'no-2009', @@ -143,10 +146,10 @@ module.exports = function (webpackEnv) { // Adds PostCSS Normalize as the reset css with default options, // so that it honors browserslist config in package.json // which in turn let's users customize the target behavior as per their needs. - postcssNormalize(), + 'postcss-normalize', ], }, - sourceMap: isEnvProduction && shouldUseSourceMap, + sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, }, }, ].filter(Boolean); @@ -171,6 +174,7 @@ module.exports = function (webpackEnv) { }; return { + target: ['browserslist'], mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', // Stop compilation early in production bail: isEnvProduction, @@ -181,34 +185,10 @@ module.exports = function (webpackEnv) { : isEnvDevelopment && 'cheap-module-source-map', // These are the "entry points" to our application. // This means they will be the "root" imports that are included in JS bundle. - entry: - isEnvDevelopment && !shouldUseReactRefresh - ? [ - // Include an alternative client for WebpackDevServer. A client's job is to - // connect to WebpackDevServer by a socket and get notified about changes. - // When you save a file, the client will either apply hot updates (in case - // of CSS changes), or refresh the page (in case of JS changes). When you - // make a syntax error, this client will display a syntax error overlay. - // Note: instead of the default WebpackDevServer client, we use a custom one - // to bring better experience for Create React App users. You can replace - // the line below with these two lines if you prefer the stock client: - // - // require.resolve('webpack-dev-server/client') + '?/', - // require.resolve('webpack/hot/dev-server'), - // - // When using the experimental react-refresh integration, - // the webpack plugin takes care of injecting the dev client for us. - webpackDevClientEntry, - // Finally, this is your app's code: - paths.appIndexJs, - // We include the app code last so that if there is a runtime error during - // initialization, it doesn't blow up the WebpackDevServer client, and - // changing JS code would still trigger a refresh. - ] - : paths.appIndexJs, + entry: paths.appIndexJs, output: { // The build folder. - path: isEnvProduction ? paths.appBuild : undefined, + path: paths.appBuild, // Add /* filename */ comments to generated require()s in the output. pathinfo: isEnvDevelopment, // There will be one main bundle, and one file per asynchronous chunk. @@ -216,12 +196,11 @@ module.exports = function (webpackEnv) { filename: isEnvProduction ? 'static/js/[name].[contenthash:8].js' : isEnvDevelopment && 'static/js/bundle.js', - // TODO: remove this when upgrading to webpack 5 - futureEmitAssets: true, // There are also additional JS chunk files if you use code splitting. chunkFilename: isEnvProduction ? 'static/js/[name].[contenthash:8].chunk.js' : isEnvDevelopment && 'static/js/[name].chunk.js', + assetModuleFilename: 'static/media/[name].[hash][ext]', // webpack uses `publicPath` to determine where the app is being served from. // It requires a trailing slash, or the file assets will get an incorrect path. // We inferred the "public path" (such as / or /my-project) from homepage. @@ -234,12 +213,22 @@ module.exports = function (webpackEnv) { .replace(/\\/g, '/') : isEnvDevelopment && (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')), - // Prevents conflicts when multiple webpack runtimes (from different apps) - // are used on the same page. - jsonpFunction: `webpackJsonp${appPackageJson.name}`, - // this defaults to 'window', but by setting it to 'this' then - // module chunks which are built will work in web workers as well. - globalObject: 'this', + }, + cache: { + type: 'filesystem', + version: createEnvironmentHash(env.raw), + cacheDirectory: paths.appWebpackCache, + store: 'pack', + buildDependencies: { + defaultWebpack: ['webpack/lib/'], + config: [__filename], + tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f => + fs.existsSync(f) + ), + }, + }, + infrastructureLogging: { + level: 'none', }, optimization: { minimize: isEnvProduction, @@ -283,41 +272,10 @@ module.exports = function (webpackEnv) { ascii_only: true, }, }, - sourceMap: shouldUseSourceMap, }), // This is only used in production mode - new OptimizeCSSAssetsPlugin({ - cssProcessorOptions: { - parser: safePostCssParser, - map: shouldUseSourceMap - ? { - // `inline: false` forces the sourcemap to be output into a - // separate file - inline: false, - // `annotation: true` appends the sourceMappingURL to the end of - // the css file, helping the browser find the sourcemap - annotation: true, - } - : false, - }, - cssProcessorPluginOptions: { - preset: ['default', { minifyFontValues: { removeQuotes: false } }], - }, - }), + new CssMinimizerPlugin(), ], - // Automatically split vendor and commons - // https://twitter.com/wSokra/status/969633336732905474 - // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 - splitChunks: { - chunks: 'all', - name: isEnvDevelopment, - }, - // Keep the runtime chunk separated to enable long term caching - // https://twitter.com/wSokra/status/969679223278505985 - // https://github.com/facebook/create-react-app/issues/5358 - runtimeChunk: { - name: entrypoint => `runtime-${entrypoint.name}`, - }, }, resolve: { // This allows you to set a fallback for where webpack should look for modules. @@ -348,9 +306,6 @@ module.exports = function (webpackEnv) { ...(modules.webpackAliases || {}), }, plugins: [ - // Adds support for installing with Plug'n'Play, leading to faster installs and adding - // guards against forgotten dependencies and such. - PnpWebpackPlugin, // Prevents users from importing files from outside of src/ (or node_modules/). // This often causes confusion because we only process files within src/ with babel. // To fix this, we prevent you from importing files out of src/ -- if you'd like to, @@ -358,22 +313,17 @@ module.exports = function (webpackEnv) { // Make sure your source files are compiled, as they will not be processed in any way. new ModuleScopePlugin(paths.appSrc, [ paths.appPackageJson, - reactRefreshOverlayEntry, + reactRefreshRuntimeEntry, + reactRefreshWebpackPluginRuntimeEntry, + babelRuntimeEntry, + babelRuntimeEntryHelpers, + babelRuntimeRegenerator, ]), ], }, - resolveLoader: { - plugins: [ - // Also related to Plug'n'Play, but this time it tells webpack to load its loaders - // from the current package. - PnpWebpackPlugin.moduleLoader(module), - ], - }, module: { strictExportPresence: true, rules: [ - // Disable require.ensure as it's not a standard language feature. - { parser: { requireEnsure: false } }, // Handle node_modules packages that contain sourcemaps shouldUseSourceMap && { enforce: 'pre', @@ -390,11 +340,12 @@ module.exports = function (webpackEnv) { // https://github.com/jshttp/mime-db { test: [/\.avif$/], - loader: require.resolve('url-loader'), - options: { - limit: imageInlineSizeLimit, - mimetype: 'image/avif', - name: 'static/media/[name].[hash:8].[ext]', + type: 'asset', + mimetype: 'image/avif', + parser: { + dataUrlCondition: { + maxSize: imageInlineSizeLimit, + }, }, }, // "url" loader works like "file" loader except that it embeds assets @@ -402,10 +353,37 @@ module.exports = function (webpackEnv) { // A missing `test` is equivalent to a match. { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], - loader: require.resolve('url-loader'), - options: { - limit: imageInlineSizeLimit, - name: 'static/media/[name].[hash:8].[ext]', + type: 'asset', + parser: { + dataUrlCondition: { + maxSize: imageInlineSizeLimit, + }, + }, + }, + { + test: /\.svg$/, + use: [ + { + loader: '@svgr/webpack', + options: { + prettier: false, + svgo: false, + svgoConfig: { + plugins: [{ removeViewBox: false }], + }, + titleProp: true, + ref: true, + }, + }, + { + loader: 'file-loader', + options: { + name: 'static/media/[name].[hash].[ext]', + }, + }, + ], + issuer: { + and: [/\.(ts|tsx|js|jsx|md|mdx)$/], }, }, // Process application JS with Babel. @@ -447,17 +425,6 @@ module.exports = function (webpackEnv) { ), // @remove-on-eject-end plugins: [ - [ - require.resolve('babel-plugin-named-asset-import'), - { - loaderMap: { - svg: { - ReactComponent: - '@svgr/webpack?-svgo,+titleProp,+ref![path]', - }, - }, - }, - ], isEnvDevelopment && shouldUseReactRefresh && require.resolve('react-refresh/babel'), @@ -526,7 +493,7 @@ module.exports = function (webpackEnv) { ? shouldUseSourceMap : isEnvDevelopment, modules: { - compileType: 'icss', + mode: 'icss', }, }), // Don't consider CSS imports dead code even if the @@ -545,7 +512,7 @@ module.exports = function (webpackEnv) { ? shouldUseSourceMap : isEnvDevelopment, modules: { - compileType: 'module', + mode: 'local', getLocalIdent: getCSSModuleLocalIdent, }, }), @@ -563,7 +530,7 @@ module.exports = function (webpackEnv) { ? shouldUseSourceMap : isEnvDevelopment, modules: { - compileType: 'icss', + mode: 'icss', }, }, 'sass-loader' @@ -585,7 +552,7 @@ module.exports = function (webpackEnv) { ? shouldUseSourceMap : isEnvDevelopment, modules: { - compileType: 'module', + mode: 'local', getLocalIdent: getCSSModuleLocalIdent, }, }, @@ -598,15 +565,12 @@ module.exports = function (webpackEnv) { // This loader doesn't use a "test" so it will catch all modules // that fall through the other loaders. { - loader: require.resolve('file-loader'), // Exclude `js` files to keep "css" loader working as it injects // its runtime that would otherwise be processed through "file" loader. // Also exclude `html` and `json` extensions so they get processed // by webpacks internal loaders. - exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], - options: { - name: 'static/media/[name].[hash:8].[ext]', - }, + exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], + type: 'asset/resource', }, // ** STOP ** Are you adding a new loader? // Make sure to add the new loader(s) before the "file" loader. @@ -662,33 +626,17 @@ module.exports = function (webpackEnv) { // during a production build. // Otherwise React will be compiled in the very slow development mode. new webpack.DefinePlugin(env.stringified), - // This is necessary to emit hot updates (CSS and Fast Refresh): - isEnvDevelopment && new webpack.HotModuleReplacementPlugin(), // Experimental hot reloading for React . // https://github.com/facebook/react/tree/main/packages/react-refresh isEnvDevelopment && shouldUseReactRefresh && new ReactRefreshWebpackPlugin({ - overlay: { - entry: webpackDevClientEntry, - // The expected exports are slightly different from what the overlay exports, - // so an interop is included here to enable feedback on module-level errors. - module: reactRefreshOverlayEntry, - // Since we ship a custom dev client and overlay integration, - // the bundled socket handling logic can be eliminated. - sockIntegration: false, - }, + overlay: false, }), // Watcher doesn't work well if you mistype casing in a path so we use // a plugin that prints an error when you attempt to do this. // See https://github.com/facebook/create-react-app/issues/240 isEnvDevelopment && new CaseSensitivePathsPlugin(), - // If you require a missing module and then `npm install` it, you still have - // to restart the development server for webpack to discover it. This plugin - // makes the discovery automatic so you don't have to restart. - // See https://github.com/facebook/create-react-app/issues/186 - isEnvDevelopment && - new WatchMissingNodeModulesPlugin(paths.appNodeModules), isEnvProduction && new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output @@ -725,7 +673,10 @@ module.exports = function (webpackEnv) { // solution that requires the user to opt into importing specific locales. // 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$/), + new webpack.IgnorePlugin({ + resourceRegExp: /^\.\/locale$/, + contextRegExp: /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 && @@ -742,33 +693,50 @@ module.exports = function (webpackEnv) { // TypeScript type checking useTypeScript && new ForkTsCheckerWebpackPlugin({ - typescript: resolve.sync('typescript', { - basedir: paths.appNodeModules, - }), async: isEnvDevelopment, - checkSyntacticErrors: true, - resolveModuleNameModule: process.versions.pnp - ? `${__dirname}/pnpTs.js` - : undefined, - resolveTypeReferenceDirectiveModule: process.versions.pnp - ? `${__dirname}/pnpTs.js` - : undefined, - tsconfig: paths.appTsConfig, - reportFiles: [ + typescript: { + typescriptPath: resolve.sync('typescript', { + basedir: paths.appNodeModules, + }), + configOverwrite: { + compilerOptions: { + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, + skipLibCheck: true, + inlineSourceMap: false, + declarationMap: false, + noEmit: true, + incremental: true, + tsBuildInfoFile: paths.appTsBuildInfoFile, + }, + }, + context: paths.appPath, + diagnosticOptions: { + syntactic: true, + }, + mode: 'write-references', + // profile: true, + }, + issue: { // This one is specifically to match during CI tests, // as micromatch doesn't match // '../cra-template-typescript/template/src/App.tsx' // otherwise. - '../**/src/**/*.{ts,tsx}', - '**/src/**/*.{ts,tsx}', - '!**/src/**/__tests__/**', - '!**/src/**/?(*.)(spec|test).*', - '!**/src/setupProxy.*', - '!**/src/setupTests.*', - ], - silent: true, - // The formatter is invoked directly in WebpackDevServerUtils during development - formatter: isEnvProduction ? typescriptFormatter : undefined, + include: [ + { file: '../**/src/**/*.{ts,tsx}' }, + { file: '**/src/**/*.{ts,tsx}' }, + ], + exclude: [ + { file: '**/src/**/__tests__/**' }, + { file: '**/src/**/?(*.){spec|test}.*' }, + { file: '**/src/setupProxy.*' }, + { file: '**/src/setupTests.*' }, + ], + }, + logger: { + infrastructure: 'silent', + }, }), !disableESLintPlugin && new ESLintPlugin({ @@ -796,18 +764,6 @@ module.exports = function (webpackEnv) { }, }), ].filter(Boolean), - // Some libraries import Node modules but don't use them in the browser. - // Tell webpack to provide empty mocks for them so importing them works. - node: { - module: 'empty', - dgram: 'empty', - dns: 'mock', - fs: 'empty', - http2: 'empty', - net: 'empty', - tls: 'empty', - child_process: 'empty', - }, // Turn off performance processing because we utilize // our own hints via the FileSizeReporter performance: false, diff --git a/config/webpack/persistentCache/createEnvironmentHash.js b/config/webpack/persistentCache/createEnvironmentHash.js new file mode 100644 index 00000000000..4487e853e18 --- /dev/null +++ b/config/webpack/persistentCache/createEnvironmentHash.js @@ -0,0 +1,9 @@ +'use strict'; +const { createHash } = require('crypto'); + +module.exports = env => { + const hash = createHash('md5'); + hash.update(JSON.stringify(env)); + + return hash.digest('hex'); +}; diff --git a/config/webpackDevServer.config.js b/config/webpackDevServer.config.js index bf8c98d0d91..2202da20346 100644 --- a/config/webpackDevServer.config.js +++ b/config/webpackDevServer.config.js @@ -9,7 +9,6 @@ 'use strict'; const fs = require('fs'); -const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware'); const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); const ignoredFiles = require('react-dev-utils/ignoredFiles'); @@ -19,10 +18,12 @@ const getHttpsConfig = require('./getHttpsConfig'); const host = process.env.HOST || '0.0.0.0'; const sockHost = process.env.WDS_SOCKET_HOST; -const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node' +const sockPath = process.env.WDS_SOCKET_PATH; // default: '/ws' const sockPort = process.env.WDS_SOCKET_PORT; module.exports = function (proxy, allowedHost) { + const disableFirewall = + !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true'; return { // WebpackDevServer 2.4.3 introduced a security fix that prevents remote // websites from potentially accessing local content through DNS rebinding: @@ -40,90 +41,78 @@ module.exports = function (proxy, allowedHost) { // So we will disable the host check normally, but enable it if you have // specified the `proxy` setting. Finally, we let you override it if you // really know what you're doing with a special environment variable. - disableHostCheck: - !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', + // Note: ["localhost", ".localhost"] will support subdomains - but we might + // want to allow setting the allowedHosts manually for more complex setups + allowedHosts: disableFirewall ? 'all' : [allowedHost], // Enable gzip compression of generated files. compress: true, - // Silence WebpackDevServer's own logs since they're generally not useful. - // It will still show compile warnings and errors with this setting. - clientLogLevel: 'none', - // By default WebpackDevServer serves physical files from current directory - // in addition to all the virtual build products that it serves from memory. - // This is confusing because those files won’t automatically be available in - // production build folder unless we copy them. However, copying the whole - // project directory is dangerous because we may expose sensitive files. - // Instead, we establish a convention that only files in `public` directory - // get served. Our build script will copy `public` into the `build` folder. - // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: - // - // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. - // Note that we only recommend to use `public` folder as an escape hatch - // for files like `favicon.ico`, `manifest.json`, and libraries that are - // for some reason broken when imported through webpack. If you just want to - // use an image, put it in `src` and `import` it from JavaScript instead. - contentBase: paths.appPublic, - contentBasePublicPath: paths.publicUrlOrPath, - // By default files from `contentBase` will not trigger a page reload. - watchContentBase: true, - // Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint - // for the WebpackDevServer client so it can learn when the files were - // updated. The WebpackDevServer client is included as an entry point - // in the webpack development configuration. Note that only changes - // to CSS are currently hot reloaded. JS changes will refresh the browser. - hot: true, - // Use 'ws' instead of 'sockjs-node' on server since we're using native - // websockets in `webpackHotDevClient`. - transportMode: 'ws', - // Prevent a WS client from getting injected as we're already including - // `webpackHotDevClient`. - injectClient: false, - // Enable custom sockjs pathname for websocket connection to hot reloading server. - // Enable custom sockjs hostname, pathname and port for websocket connection - // to hot reloading server. - sockHost, - sockPath, - sockPort, - // It is important to tell WebpackDevServer to use the same "publicPath" path as - // we specified in the webpack config. When homepage is '.', default to serving - // from the root. - // remove last slash so user can land on `/test` instead of `/test/` - publicPath: paths.publicUrlOrPath.slice(0, -1), - // WebpackDevServer is noisy by default so we emit custom message instead - // by listening to the compiler events with `compiler.hooks[...].tap` calls above. - quiet: true, - // Reportedly, this avoids CPU overload on some systems. - // https://github.com/facebook/create-react-app/issues/293 - // src/node_modules is not ignored to support absolute imports - // https://github.com/facebook/create-react-app/issues/1065 - watchOptions: { - ignored: ignoredFiles(paths.appSrc), + static: { + // By default WebpackDevServer serves physical files from current directory + // in addition to all the virtual build products that it serves from memory. + // This is confusing because those files won’t automatically be available in + // production build folder unless we copy them. However, copying the whole + // project directory is dangerous because we may expose sensitive files. + // Instead, we establish a convention that only files in `public` directory + // get served. Our build script will copy `public` into the `build` folder. + // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: + // + // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. + // Note that we only recommend to use `public` folder as an escape hatch + // for files like `favicon.ico`, `manifest.json`, and libraries that are + // for some reason broken when imported through webpack. If you just want to + // use an image, put it in `src` and `import` it from JavaScript instead. + directory: paths.appPublic, + publicPath: [paths.publicUrlOrPath], + // By default files from `contentBase` will not trigger a page reload. + watch: { + // Reportedly, this avoids CPU overload on some systems. + // https://github.com/facebook/create-react-app/issues/293 + // src/node_modules is not ignored to support absolute imports + // https://github.com/facebook/create-react-app/issues/1065 + ignored: ignoredFiles(paths.appSrc), + }, }, + client: { + webSocketURL: { + // Enable custom sockjs pathname for websocket connection to hot reloading server. + // Enable custom sockjs hostname, pathname and port for websocket connection + // to hot reloading server. + hostname: sockHost, + pathname: sockPath, + port: sockPort, + }, + overlay: true, + }, + devMiddleware: { + // It is important to tell WebpackDevServer to use the same "publicPath" path as + // we specified in the webpack config. When homepage is '.', default to serving + // from the root. + // remove last slash so user can land on `/test` instead of `/test/` + publicPath: paths.publicUrlOrPath.slice(0, -1), + }, + https: getHttpsConfig(), host, - overlay: false, historyApiFallback: { // Paths with dots should still use the history fallback. // See https://github.com/facebook/create-react-app/issues/387. disableDotRule: true, index: paths.publicUrlOrPath, }, - public: allowedHost, // `proxy` is run between `before` and `after` `webpack-dev-server` hooks proxy, - before(app, server) { - // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware` + onBeforeSetupMiddleware(app, server) { + // Keep `evalSourceMapMiddleware` // middlewares before `redirectServedPath` otherwise will not have any effect // This lets us fetch source contents from webpack for the error overlay app.use(evalSourceMapMiddleware(server)); - // This lets us open files from the runtime error overlay. - app.use(errorOverlayMiddleware()); if (fs.existsSync(paths.proxySetup)) { // This registers user provided middleware for proxy reasons require(paths.proxySetup)(app); } }, - after(app) { + onAfterSetupMiddleware(app) { // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match app.use(redirectServedPath(paths.publicUrlOrPath)); diff --git a/fixtures/kitchensink/template.json b/fixtures/kitchensink/template.json index c859e7a14cd..746cead3017 100644 --- a/fixtures/kitchensink/template.json +++ b/fixtures/kitchensink/template.json @@ -3,7 +3,7 @@ "dependencies": { "bootstrap": "4.3.1", "jest": "26.6.0", - "node-sass": "4.x", + "node-sass": "6.x", "normalize.css": "7.0.0", "prop-types": "15.7.2", "test-integrity": "2.0.1" diff --git a/fixtures/kitchensink/template/integration/webpack.test.js b/fixtures/kitchensink/template/integration/webpack.test.js index fcb61f50ec9..7c56bdceee5 100644 --- a/fixtures/kitchensink/template/integration/webpack.test.js +++ b/fixtures/kitchensink/template/integration/webpack.test.js @@ -93,8 +93,9 @@ describe('Integration', () => { it('no ext inclusion', async () => { doc = await initDOM('no-ext-inclusion'); - expect(doc.getElementById('feature-no-ext-inclusion').href).toMatch( - /\/static\/media\/aFileWithoutExt\.[a-f0-9]{8}\.bin$/ + // Webpack 4 added a default extension ".bin" seems like webpack 5 asset modules do not + expect(doc.getElementById('feature-no-ext-inclusion').getAttribute('href')).toMatch( + /\/static\/media\/aFileWithoutExt\.[a-f0-9]+$/ ); }); @@ -135,8 +136,8 @@ describe('Integration', () => { it('unknown ext inclusion', async () => { doc = await initDOM('unknown-ext-inclusion'); - expect(doc.getElementById('feature-unknown-ext-inclusion').href).toMatch( - /\/static\/media\/aFileWithExt\.[a-f0-9]{8}\.unknown$/ + expect(doc.getElementById('feature-unknown-ext-inclusion').getAttribute('href')).toMatch( + /\/static\/media\/aFileWithExt\.[a-f0-9]+\.unknown$/ ); }); }); diff --git a/fixtures/kitchensink/template/src/features/webpack/JsonInclusion.js b/fixtures/kitchensink/template/src/features/webpack/JsonInclusion.js index 97f782f515c..a67fdebf015 100644 --- a/fixtures/kitchensink/template/src/features/webpack/JsonInclusion.js +++ b/fixtures/kitchensink/template/src/features/webpack/JsonInclusion.js @@ -6,7 +6,9 @@ */ import React from 'react'; -import { abstract } from './assets/abstract.json'; +import abstractJson from './assets/abstract.json'; + +const { abstract } = abstractJson; const JsonInclusion = () => ( {abstract} diff --git a/package.json b/package.json index 6b37f9eeb69..bd95dae6a0b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "license": "MIT", "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=14.0.0" }, "bugs": { "url": "https://github.com/facebook/create-react-app/issues" @@ -28,66 +28,52 @@ }, "types": "./lib/react-app.d.ts", "dependencies": { - "@babel/core": "7.12.3", - "@babel/eslint-parser": "^7.13.14", - "@pmmmwh/react-refresh-webpack-plugin": "0.4.3", + "@babel/core": "7.14.2", + "@pmmmwh/react-refresh-webpack-plugin": "0.5.0-rc.0", "@svgr/webpack": "5.5.0", - "@typescript-eslint/eslint-plugin": "^4.5.0", - "@typescript-eslint/parser": "^4.5.0", "babel-jest": "^26.6.0", - "babel-loader": "^8.1.0", + "babel-loader": "8.2.2", "babel-plugin-named-asset-import": "^0.3.7", "babel-preset-react-app": "^10.0.0", "bfj": "^7.0.2", - "camelcase": "^6.1.0", - "case-sensitive-paths-webpack-plugin": "2.3.0", - "css-loader": "4.3.0", - "dotenv": "8.2.0", + "browserslist": "^4.16.6", + "camelcase": "^6.2.0", + "case-sensitive-paths-webpack-plugin": "2.4.0", + "css-loader": "6.2.0", + "css-minimizer-webpack-plugin": "3.0.2", + "dotenv": "9.0.2", "dotenv-expand": "5.1.0", - "eslint": "^7.11.0", + "eslint": "^7.30.0", "eslint-config-react-app": "^6.0.0", - "eslint-plugin-flowtype": "^5.2.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.1.0", - "eslint-plugin-jsx-a11y": "^6.3.1", - "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-testing-library": "^3.9.2", - "eslint-webpack-plugin": "^2.5.2", - "file-loader": "6.1.1", - "fs-extra": "^9.0.1", - "html-webpack-plugin": "4.5.0", + "eslint-webpack-plugin": "^2.5.4", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "5.3.2", "identity-obj-proxy": "3.0.0", "jest": "26.6.0", "jest-circus": "26.6.0", "jest-resolve": "26.6.0", "jest-watch-typeahead": "0.6.1", - "mini-css-extract-plugin": "0.11.3", - "optimize-css-assets-webpack-plugin": "5.0.4", - "pnp-webpack-plugin": "1.6.4", + "mini-css-extract-plugin": "2.1.0", "postcss": "8.3.5", "postcss-flexbugs-fixes": "5.0.2", - "postcss-loader": "4.2.0", - "postcss-normalize": "9.0.0", + "postcss-loader": "6.1.1", + "postcss-normalize": "10.0.0", "postcss-preset-env": "6.7.0", - "postcss-safe-parser": "5.0.2", - "prompts": "2.4.0", + "prompts": "2.4.1", "react-app-polyfill": "^2.0.0", - "react-dev-utils": "^11.0.3", - "react-refresh": "^0.8.3", - "resolve": "1.18.1", - "resolve-url-loader": "^3.1.2", - "sass-loader": "^10.0.5", - "semver": "7.3.2", + "react-dev-utils": "^11.0.4", + "react-refresh": "^0.10.0", + "resolve": "1.20.0", + "resolve-url-loader": "^4.0.0", + "sass-loader": "^12.1.0", + "semver": "7.3.5", "source-map-loader": "^1.1.2", - "style-loader": "1.3.0", - "terser-webpack-plugin": "4.2.3", - "ts-pnp": "1.2.0", - "url-loader": "4.1.1", - "webpack": "4.44.2", - "webpack-dev-server": "3.11.1", - "webpack-manifest-plugin": "3.0.0", - "workbox-webpack-plugin": "5.1.4" + "style-loader": "3.0.0", + "webpack": "5.41.1", + "webpack-dev-server": "4.0.0-rc.0", + "webpack-manifest-plugin": "3.1.1", + "workbox-webpack-plugin": "6.1.5" }, "devDependencies": { "react": "^17.0.1", diff --git a/scripts/eject.js b/scripts/eject.js index 0972d33384a..e39907f01d7 100644 --- a/scripts/eject.js +++ b/scripts/eject.js @@ -109,7 +109,7 @@ prompts({ } } - const folders = ['config', 'config/jest', 'scripts']; + const folders = ['config', 'config/jest', 'scripts', 'config/webpack/persistentCache']; // Make shallow array of files paths const files = folders.reduce((files, folder) => { @@ -138,7 +138,7 @@ prompts({ console.log(cyan(`Copying files into ${appPath}`)); folders.forEach(folder => { - fs.mkdirSync(path.join(appPath, folder)); + fs.mkdirSync(path.join(appPath, folder), {recursive: true}); }); files.forEach(file => { diff --git a/scripts/start.js b/scripts/start.js index ffbb15d1204..7bf39b4ae8a 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -101,28 +101,19 @@ checkBrowsers(paths.appPath, isInteractive) const appName = require(paths.appPackageJson).name; const useTypeScript = fs.existsSync(paths.appTsConfig); - const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; const urls = prepareUrls( protocol, HOST, port, paths.publicUrlOrPath.slice(0, -1) ); - const devSocket = { - warnings: warnings => - devServer.sockWrite(devServer.sockets, 'warnings', warnings), - errors: errors => - devServer.sockWrite(devServer.sockets, 'errors', errors), - }; // Create a webpack compiler that is configured with custom messages. const compiler = createCompiler({ appName, config, - devSocket, urls, useYarn, useTypeScript, - tscCompileOnError, webpack, }); // Load proxy config diff --git a/scripts/utils/verifyPackageTree.js b/scripts/utils/verifyPackageTree.js index d1b6e27e3ba..42ec89de83d 100644 --- a/scripts/utils/verifyPackageTree.js +++ b/scripts/utils/verifyPackageTree.js @@ -23,13 +23,11 @@ function verifyPackageTree() { // These are packages most likely to break in practice. // See https://github.com/facebook/create-react-app/issues/1795 for reasons why. // I have not included Babel here because plugins typically don't import Babel (so it's not affected). - '@babel/eslint-parser', 'babel-jest', 'babel-loader', 'jest', 'webpack', 'webpack-dev-server', - isESLintPluginEnabled && 'babel-eslint', isESLintPluginEnabled && 'eslint', ].filter(Boolean); diff --git a/scripts/utils/verifyTypeScriptSetup.js b/scripts/utils/verifyTypeScriptSetup.js index 949f34ab7d2..cdc2d77a880 100644 --- a/scripts/utils/verifyTypeScriptSetup.js +++ b/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];