diff --git a/.changeset/silver-dolphins-remember.md b/.changeset/silver-dolphins-remember.md
new file mode 100644
index 000000000..1a0c7f65b
--- /dev/null
+++ b/.changeset/silver-dolphins-remember.md
@@ -0,0 +1,5 @@
+---
+"modular-scripts": major
+---
+
+Upgrade to Webpack 5
diff --git a/packages/modular-scripts/package.json b/packages/modular-scripts/package.json
index fd6df0f24..773590d12 100644
--- a/packages/modular-scripts/package.json
+++ b/packages/modular-scripts/package.json
@@ -41,7 +41,8 @@
"change-case": "4.1.2",
"commander": "9.1.0",
"cross-spawn": "7.0.3",
- "css-loader": "4.3.0",
+ "css-loader": "6.6.0",
+ "css-minimizer-webpack-plugin": "3.4.1",
"dedent": "0.7.0",
"detect-port-alt": "1.1.6",
"dotenv": "16.0.0",
@@ -56,12 +57,12 @@
"file-loader": "6.2.0",
"filesize": "8.0.3",
"find-up": "5.0.0",
- "fork-ts-checker-webpack-plugin": "4.1.6",
+ "fork-ts-checker-webpack-plugin": "6.5.0",
"fs-extra": "10.0.1",
"global-modules": "2.0.0",
"globby": "11.0.4",
"gzip-size": "6.0.0",
- "html-webpack-plugin": "4.5.2",
+ "html-webpack-plugin": "5.5.0",
"is-ci": "2.0.0",
"is-root": "2.1.0",
"jest": "26.6.3",
@@ -72,19 +73,18 @@
"jest-transform-stub": "2.0.0",
"jest-watch-typeahead": "0.6.5",
"js-yaml": "^4.1.0",
- "loader-utils": "2.0.0",
+ "loader-utils": "3.2.0",
"micromatch": "4.0.4",
"mime": "^3.0.0",
- "mini-css-extract-plugin": "0.11.3",
+ "mini-css-extract-plugin": "2.5.3",
"npm-packlist": "4.0.0",
"open": "8.3.0",
- "optimize-css-assets-webpack-plugin": "6.0.1",
"parse5": "6.0.1",
"pkg-up": "3.1.0",
- "pnp-webpack-plugin": "1.6.4",
+ "pnp-webpack-plugin": "1.7.0",
"postcss": "8.4.12",
"postcss-flexbugs-fixes": "4.2.1",
- "postcss-loader": "4.2.0",
+ "postcss-loader": "6.2.1",
"postcss-normalize": "8.0.1",
"postcss-preset-env": "7.4.3",
"postcss-safe-parser": "5.0.2",
@@ -94,27 +94,27 @@
"react-refresh": "0.8.3",
"recursive-readdir": "2.2.2",
"resolve": "1.21.0",
- "resolve-url-loader": "5.0.0-beta.1",
+ "resolve-url-loader": "5.0.0",
"rimraf": "3.0.2",
"rollup": "2.70.1",
"rollup-plugin-esbuild": "^4.7.2",
"rollup-plugin-postcss": "4.0.2",
- "sass-loader": "10.0.5",
+ "sass-loader": "12.6.0",
"semver": "7.3.5",
"semver-regex": "3.1.3",
"shell-quote": "1.7.3",
"source-map-support": "0.5.21",
"strip-ansi": "6.0.0",
- "style-loader": "1.3.0",
- "terser-webpack-plugin": "4.2.3",
+ "style-loader": "3.3.1",
+ "terser-webpack-plugin": "5.3.1",
"tmp": "^0.2.1",
"ts-jest": "26.5.6",
"ts-morph": "^14.0.0",
"update-notifier": "5.1.0",
"url-loader": "4.1.1",
- "webpack": "4.46.0",
+ "webpack": "5.70.0",
"webpack-dev-server": "4.7.4",
- "webpack-manifest-plugin": "2.2.0",
+ "webpack-manifest-plugin": "4.1.1",
"ws": "8.5.0"
},
"peerDependencies": {
diff --git a/packages/modular-scripts/react-dev-utils/WebpackDevServerUtils.js b/packages/modular-scripts/react-dev-utils/WebpackDevServerUtils.js
index db9e745ee..bb7e0cca7 100644
--- a/packages/modular-scripts/react-dev-utils/WebpackDevServerUtils.js
+++ b/packages/modular-scripts/react-dev-utils/WebpackDevServerUtils.js
@@ -13,7 +13,6 @@ const forkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const { clearConsole } = require('./logger');
const formatWebpackMessages = require('./formatWebpackMessages');
const getProcessForPort = require('./getProcessForPort');
-const typescriptFormatter = require('./typescriptFormatter');
const console = require('./logger');
const isInteractive = process.stdout.isTTY;
@@ -94,15 +93,7 @@ function printInstructions(appName, urls) {
console.log();
}
-function createCompiler({
- appName,
- config,
- devSocket,
- urls,
- useTypeScript,
- tscCompileOnError,
- webpack,
-}) {
+function createCompiler({ appName, config, urls, useTypeScript, webpack }) {
// "Compiler" is a low-level interface to webpack.
// It lets us listen to some events and provide our own custom messages.
let compiler;
@@ -129,28 +120,16 @@ function createCompiler({
let isFirstCompile = true;
let tsMessagesPromise;
- let tsMessagesResolver;
if (useTypeScript) {
- compiler.hooks.beforeCompile.tap('beforeCompile', () => {
- tsMessagesPromise = 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),
- });
+ .waiting.tap('awaitingTypeScriptCheck', () => {
+ console.log(
+ chalk.yellow(
+ 'Files successfully emitted, waiting for typecheck results...',
+ ),
+ );
});
}
@@ -172,48 +151,6 @@ function createCompiler({
errors: true,
});
- if (useTypeScript && statsData.errors.length === 0) {
- const delayedMsg = setTimeout(() => {
- console.log(
- chalk.yellow(
- 'Files successfully emitted, waiting for typecheck results...',
- ),
- );
- }, 100);
-
- const messages = await tsMessagesPromise;
- clearTimeout(delayedMsg);
- if (tscCompileOnError) {
- statsData.warnings.push(...messages.errors);
- } else {
- statsData.errors.push(...messages.errors);
- }
- statsData.warnings.push(...messages.warnings);
-
- // Push errors and warnings into compilation result
- // to show them after page refresh triggered by user.
- if (tscCompileOnError) {
- stats.compilation.warnings.push(...messages.errors);
- } else {
- stats.compilation.errors.push(...messages.errors);
- }
- stats.compilation.warnings.push(...messages.warnings);
-
- if (messages.errors.length > 0) {
- if (tscCompileOnError) {
- devSocket.warnings(messages.errors);
- } else {
- devSocket.errors(messages.errors);
- }
- } else if (messages.warnings.length > 0) {
- devSocket.warnings(messages.warnings);
- }
-
- if (isInteractive) {
- clearConsole();
- }
- }
-
const messages = formatWebpackMessages(statsData);
const isSuccessful = !messages.errors.length && !messages.warnings.length;
if (isSuccessful) {
@@ -371,7 +308,7 @@ function prepareProxy(proxy, appPublicFolder, servedPathname) {
// If proxy is specified, let it handle any request except for
// files in the public folder and requests to the WebpackDevServer socket endpoint.
// https://github.com/facebook/create-react-app/issues/6720
- const sockPath = process.env.WDS_SOCKET_PATH || '/sockjs-node';
+ const sockPath = process.env.WDS_SOCKET_PATH || '/ws';
const isDefaultSockHost = !process.env.WDS_SOCKET_HOST;
function mayProxy(pathname) {
const maybePublicPath = path.resolve(
diff --git a/packages/modular-scripts/react-dev-utils/evalSourceMapMiddleware.js b/packages/modular-scripts/react-dev-utils/evalSourceMapMiddleware.js
index dbb0e67f7..a7244dd87 100644
--- a/packages/modular-scripts/react-dev-utils/evalSourceMapMiddleware.js
+++ b/packages/modular-scripts/react-dev-utils/evalSourceMapMiddleware.js
@@ -8,7 +8,9 @@ function base64SourceMap(source) {
}
function getSourceById(server, id) {
- const module = server._stats.compilation.modules.find((m) => m.id === id);
+ const module = Array.from(server._stats.compilation.modules).find(
+ (m) => server._stats.compilation.chunkGraph.getModuleId(m) === id,
+ );
return module.originalSource();
}
diff --git a/packages/modular-scripts/react-dev-utils/formatWebpackMessages.js b/packages/modular-scripts/react-dev-utils/formatWebpackMessages.js
index 04ce7a84a..6600a4c8e 100644
--- a/packages/modular-scripts/react-dev-utils/formatWebpackMessages.js
+++ b/packages/modular-scripts/react-dev-utils/formatWebpackMessages.js
@@ -8,7 +8,13 @@ function isLikelyASyntaxError(message) {
// Cleans up webpack error messages.
function formatMessage(message) {
- let lines = message.split('\n');
+ let lines = [];
+
+ if (typeof message === 'string' || message instanceof String) {
+ lines = message.split('\n');
+ } else if ('message' in message) {
+ lines = message['message'].split('\n');
+ }
// Strip webpack-added headers off errors/warnings
// https://github.com/webpack/webpack/blob/master/lib/ModuleError.js
diff --git a/packages/modular-scripts/react-dev-utils/webpackHotDevClient.js b/packages/modular-scripts/react-dev-utils/webpackHotDevClient.js
index bf435fb94..f85179e64 100644
--- a/packages/modular-scripts/react-dev-utils/webpackHotDevClient.js
+++ b/packages/modular-scripts/react-dev-utils/webpackHotDevClient.js
@@ -56,7 +56,7 @@ var connection = new WebSocket(
hostname: process.env.WDS_SOCKET_HOST || window.location.hostname,
port: process.env.WDS_SOCKET_PORT || window.location.port,
// Hardcoded in WebpackDevServer
- pathname: process.env.WDS_SOCKET_PATH || '/sockjs-node',
+ pathname: process.env.WDS_SOCKET_PATH || '/ws',
slashes: true,
}),
);
diff --git a/packages/modular-scripts/react-scripts/config/webpack.config.js b/packages/modular-scripts/react-scripts/config/webpack.config.js
index ff26a93f3..fac4cae2f 100644
--- a/packages/modular-scripts/react-scripts/config/webpack.config.js
+++ b/packages/modular-scripts/react-scripts/config/webpack.config.js
@@ -11,9 +11,8 @@ 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 ManifestPlugin = require('webpack-manifest-plugin');
+const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
+const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const { info } = require('../../react-dev-utils/logger');
const InterpolateHtmlPlugin = require('../../react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('../../react-dev-utils/WatchMissingNodeModulesPlugin');
@@ -24,22 +23,25 @@ const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('../../react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
-const typescriptFormatter = require('../../react-dev-utils/typescriptFormatter');
const postcssNormalize = require('postcss-normalize');
const isCI = require('is-ci');
-const esbuildTargetFactory = process.env.ESBUILD_TARGET_FACTORY
- ? JSON.parse(process.env.ESBUILD_TARGET_FACTORY)
- : 'es2015';
+const isApp = process.env.MODULAR_IS_APP === 'true';
+const isView = !isApp;
-const appPackageJson = require(paths.appPackageJson);
+const esbuildTargetFactory = isApp
+ ? process.env.ESBUILD_TARGET_FACTORY
+ ? JSON.parse(process.env.ESBUILD_TARGET_FACTORY)
+ : 'es2015'
+ : 'es2020';
+
+const { externalDependencies } = process.env.MODULAR_PACKAGE_DEPS
+ ? JSON.parse(process.env.MODULAR_PACKAGE_DEPS)
+ : {};
// 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 reactRefreshOverlayEntry = require.resolve(
'../../react-dev-utils/refreshOverlayInterop',
);
@@ -66,6 +68,17 @@ const sassModuleRegex = /\.module\.(scss|sass)$/;
module.exports = function (webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
+ const isViewDevelopment = isView & isEnvDevelopment;
+
+ // This is needed if we're serving a view in development node, since it won't be defined in the view dependencies.
+ if (externalDependencies.react && isViewDevelopment) {
+ externalDependencies['react-dom'] = externalDependencies.react;
+ }
+
+ // Create an import map of external dependencies if we're building a view
+ const importMap = isView
+ ? createExternalDependenciesMap(externalDependencies)
+ : {};
// Variable used for enabling profiling in Production
// passed into alias object. Uses a flag if passed into the build command
@@ -141,6 +154,35 @@ module.exports = function (webpackEnv) {
};
const webpackConfig = {
+ externals: isApp
+ ? undefined
+ : function ({ request }, callback) {
+ const parsedModule = parsePackageName(request);
+
+ // If the module is absolute and it is in the import map, we want to externalise it
+ if (
+ parsedModule &&
+ parsedModule.dependencyName &&
+ importMap[parsedModule.dependencyName]
+ ) {
+ const { dependencyName, submodule } = parsedModule;
+
+ const toRewrite = `${importMap[dependencyName]}${
+ submodule ? `/${submodule}` : ''
+ }`;
+
+ return callback(null, toRewrite);
+ }
+ // Otherwise we just want to bundle it
+ return callback();
+ },
+
+ externalsType: isApp ? undefined : 'module',
+ experiments: {
+ outputModule: isApp ? undefined : true,
+ },
+ // Workaround for this bug: https://stackoverflow.com/questions/53905253/cant-set-up-the-hmr-stuck-with-waiting-for-update-signal-from-wds-in-cons
+ target: 'web',
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
@@ -151,31 +193,15 @@ 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
- ? [
- // 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,
+ // We bundle a virtual file to trampoline the view as an entry point if we're starting it (views have no ReactDOM.render)
+ entry: isViewDevelopment ? getVirtualTrampoline() : paths.appIndexJs,
output: {
+ module: isApp ? undefined : true,
+ library: isApp
+ ? undefined
+ : {
+ type: 'module',
+ },
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
@@ -184,10 +210,9 @@ module.exports = function (webpackEnv) {
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
- : isEnvDevelopment && 'static/js/bundle.js',
- // TODO: remove this when upgrading to webpack 5
- futureEmitAssets: true,
+ : isEnvDevelopment && 'static/js/[name].js',
// There are also additional JS chunk files if you use code splitting.
+ // Please remember that Webpack 5, unlike Webpack 4, controls "splitChunks" via fileName, not chunkFilename - https://stackoverflow.com/questions/66077740/webpack-5-output-chunkfilename-not-working
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
@@ -204,12 +229,6 @@ module.exports = function (webpackEnv) {
: 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',
},
optimization: {
minimize: isEnvProduction,
@@ -253,41 +272,25 @@ 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,
- },
+ splitChunks: isApp
+ ? {
+ chunks: 'all',
+ }
+ : undefined,
// 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}`,
- },
+ runtimeChunk: isApp
+ ? {
+ name: (entrypoint) => `runtime-${entrypoint.name}`,
+ }
+ : undefined,
},
resolve: {
// This allows you to set a fallback for where webpack should look for modules.
@@ -305,7 +308,7 @@ module.exports = function (webpackEnv) {
// for React Native Web.
extensions: paths.moduleFileExtensions
.map((ext) => `.${ext}`)
- .filter((ext) => useTypeScript || !ext.includes('ts')),
+ .filter((ext) => useTypeScript || !isApp || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
@@ -317,6 +320,19 @@ module.exports = function (webpackEnv) {
}),
...(modules.webpackAliases || {}),
},
+ // 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.
+ // See https://github.com/webpack/webpack/issues/11649
+ fallback: {
+ module: false,
+ dgram: false,
+ dns: false,
+ fs: false,
+ http2: false,
+ net: false,
+ tls: false,
+ child_process: false,
+ },
plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
@@ -342,8 +358,6 @@ module.exports = function (webpackEnv) {
module: {
strictExportPresence: true,
rules: [
- // Disable require.ensure as it's not a standard language feature.
- { parser: { requireEnsure: false } },
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
@@ -496,7 +510,7 @@ module.exports = function (webpackEnv) {
// 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$/],
+ exclude: [/(^|\.(js|mjs|jsx|ts|tsx|html|json))$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
@@ -509,35 +523,56 @@ module.exports = function (webpackEnv) {
},
plugins: [
// Generates an `index.html` file with the
-
-
-