Skip to content

Commit 9168ac5

Browse files
committed
[no jira]: Full SSR, Hard Source plugin
1 parent 1904fcb commit 9168ac5

File tree

13 files changed

+245
-41
lines changed

13 files changed

+245
-41
lines changed

packages/react-scripts/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
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+
14+
315
## 8.0.2
416

517
### Fixed

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(
@@ -195,7 +204,7 @@ module.exports = function(webpackEnv) {
195204
output: {
196205
crossOriginLoading: sriEnabled ? 'anonymous' : crossOriginLoading,
197206
// The build folder.
198-
path: isEnvProduction ? paths.appBuild : undefined,
207+
path: paths.appBuildWeb,
199208
// Add /* filename */ comments to generated require()s in the output.
200209
pathinfo: isEnvDevelopment,
201210
// There will be one main bundle, and one file per asynchronous chunk.
@@ -374,6 +383,7 @@ module.exports = function(webpackEnv) {
374383
],
375384
},
376385
module: {
386+
noParse: /iconv-loader\.js$/, // https://github.com/webpack/webpack/issues/3078#issuecomment-400697407
377387
strictExportPresence: true,
378388
rules: [
379389
// Disable require.ensure as it's not a standard language feature.
@@ -469,6 +479,7 @@ module.exports = function(webpackEnv) {
469479
),
470480
// @remove-on-eject-end
471481
plugins: [
482+
require.resolve('@loadable/babel-plugin'),
472483
[
473484
require.resolve('babel-plugin-named-asset-import'),
474485
{
@@ -646,6 +657,20 @@ module.exports = function(webpackEnv) {
646657
],
647658
},
648659
plugins: [
660+
useHardSourceWebpackPlugin &&
661+
new HardSourceWebpackPlugin({ environmentHash }),
662+
useHardSourceWebpackPlugin &&
663+
new HardSourceWebpackPlugin.ExcludeModulePlugin([
664+
{
665+
// HardSource works with mini-css-extract-plugin but due to how
666+
// mini-css emits assets, assets are not emitted on repeated builds with
667+
// mini-css and hard-source together. Ignoring the mini-css loader
668+
// modules, but not the other css loader modules, excludes the modules
669+
// that mini-css needs rebuilt to output assets every time.
670+
test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
671+
},
672+
]),
673+
new LoadablePlugin(),
649674
// Generates an `index.html` file with the <script> injected.
650675
new HtmlWebpackPlugin(
651676
Object.assign(
@@ -712,7 +737,10 @@ module.exports = function(webpackEnv) {
712737
// It is absolutely essential that NODE_ENV is set to production
713738
// during a production build.
714739
// Otherwise React will be compiled in the very slow development mode.
715-
new webpack.DefinePlugin(env.stringified),
740+
new webpack.DefinePlugin({
741+
...env.stringified,
742+
'typeof window': '"object"',
743+
}),
716744
// This is necessary to emit hot updates (currently CSS only):
717745
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
718746
// Watcher doesn't work well if you mistype casing in a path so we use
@@ -731,6 +759,7 @@ module.exports = function(webpackEnv) {
731759
// both options are optional
732760
filename: 'static/css/[name].[contenthash:8].css',
733761
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
762+
ignoreOrder: supressCssWarnings,
734763
}),
735764
// Generate an asset manifest file with the following content:
736765
// - "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(
@@ -98,18 +106,21 @@ module.exports = function(webpackEnv) {
98106
preProcessorOptions = {}
99107
) => {
100108
const loaders = [
101-
isEnvDevelopment && require.resolve('style-loader'),
102-
isEnvProduction && {
103-
loader: MiniCssExtractPlugin.loader,
104-
// css is located in `static/css`, use '../../' to locate index.html folder
105-
// in production `paths.publicUrlOrPath` can be a relative path
106-
options: paths.publicUrlOrPath.startsWith('.')
107-
? { publicPath: '../../' }
108-
: {},
109-
},
109+
//isEnvDevelopment && require.resolve('style-loader'),
110+
// isEnvProduction && {
111+
// loader: MiniCssExtractPlugin.loader,
112+
// // css is located in `static/css`, use '../../' to locate index.html folder
113+
// // in production `paths.publicUrlOrPath` can be a relative path
114+
// options: paths.publicUrlOrPath.startsWith('.')
115+
// ? { publicPath: '../../' }
116+
// : {},
117+
// },
110118
{
119+
// Since v2.0.0 css-loader/locals was removed in favour of exportOnlyLocals option
120+
// So adding the option here in replacement as per
121+
// https://github.com/webpack-contrib/css-loader#exportonlylocals
111122
loader: require.resolve('css-loader'),
112-
options: cssOptions,
123+
options: { ...cssOptions, exportOnlyLocals: true },
113124
},
114125
{
115126
// Options for PostCSS as we reference these options twice
@@ -182,8 +193,8 @@ module.exports = function(webpackEnv) {
182193
// the line below with these two lines if you prefer the stock client:
183194
// require.resolve('webpack-dev-server/client') + '?/',
184195
// require.resolve('webpack/hot/dev-server'),
185-
isEnvDevelopment &&
186-
require.resolve('react-dev-utils/webpackHotDevClient'),
196+
// isEnvDevelopment &&
197+
// require.resolve('react-dev-utils/webpackHotDevClient'),
187198
// Finally, this is your app's code:
188199
// paths.appIndexJs,
189200
paths.appSsrJs,
@@ -193,7 +204,7 @@ module.exports = function(webpackEnv) {
193204
].filter(Boolean),
194205
output: {
195206
// The build folder.
196-
path: isEnvProduction ? paths.appBuild : undefined,
207+
path: paths.appBuildSsr,
197208
// Add /* filename */ comments to generated require()s in the output.
198209
pathinfo: isEnvDevelopment,
199210
// There will be one main bundle, and one file per asynchronous chunk.
@@ -203,12 +214,12 @@ module.exports = function(webpackEnv) {
203214
// : isEnvDevelopment && 'static/js/bundle.js',
204215
// TODO: remove this when upgrading to webpack 5
205216
futureEmitAssets: true,
206-
filename: 'ssr.js',
217+
filename: 'ssr.[hash:8].js',
207218
libraryTarget: 'commonjs2',
208219
// There are also additional JS chunk files if you use code splitting.
209220
chunkFilename: isEnvProduction
210221
? 'static/js/[name].[contenthash:8].chunk.js'
211-
: isEnvDevelopment && 'static/js/[name].chunk.js',
222+
: isEnvDevelopment && 'static/js/[name].[chunkhash:8].chunk.js',
212223
// webpack uses `publicPath` to determine where the app is being served from.
213224
// It requires a trailing slash, or the file assets will get an incorrect path.
214225
// We inferred the "public path" (such as / or /my-project) from homepage.
@@ -351,6 +362,7 @@ module.exports = function(webpackEnv) {
351362
],
352363
},
353364
module: {
365+
noParse: /iconv-loader\.js$/, // https://github.com/webpack/webpack/issues/3078#issuecomment-400697407
354366
strictExportPresence: true,
355367
rules: [
356368
// Disable require.ensure as it's not a standard language feature.
@@ -446,6 +458,7 @@ module.exports = function(webpackEnv) {
446458
),
447459
// @remove-on-eject-end
448460
plugins: [
461+
require.resolve('@loadable/babel-plugin'),
449462
[
450463
require.resolve('babel-plugin-named-asset-import'),
451464
{
@@ -623,6 +636,20 @@ module.exports = function(webpackEnv) {
623636
],
624637
},
625638
plugins: [
639+
useHardSourceWebpackPlugin &&
640+
new HardSourceWebpackPlugin({ environmentHash }),
641+
useHardSourceWebpackPlugin &&
642+
new HardSourceWebpackPlugin.ExcludeModulePlugin([
643+
{
644+
// HardSource works with mini-css-extract-plugin but due to how
645+
// mini-css emits assets, assets are not emitted on repeated builds with
646+
// mini-css and hard-source together. Ignoring the mini-css loader
647+
// modules, but not the other css loader modules, excludes the modules
648+
// that mini-css needs rebuilt to output assets every time.
649+
test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
650+
},
651+
]),
652+
new LoadablePlugin(),
626653
// Generates an `index.html` file with the <script> injected.
627654
// new HtmlWebpackPlugin(
628655
// Object.assign(
@@ -689,9 +716,12 @@ module.exports = function(webpackEnv) {
689716
// It is absolutely essential that NODE_ENV is set to production
690717
// during a production build.
691718
// Otherwise React will be compiled in the very slow development mode.
692-
new webpack.DefinePlugin(env.stringified),
719+
new webpack.DefinePlugin({
720+
...env.stringified,
721+
'typeof window': '"undefined"',
722+
}),
693723
// This is necessary to emit hot updates (currently CSS only):
694-
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
724+
// isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
695725
// Watcher doesn't work well if you mistype casing in a path so we use
696726
// a plugin that prints an error when you attempt to do this.
697727
// See https://github.com/facebook/create-react-app/issues/240
@@ -702,14 +732,15 @@ module.exports = function(webpackEnv) {
702732
// See https://github.com/facebook/create-react-app/issues/186
703733
isEnvDevelopment &&
704734
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
705-
isEnvProduction &&
706-
new MiniCssExtractPlugin({
707-
// Options similar to the same options in webpackOptions.output
708-
// both options are optional
709-
// filename: 'static/css/[name].[contenthash:8].css',
710-
filename: 'ssr.css',
711-
// chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
712-
}),
735+
// isEnvProduction &&
736+
// new MiniCssExtractPlugin({
737+
// // Options similar to the same options in webpackOptions.output
738+
// // both options are optional
739+
// // filename: 'static/css/[name].[contenthash:8].css',
740+
// filename: 'ssr.css',
741+
// // chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
742+
// // ignoreOrder: true,
743+
// }),
713744
// Generate an asset manifest file with the following content:
714745
// - "files" key: Mapping of all asset filenames to their corresponding
715746
// output file so that tools can pick it up without having to parse

packages/react-scripts/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@skyscanner/backpack-react-scripts",
3-
"version": "8.0.2",
3+
"version": "8.1.0-alpha",
44
"description": "Backpack configuration and scripts for Create React App.",
55
"repository": {
66
"type": "git",
@@ -26,6 +26,8 @@
2626
"types": "./lib/react-app.d.ts",
2727
"dependencies": {
2828
"@babel/core": "7.9.0",
29+
"@loadable/babel-plugin": "^5.6.0",
30+
"@loadable/webpack-plugin": "^5.5.0",
2931
"@svgr/webpack": "4.3.3",
3032
"@typescript-eslint/eslint-plugin": "^2.10.0",
3133
"@typescript-eslint/parser": "^2.10.0",
@@ -41,6 +43,7 @@
4143
"dotenv-expand": "5.1.0",
4244
"file-loader": "4.3.0",
4345
"fs-extra": "^8.1.0",
46+
"hard-source-webpack-plugin": "^0.13.1",
4447
"html-webpack-plugin": "4.0.0-beta.11",
4548
"identity-obj-proxy": "3.0.0",
4649
"jest": "24.9.0",
@@ -49,6 +52,7 @@
4952
"jest-watch-typeahead": "0.4.2",
5053
"lodash": "^4.17.20",
5154
"mini-css-extract-plugin": "0.9.0",
55+
"mkdirp": "^1.0.3",
5256
"node-sass": "^4.12.0",
5357
"optimize-css-assets-webpack-plugin": "5.0.3",
5458
"pnp-webpack-plugin": "1.6.4",

0 commit comments

Comments
 (0)