diff --git a/packages/react-scripts/CHANGELOG.md b/packages/react-scripts/CHANGELOG.md
index d7a1944f7e9..86396b7b11d 100644
--- a/packages/react-scripts/CHANGELOG.md
+++ b/packages/react-scripts/CHANGELOG.md
@@ -1,5 +1,20 @@
# `backpack-react-scripts` Change Log
+## 8.1.0 (Pending)
+
+- Added support for loadable components.
+- Added `start-ssr` command, to produce Node.js-compatible watched output. Several changes to SSR Webpack config to support.
+- Defined `typeof window` for browser and SSR environments, enabling dead code elimination (https://webpack.js.org/plugins/define-plugin/#usage)
+- SSR output always includes hash as part of filename
+- Added experimental support for https://github.com/mzgoddard/hard-source-webpack-plugin/, enabled by `USE_HARD_SOURCE_WEBPACK_PLUGIN=true` environment variable
+- `web` and `ssr` subpaths for each build's output
+- Output build 'status files' (`.build-status`, one for web, one for SSR), which can be watched by a Node.js server to know when builds are in progress or completed.
+- Added `ignoreCssWarnings` config item to allow the ability to supress CSS ordering issues when its safe to allow mixed order when it has not effect on output. https://github.com/webpack-contrib/mini-css-extract-plugin#remove-order-warnings
+
+## 8.0.6
+
+- BACKPORT: Updated webpack paths to include the new `@skyscanner/bpk` foundations packages.
+
## 8.0.4
- Updated `bpk-mixins` to latest version `20.1.5`
@@ -11,7 +26,8 @@
- Downgraded `sass-loader` to `7.3.1` due to issues with `node-sass` and functional changes to options.
Rebased onto `upstream/master` v3.4.4 (6c009edface3ed63d0c7046f088c675a63c82fdb)
- - Update `resolve-url-loader` to `3.1.2` to resolve security vunerability
+
+- Update `resolve-url-loader` to `3.1.2` to resolve security vunerability
## 8.0.2
diff --git a/packages/react-scripts/README.md b/packages/react-scripts/README.md
index 4b2500f109b..1ec6a1063bb 100644
--- a/packages/react-scripts/README.md
+++ b/packages/react-scripts/README.md
@@ -23,7 +23,7 @@ npm start
- **`css.html` & `js.html`**: New files in the `build/` output folder. These are html partials that include `` and `` references to the various static assets output by webpack. Useful if automatic chunking is turned on and you don't want to worry about order.
- A bunch of configuration options via `"backpack-react-scripts"` field in `package.json`:
- `crossOriginLoading`: Modify the default behaviour, see [docs](https://webpack.js.org/configuration/output/#output-crossoriginloading).
- - `babelIncludePrefixes`: An array of module name prefixes to opt into babel compilation. Includes `["bpk-", "saddlebag-"]` by default.
+ - `babelIncludePrefixes`: An array of module name prefixes to opt into babel compilation. Includes `["@skyscanner/bpk-", "bpk-", "saddlebag-"]` by default.
- `enableAutomaticChunking`: Boolean, opt in to automatic chunking of vendor, common and app code.
- `vendorsChunkRegex`: String, Regex for picking what goes into the `vendors` chunk. See `cacheGroups` in webpack docs. Dependent on `enableAutomaticChunking` being enabled
- `amdExcludes`: Array of module names to exclude from AMD parsing. Incldues `["lodash"]` by default.
@@ -32,6 +32,7 @@ npm start
- `cssModules`: Boolean, true by default.
- `sriEnabled`: Sets if SRI is to be used during build to add integrity hash for files, see [docs](https://github.com/waysact/webpack-subresource-integrity/blob/master/README.md).
- **Note** if this is enabled, `crossOriginLoading` value is overriden with `anonymous` in order for it to output with the integrity value.
+ - `ignoreCssWarnings`: Boolean, false by default. Allows the ability to supress CSS ordering issues when its safe to allow mixed order when it has not effect on output, see [docs](https://github.com/webpack-contrib/mini-css-extract-plugin#remove-order-warnings). False by default
## Releasing a new version of `backpack-react-scripts`
diff --git a/packages/react-scripts/bin/react-scripts.js b/packages/react-scripts/bin/react-scripts.js
index 7e6e290251a..1b2d36d222f 100755
--- a/packages/react-scripts/bin/react-scripts.js
+++ b/packages/react-scripts/bin/react-scripts.js
@@ -19,12 +19,17 @@ const spawn = require('react-dev-utils/crossSpawn');
const args = process.argv.slice(2);
const scriptIndex = args.findIndex(
- x => x === 'build' || x === 'eject' || x === 'start' || x === 'test'
+ x =>
+ x === 'build' ||
+ x === 'eject' ||
+ x === 'start' ||
+ x === 'start-ssr' ||
+ x === 'test'
);
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
-if (['build', 'eject', 'start', 'test'].includes(script)) {
+if (['build', 'eject', 'start', 'start-ssr', 'test'].includes(script)) {
const result = spawn.sync(
'node',
nodeArgs
diff --git a/packages/react-scripts/config/environmentHash.js b/packages/react-scripts/config/environmentHash.js
new file mode 100644
index 00000000000..2ff71f01c46
--- /dev/null
+++ b/packages/react-scripts/config/environmentHash.js
@@ -0,0 +1,15 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+
+const reactScriptsRoot = path.resolve(__dirname, '..');
+const haveIsolatedDependencies =
+ fs.existsSync(path.join(reactScriptsRoot, 'package-lock.json')) ||
+ fs.existsSync(path.join(reactScriptsRoot, 'yarn.lock'));
+
+module.exports = {
+ root: haveIsolatedDependencies ? reactScriptsRoot : process.cwd(),
+ directories: [],
+ files: ['package-lock.json', 'yarn.lock'],
+};
diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js
index 3b4a304055b..9ab55e2d93a 100644
--- a/packages/react-scripts/config/paths.js
+++ b/packages/react-scripts/config/paths.js
@@ -60,7 +60,8 @@ const resolveModule = (resolveFn, filePath) => {
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
- appBuild: resolveApp('build'),
+ appBuildWeb: resolveApp('build/web'),
+ appBuildSsr: resolveApp('build/ssr'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
@@ -83,7 +84,8 @@ const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath);
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
- appBuild: resolveApp('build'),
+ appBuildWeb: resolveApp('build/web'),
+ appBuildSsr: resolveApp('build/ssr'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
@@ -119,7 +121,8 @@ if (
module.exports = {
dotenv: resolveOwn(`${templatePath}/.env`),
appPath: resolveApp('.'),
- appBuild: resolveOwn('../../build'),
+ appBuildWeb: resolveOwn('../../build/web'),
+ appBuildSsr: resolveOwn('../../build/ssr'),
appPublic: resolveOwn(`${templatePath}/public`),
appHtml: resolveOwn(`${templatePath}/public/index.html`),
appIndexJs: resolveModule(resolveOwn, `${templatePath}/src/index`),
diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js
index c5bd3f16650..6aab668bc82 100644
--- a/packages/react-scripts/config/webpack.config.js
+++ b/packages/react-scripts/config/webpack.config.js
@@ -40,17 +40,21 @@ const postcssNormalize = require('postcss-normalize');
const appPackageJson = require(paths.appPackageJson);
+const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
+const LoadablePlugin = require('@loadable/webpack-plugin');
+
const sassFunctions = require('bpk-mixins/sass-functions');
const camelCase = require('lodash/camelCase');
const bpkReactScriptsConfig = appPackageJson['backpack-react-scripts'] || {};
const customModuleRegexes = bpkReactScriptsConfig.babelIncludePrefixes
? bpkReactScriptsConfig.babelIncludePrefixes.map(
- prefix => new RegExp(`node_modules[\\/]${prefix}`)
+ (prefix) => new RegExp(`node_modules[\\/]${prefix}`)
)
: [];
const cssModulesEnabled = bpkReactScriptsConfig.cssModules !== false;
const crossOriginLoading = bpkReactScriptsConfig.crossOriginLoading || false;
const sriEnabled = bpkReactScriptsConfig.sriEnabled || false;
+const supressCssWarnings = bpkReactScriptsConfig.ignoreCssWarnings || false;
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
@@ -58,6 +62,11 @@ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
+// We might not want to use the hard source plugin on environments that won't persist the cache for later
+const useHardSourceWebpackPlugin =
+ process.env.USE_HARD_SOURCE_WEBPACK_PLUGIN === 'true';
+const environmentHash = require('./environmentHash');
+
// const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
const imageInlineSizeLimit = parseInt(
@@ -76,10 +85,11 @@ const sassModuleRegex = /\.module\.(scss|sass)$/;
// Backpack / saddlebag node module regexes
const backpackModulesRegex = /node_modules[\\/]bpk-/;
const saddlebagModulesRegex = /node_modules[\\/]saddlebag-/;
+const scopedBackpackModulesRegex = /node_modules[\\/]@skyscanner[\\/]bpk-/;
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
-module.exports = function(webpackEnv) {
+module.exports = function (webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
@@ -195,7 +205,7 @@ module.exports = function(webpackEnv) {
output: {
crossOriginLoading: sriEnabled ? 'anonymous' : crossOriginLoading,
// The build folder.
- path: isEnvProduction ? paths.appBuild : undefined,
+ path: paths.appBuildWeb,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
@@ -215,12 +225,13 @@ module.exports = function(webpackEnv) {
publicPath: paths.publicUrlOrPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
- ? info =>
+ ? (info) =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
- (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
+ ((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}`,
@@ -321,7 +332,7 @@ module.exports = function(webpackEnv) {
// },
runtimeChunk: bpkReactScriptsConfig.enableAutomaticChunking
? {
- name: entrypoint => `runtime-${entrypoint.name}`,
+ name: (entrypoint) => `runtime-${entrypoint.name}`,
}
: false,
},
@@ -341,8 +352,8 @@ module.exports = function(webpackEnv) {
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
- .map(ext => `.${ext}`)
- .filter(ext => useTypeScript || !ext.includes('ts')),
+ .map((ext) => `.${ext}`)
+ .filter((ext) => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
@@ -374,6 +385,7 @@ module.exports = function(webpackEnv) {
],
},
module: {
+ noParse: /iconv-loader\.js$/, // https://github.com/webpack/webpack/issues/3078#issuecomment-400697407
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
@@ -440,6 +452,7 @@ module.exports = function(webpackEnv) {
paths.appSrc,
backpackModulesRegex,
saddlebagModulesRegex,
+ scopedBackpackModulesRegex,
...customModuleRegexes,
],
loader: require.resolve('babel-loader'),
@@ -469,6 +482,7 @@ module.exports = function(webpackEnv) {
),
// @remove-on-eject-end
plugins: [
+ require.resolve('@loadable/babel-plugin'),
[
require.resolve('babel-plugin-named-asset-import'),
{
@@ -539,7 +553,7 @@ module.exports = function(webpackEnv) {
{
test: {
and: [cssRegex, () => !cssModulesEnabled],
- exclude: [backpackModulesRegex],
+ exclude: [backpackModulesRegex, scopedBackpackModulesRegex],
},
exclude: cssModuleRegex,
use: getStyleLoaders({
@@ -561,7 +575,11 @@ module.exports = function(webpackEnv) {
and: [cssRegex, () => cssModulesEnabled],
},
{
- and: [cssRegex, backpackModulesRegex],
+ and: [
+ cssRegex,
+ backpackModulesRegex,
+ scopedBackpackModulesRegex,
+ ],
},
],
use: getStyleLoaders({
@@ -578,7 +596,7 @@ module.exports = function(webpackEnv) {
{
test: {
and: [sassRegex, () => !cssModulesEnabled],
- exclude: [backpackModulesRegex],
+ exclude: [backpackModulesRegex, scopedBackpackModulesRegex],
},
exclude: sassModuleRegex,
use: getStyleLoaders(
@@ -606,7 +624,11 @@ module.exports = function(webpackEnv) {
and: [sassRegex, () => cssModulesEnabled],
},
{
- and: [sassRegex, backpackModulesRegex],
+ and: [
+ sassRegex,
+ backpackModulesRegex,
+ scopedBackpackModulesRegex,
+ ],
},
],
use: getStyleLoaders(
@@ -646,6 +668,20 @@ module.exports = function(webpackEnv) {
],
},
plugins: [
+ useHardSourceWebpackPlugin &&
+ new HardSourceWebpackPlugin({ environmentHash }),
+ useHardSourceWebpackPlugin &&
+ new HardSourceWebpackPlugin.ExcludeModulePlugin([
+ {
+ // HardSource works with mini-css-extract-plugin but due to how
+ // mini-css emits assets, assets are not emitted on repeated builds with
+ // mini-css and hard-source together. Ignoring the mini-css loader
+ // modules, but not the other css loader modules, excludes the modules
+ // that mini-css needs rebuilt to output assets every time.
+ test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
+ },
+ ]),
+ new LoadablePlugin(),
// Generates an `index.html` file with the