diff --git a/packages/react-scripts/CHANGELOG.md b/packages/react-scripts/CHANGELOG.md
index d0ccfcdeb59..ac89693b53e 100644
--- a/packages/react-scripts/CHANGELOG.md
+++ b/packages/react-scripts/CHANGELOG.md
@@ -1,5 +1,17 @@
# `backpack-react-scripts` Change Log
+## 7.1.0 (Pending)
+
+- BACKPORT: Added support for Backpack Foundations.
+- 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
+
## 7.0.5 - 2020-01-10
### Fixed
diff --git a/packages/react-scripts/README.md b/packages/react-scripts/README.md
index b29f21e13c6..d70a9825ba5 100644
--- a/packages/react-scripts/README.md
+++ b/packages/react-scripts/README.md
@@ -23,13 +23,14 @@ 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-", "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.
- `externals`: exposing the Webpack config to modify externals, see [docs](https://webpack.js.org/configuration/externals/).
- `ssrExternals`: Similar to above, but for `ssr.js` only.
- `cssModules`: Boolean, true by default.
+ - `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 84cc59b7253..074cd73d49d 100755
--- a/packages/react-scripts/bin/react-scripts.js
+++ b/packages/react-scripts/bin/react-scripts.js
@@ -19,7 +19,7 @@ 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) : [];
@@ -28,6 +28,7 @@ switch (script) {
case 'build':
case 'eject':
case 'start':
+ case 'start-ssr':
case 'test': {
const result = spawn.sync(
'node',
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 4a072de4e72..110857bea26 100644
--- a/packages/react-scripts/config/paths.js
+++ b/packages/react-scripts/config/paths.js
@@ -77,7 +77,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'),
@@ -100,7 +101,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'),
@@ -135,7 +137,8 @@ if (
module.exports = {
dotenv: resolveOwn('template/.env'),
appPath: resolveApp('.'),
- appBuild: resolveOwn('../../build'),
+ appBuildWeb: resolveOwn('../../build/web'),
+ appBuildSsr: resolveOwn('../../build/ssr'),
appPublic: resolveOwn('template/public'),
appHtml: resolveOwn('template/public/index.html'),
appIndexJs: resolveModule(resolveOwn, 'template/src/index'),
diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js
index c2d5e98b9d4..5bd1b0c498d 100644
--- a/packages/react-scripts/config/webpack.config.js
+++ b/packages/react-scripts/config/webpack.config.js
@@ -35,6 +35,8 @@ const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
// @remove-on-eject-end
+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 pkgJson = require(paths.appPackageJson);
@@ -49,12 +51,17 @@ const customModuleRegexes = bpkReactScriptsConfig.babelIncludePrefixes
const cssModulesEnabled = bpkReactScriptsConfig.cssModules !== false;
const crossOriginLoading = bpkReactScriptsConfig.crossOriginLoading || 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';
// 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';
+// 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');
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
@@ -68,6 +75,7 @@ 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.
@@ -183,7 +191,7 @@ module.exports = function(webpackEnv) {
jsonpFunction: camelCase(pkgJson.name + 'JsonpCallback'),
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.
@@ -281,11 +289,13 @@ module.exports = function(webpackEnv) {
? {
chunks: 'all',
name: false,
- cacheGroups: bpkReactScriptsConfig.vendorsChunkRegex ? {
- vendors: {
- test: new RegExp(bpkReactScriptsConfig.vendorsChunkRegex),
- },
- } : {},
+ cacheGroups: bpkReactScriptsConfig.vendorsChunkRegex
+ ? {
+ vendors: {
+ test: new RegExp(bpkReactScriptsConfig.vendorsChunkRegex),
+ },
+ }
+ : {},
}
: {},
// Keep the runtime chunk seperated to enable long term caching
@@ -337,6 +347,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.
@@ -400,6 +411,7 @@ module.exports = function(webpackEnv) {
paths.appSrc,
backpackModulesRegex,
saddlebagModulesRegex,
+ scopedBackpackModulesRegex,
...customModuleRegexes,
],
loader: require.resolve('babel-loader'),
@@ -429,6 +441,7 @@ module.exports = function(webpackEnv) {
),
// @remove-on-eject-end
plugins: [
+ require.resolve('@loadable/babel-plugin'),
[
require.resolve('babel-plugin-named-asset-import'),
{
@@ -497,7 +510,7 @@ module.exports = function(webpackEnv) {
{
test: {
and: [cssRegex, () => !cssModulesEnabled],
- exclude: [backpackModulesRegex],
+ exclude: [backpackModulesRegex, scopedBackpackModulesRegex],
},
exclude: cssModuleRegex,
use: getStyleLoaders({
@@ -519,7 +532,7 @@ module.exports = function(webpackEnv) {
and: [cssRegex, () => cssModulesEnabled],
},
{
- and: [cssRegex, backpackModulesRegex],
+ and: [cssRegex, backpackModulesRegex, scopedBackpackModulesRegex],
},
],
use: getStyleLoaders({
@@ -535,7 +548,7 @@ module.exports = function(webpackEnv) {
{
test: {
and: [sassRegex, () => !cssModulesEnabled],
- exclude: [backpackModulesRegex],
+ exclude: [backpackModulesRegex, scopedBackpackModulesRegex],
},
exclude: sassModuleRegex,
use: getStyleLoaders(
@@ -563,7 +576,7 @@ module.exports = function(webpackEnv) {
and: [sassRegex, () => cssModulesEnabled],
},
{
- and: [sassRegex, backpackModulesRegex],
+ and: [sassRegex, backpackModulesRegex, scopedBackpackModulesRegex],
},
],
use: getStyleLoaders(
@@ -602,6 +615,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