Skip to content

Commit b650acb

Browse files
committed
[no jira]: Full SSR, Hard Source plugin
1 parent f6b5d72 commit b650acb

File tree

13 files changed

+244
-45
lines changed

13 files changed

+244
-45
lines changed

packages/react-scripts/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# `backpack-react-scripts` Change Log
22

3+
## 8.1.0 (Pending)
4+
5+
- Added support for loadable components.
6+
- Added `start-ssr` command, to produce Node.js-compatible watched output. Several changes to SSR Webpack config to support.
7+
- Defined `typeof window` for browser and SSR environments, enabling dead code elimination (https://webpack.js.org/plugins/define-plugin/#usage)
8+
- SSR output always includes hash as part of filename
9+
- Added experimental support for https://github.com/mzgoddard/hard-source-webpack-plugin/, enabled by `USE_HARD_SOURCE_WEBPACK_PLUGIN=true` environment variable
10+
- `web` and `ssr` subpaths for each build's output
11+
- 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.
12+
- 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
13+
314
## 8.0.6
415

516
- BACKPORT: Updated webpack paths to include the new `@skyscanner/bpk` foundations packages.

packages/react-scripts/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ npm start
3232
- `cssModules`: Boolean, true by default.
3333
- `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).
3434
- **Note** if this is enabled, `crossOriginLoading` value is overriden with `anonymous` in order for it to output with the integrity value.
35+
- `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
3536

3637
## Releasing a new version of `backpack-react-scripts`
3738

packages/react-scripts/bin/react-scripts.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@ const spawn = require('react-dev-utils/crossSpawn');
1919
const args = process.argv.slice(2);
2020

2121
const scriptIndex = args.findIndex(
22-
x => x === 'build' || x === 'eject' || x === 'start' || x === 'test'
22+
x =>
23+
x === 'build' ||
24+
x === 'eject' ||
25+
x === 'start' ||
26+
x === 'start-ssr' ||
27+
x === 'test'
2328
);
2429
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
2530
const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
2631

27-
if (['build', 'eject', 'start', 'test'].includes(script)) {
32+
if (['build', 'eject', 'start', 'start-ssr', 'test'].includes(script)) {
2833
const result = spawn.sync(
2934
'node',
3035
nodeArgs
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
6+
const reactScriptsRoot = path.resolve(__dirname, '..');
7+
const haveIsolatedDependencies =
8+
fs.existsSync(path.join(reactScriptsRoot, 'package-lock.json')) ||
9+
fs.existsSync(path.join(reactScriptsRoot, 'yarn.lock'));
10+
11+
module.exports = {
12+
root: haveIsolatedDependencies ? reactScriptsRoot : process.cwd(),
13+
directories: [],
14+
files: ['package-lock.json', 'yarn.lock'],
15+
};

packages/react-scripts/config/paths.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ const resolveModule = (resolveFn, filePath) => {
6060
module.exports = {
6161
dotenv: resolveApp('.env'),
6262
appPath: resolveApp('.'),
63-
appBuild: resolveApp('build'),
63+
appBuildWeb: resolveApp('build/web'),
64+
appBuildSsr: resolveApp('build/ssr'),
6465
appPublic: resolveApp('public'),
6566
appHtml: resolveApp('public/index.html'),
6667
appIndexJs: resolveModule(resolveApp, 'src/index'),
@@ -83,7 +84,8 @@ const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath);
8384
module.exports = {
8485
dotenv: resolveApp('.env'),
8586
appPath: resolveApp('.'),
86-
appBuild: resolveApp('build'),
87+
appBuildWeb: resolveApp('build/web'),
88+
appBuildSsr: resolveApp('build/ssr'),
8789
appPublic: resolveApp('public'),
8890
appHtml: resolveApp('public/index.html'),
8991
appIndexJs: resolveModule(resolveApp, 'src/index'),
@@ -119,7 +121,8 @@ if (
119121
module.exports = {
120122
dotenv: resolveOwn(`${templatePath}/.env`),
121123
appPath: resolveApp('.'),
122-
appBuild: resolveOwn('../../build'),
124+
appBuildWeb: resolveOwn('../../build/web'),
125+
appBuildSsr: resolveOwn('../../build/ssr'),
123126
appPublic: resolveOwn(`${templatePath}/public`),
124127
appHtml: resolveOwn(`${templatePath}/public/index.html`),
125128
appIndexJs: resolveModule(resolveOwn, `${templatePath}/src/index`),

packages/react-scripts/config/webpack.config.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ const postcssNormalize = require('postcss-normalize');
4040

4141
const appPackageJson = require(paths.appPackageJson);
4242

43+
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
44+
const LoadablePlugin = require('@loadable/webpack-plugin');
45+
4346
const sassFunctions = require('bpk-mixins/sass-functions');
4447
const camelCase = require('lodash/camelCase');
4548
const bpkReactScriptsConfig = appPackageJson['backpack-react-scripts'] || {};
@@ -51,13 +54,19 @@ const customModuleRegexes = bpkReactScriptsConfig.babelIncludePrefixes
5154
const cssModulesEnabled = bpkReactScriptsConfig.cssModules !== false;
5255
const crossOriginLoading = bpkReactScriptsConfig.crossOriginLoading || false;
5356
const sriEnabled = bpkReactScriptsConfig.sriEnabled || false;
57+
const supressCssWarnings = bpkReactScriptsConfig.ignoreCssWarnings || false;
5458

5559
// Source maps are resource heavy and can cause out of memory issue for large source files.
5660
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
5761
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
5862
// makes for a smoother build process.
5963
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
6064

65+
// We might not want to use the hard source plugin on environments that won't persist the cache for later
66+
const useHardSourceWebpackPlugin =
67+
process.env.USE_HARD_SOURCE_WEBPACK_PLUGIN === 'true';
68+
const environmentHash = require('./environmentHash');
69+
6170
// const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
6271

6372
const imageInlineSizeLimit = parseInt(
@@ -196,7 +205,7 @@ module.exports = function(webpackEnv) {
196205
output: {
197206
crossOriginLoading: sriEnabled ? 'anonymous' : crossOriginLoading,
198207
// The build folder.
199-
path: isEnvProduction ? paths.appBuild : undefined,
208+
path: paths.appBuildWeb,
200209
// Add /* filename */ comments to generated require()s in the output.
201210
pathinfo: isEnvDevelopment,
202211
// There will be one main bundle, and one file per asynchronous chunk.
@@ -375,6 +384,7 @@ module.exports = function(webpackEnv) {
375384
],
376385
},
377386
module: {
387+
noParse: /iconv-loader\.js$/, // https://github.com/webpack/webpack/issues/3078#issuecomment-400697407
378388
strictExportPresence: true,
379389
rules: [
380390
// Disable require.ensure as it's not a standard language feature.
@@ -471,6 +481,7 @@ module.exports = function(webpackEnv) {
471481
),
472482
// @remove-on-eject-end
473483
plugins: [
484+
require.resolve('@loadable/babel-plugin'),
474485
[
475486
require.resolve('babel-plugin-named-asset-import'),
476487
{
@@ -656,6 +667,20 @@ module.exports = function(webpackEnv) {
656667
],
657668
},
658669
plugins: [
670+
useHardSourceWebpackPlugin &&
671+
new HardSourceWebpackPlugin({ environmentHash }),
672+
useHardSourceWebpackPlugin &&
673+
new HardSourceWebpackPlugin.ExcludeModulePlugin([
674+
{
675+
// HardSource works with mini-css-extract-plugin but due to how
676+
// mini-css emits assets, assets are not emitted on repeated builds with
677+
// mini-css and hard-source together. Ignoring the mini-css loader
678+
// modules, but not the other css loader modules, excludes the modules
679+
// that mini-css needs rebuilt to output assets every time.
680+
test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
681+
},
682+
]),
683+
new LoadablePlugin(),
659684
// Generates an `index.html` file with the <script> injected.
660685
new HtmlWebpackPlugin(
661686
Object.assign(
@@ -722,7 +747,10 @@ module.exports = function(webpackEnv) {
722747
// It is absolutely essential that NODE_ENV is set to production
723748
// during a production build.
724749
// Otherwise React will be compiled in the very slow development mode.
725-
new webpack.DefinePlugin(env.stringified),
750+
new webpack.DefinePlugin({
751+
...env.stringified,
752+
'typeof window': '"object"',
753+
}),
726754
// This is necessary to emit hot updates (currently CSS only):
727755
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
728756
// Watcher doesn't work well if you mistype casing in a path so we use
@@ -741,6 +769,7 @@ module.exports = function(webpackEnv) {
741769
// both options are optional
742770
filename: 'static/css/[name].[contenthash:8].css',
743771
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
772+
ignoreOrder: supressCssWarnings,
744773
}),
745774
// Generate an asset manifest file with the following content:
746775
// - "files" key: Mapping of all asset filenames to their corresponding

packages/react-scripts/config/webpack.config.ssr.js

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const PnpWebpackPlugin = require('pnp-webpack-plugin');
1717
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
1818
// const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
1919
// const TerserPlugin = require('terser-webpack-plugin');
20-
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
20+
// const MiniCssExtractPlugin = require('mini-css-extract-plugin');
2121
// const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
2222
// const safePostCssParser = require('postcss-safe-parser');
2323
// const ManifestPlugin = require('webpack-manifest-plugin');
@@ -39,6 +39,9 @@ const postcssNormalize = require('postcss-normalize');
3939

4040
const appPackageJson = require(paths.appPackageJson);
4141

42+
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
43+
const LoadablePlugin = require('@loadable/webpack-plugin');
44+
4245
const sassFunctions = require('bpk-mixins/sass-functions');
4346
// const camelCase = require('lodash/camelCase');
4447
const bpkReactScriptsConfig = appPackageJson['backpack-react-scripts'] || {};
@@ -55,6 +58,11 @@ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
5558
// makes for a smoother build process.
5659
// const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
5760

61+
// We might not want to use the hard source plugin on environments that won't persist the cache for later
62+
const useHardSourceWebpackPlugin =
63+
process.env.USE_HARD_SOURCE_WEBPACK_PLUGIN === 'true';
64+
const environmentHash = require('./environmentHash');
65+
5866
// const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
5967

6068
const imageInlineSizeLimit = parseInt(
@@ -99,18 +107,21 @@ module.exports = function(webpackEnv) {
99107
preProcessorOptions = {}
100108
) => {
101109
const loaders = [
102-
isEnvDevelopment && require.resolve('style-loader'),
103-
isEnvProduction && {
104-
loader: MiniCssExtractPlugin.loader,
105-
// css is located in `static/css`, use '../../' to locate index.html folder
106-
// in production `paths.publicUrlOrPath` can be a relative path
107-
options: paths.publicUrlOrPath.startsWith('.')
108-
? { publicPath: '../../' }
109-
: {},
110-
},
110+
//isEnvDevelopment && require.resolve('style-loader'),
111+
// isEnvProduction && {
112+
// loader: MiniCssExtractPlugin.loader,
113+
// // css is located in `static/css`, use '../../' to locate index.html folder
114+
// // in production `paths.publicUrlOrPath` can be a relative path
115+
// options: paths.publicUrlOrPath.startsWith('.')
116+
// ? { publicPath: '../../' }
117+
// : {},
118+
// },
111119
{
120+
// Since v2.0.0 css-loader/locals was removed in favour of exportOnlyLocals option
121+
// So adding the option here in replacement as per
122+
// https://github.com/webpack-contrib/css-loader#exportonlylocals
112123
loader: require.resolve('css-loader'),
113-
options: cssOptions,
124+
options: { ...cssOptions, exportOnlyLocals: true },
114125
},
115126
{
116127
// Options for PostCSS as we reference these options twice
@@ -183,8 +194,8 @@ module.exports = function(webpackEnv) {
183194
// the line below with these two lines if you prefer the stock client:
184195
// require.resolve('webpack-dev-server/client') + '?/',
185196
// require.resolve('webpack/hot/dev-server'),
186-
isEnvDevelopment &&
187-
require.resolve('react-dev-utils/webpackHotDevClient'),
197+
// isEnvDevelopment &&
198+
// require.resolve('react-dev-utils/webpackHotDevClient'),
188199
// Finally, this is your app's code:
189200
// paths.appIndexJs,
190201
paths.appSsrJs,
@@ -194,7 +205,7 @@ module.exports = function(webpackEnv) {
194205
].filter(Boolean),
195206
output: {
196207
// The build folder.
197-
path: isEnvProduction ? paths.appBuild : undefined,
208+
path: paths.appBuildSsr,
198209
// Add /* filename */ comments to generated require()s in the output.
199210
pathinfo: isEnvDevelopment,
200211
// There will be one main bundle, and one file per asynchronous chunk.
@@ -204,12 +215,12 @@ module.exports = function(webpackEnv) {
204215
// : isEnvDevelopment && 'static/js/bundle.js',
205216
// TODO: remove this when upgrading to webpack 5
206217
futureEmitAssets: true,
207-
filename: 'ssr.js',
218+
filename: 'ssr.[hash:8].js',
208219
libraryTarget: 'commonjs2',
209220
// There are also additional JS chunk files if you use code splitting.
210221
chunkFilename: isEnvProduction
211222
? 'static/js/[name].[contenthash:8].chunk.js'
212-
: isEnvDevelopment && 'static/js/[name].chunk.js',
223+
: isEnvDevelopment && 'static/js/[name].[chunkhash:8].chunk.js',
213224
// webpack uses `publicPath` to determine where the app is being served from.
214225
// It requires a trailing slash, or the file assets will get an incorrect path.
215226
// We inferred the "public path" (such as / or /my-project) from homepage.
@@ -352,6 +363,7 @@ module.exports = function(webpackEnv) {
352363
],
353364
},
354365
module: {
366+
noParse: /iconv-loader\.js$/, // https://github.com/webpack/webpack/issues/3078#issuecomment-400697407
355367
strictExportPresence: true,
356368
rules: [
357369
// Disable require.ensure as it's not a standard language feature.
@@ -448,6 +460,7 @@ module.exports = function(webpackEnv) {
448460
),
449461
// @remove-on-eject-end
450462
plugins: [
463+
require.resolve('@loadable/babel-plugin'),
451464
[
452465
require.resolve('babel-plugin-named-asset-import'),
453466
{
@@ -633,6 +646,20 @@ module.exports = function(webpackEnv) {
633646
],
634647
},
635648
plugins: [
649+
useHardSourceWebpackPlugin &&
650+
new HardSourceWebpackPlugin({ environmentHash }),
651+
useHardSourceWebpackPlugin &&
652+
new HardSourceWebpackPlugin.ExcludeModulePlugin([
653+
{
654+
// HardSource works with mini-css-extract-plugin but due to how
655+
// mini-css emits assets, assets are not emitted on repeated builds with
656+
// mini-css and hard-source together. Ignoring the mini-css loader
657+
// modules, but not the other css loader modules, excludes the modules
658+
// that mini-css needs rebuilt to output assets every time.
659+
test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
660+
},
661+
]),
662+
new LoadablePlugin(),
636663
// Generates an `index.html` file with the <script> injected.
637664
// new HtmlWebpackPlugin(
638665
// Object.assign(
@@ -699,9 +726,12 @@ module.exports = function(webpackEnv) {
699726
// It is absolutely essential that NODE_ENV is set to production
700727
// during a production build.
701728
// Otherwise React will be compiled in the very slow development mode.
702-
new webpack.DefinePlugin(env.stringified),
729+
new webpack.DefinePlugin({
730+
...env.stringified,
731+
'typeof window': '"undefined"',
732+
}),
703733
// This is necessary to emit hot updates (currently CSS only):
704-
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
734+
// isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
705735
// Watcher doesn't work well if you mistype casing in a path so we use
706736
// a plugin that prints an error when you attempt to do this.
707737
// See https://github.com/facebook/create-react-app/issues/240
@@ -712,14 +742,15 @@ module.exports = function(webpackEnv) {
712742
// See https://github.com/facebook/create-react-app/issues/186
713743
isEnvDevelopment &&
714744
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
715-
isEnvProduction &&
716-
new MiniCssExtractPlugin({
717-
// Options similar to the same options in webpackOptions.output
718-
// both options are optional
719-
// filename: 'static/css/[name].[contenthash:8].css',
720-
filename: 'ssr.css',
721-
// chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
722-
}),
745+
// isEnvProduction &&
746+
// new MiniCssExtractPlugin({
747+
// // Options similar to the same options in webpackOptions.output
748+
// // both options are optional
749+
// // filename: 'static/css/[name].[contenthash:8].css',
750+
// filename: 'ssr.css',
751+
// // chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
752+
// // ignoreOrder: true,
753+
// }),
723754
// Generate an asset manifest file with the following content:
724755
// - "files" key: Mapping of all asset filenames to their corresponding
725756
// output file so that tools can pick it up without having to parse

0 commit comments

Comments
 (0)