Skip to content

BRS 7 - Full SSR Hard Source and Loadable support #77

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5271192
[NO JIRA]: Adding loadable component support
olliecurtis Feb 28, 2020
a22a5a4
Output ssr and web chunks to different folders (#73)
olliecurtis Mar 5, 2020
e9397e2
* Add support for development env SSR (start-ssr)
ajones513 Mar 13, 2020
fcbd150
CSS loaders for browserless env, hard source excludes as recommended
ajones513 Mar 13, 2020
6efddf0
Build marker files, hash JS to prevent in-place modification on code …
ajones513 Mar 13, 2020
e1a1f1c
iconv ignore
ajones513 Mar 13, 2020
ace0ea8
No mini css extract plugin for SSR
ajones513 Mar 14, 2020
e249d51
Nicer diff
ajones513 Mar 16, 2020
10a9395
whitespace
ajones513 Mar 16, 2020
84e8580
Whitespace
ajones513 Mar 16, 2020
d9578f8
require.resolve babel plugin
ajones513 Mar 16, 2020
6cefe2f
Register hooks first, for both SSR and non-SSR
ajones513 Mar 16, 2020
722bc62
Status file tweaks
ajones513 Mar 16, 2020
ed4062e
Make hard source plugin switchable
ajones513 Mar 17, 2020
ad4c053
Hard source environmentHash allowing for standalone BRS install optim…
ajones513 Mar 25, 2020
6f76253
Fix iconv loader
ajones513 Apr 2, 2020
83e5434
iconv fix needed for non-SSR too to prevent warning
ajones513 Apr 3, 2020
9cd3c7e
Merge pull request #1 from ajones513/iconv-fix
ajones513 Apr 7, 2020
a3d057e
Update CHANGELOG.md
ajones513 Apr 17, 2020
588ef25
Update CHANGELOG.md
ajones513 Apr 17, 2020
2d52632
Update CHANGELOG.md
ajones513 Apr 17, 2020
71ed294
Adding new ignore option
olliecurtis Jun 24, 2020
21c0119
Merge pull request #2 from olliecurtis/custom
olliecurtis Aug 24, 2020
6d0ee70
Adding colors to noParse
olliecurtis Sep 23, 2020
b958eca
Revert - Adding colors to noParse
olliecurtis Sep 23, 2020
f4cb8c3
[NO JIRA]: Backport updates
olliecurtis Jun 15, 2021
c084cf6
Version updates
olliecurtis Jun 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/react-scripts/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# `backpack-react-scripts` Change Log

## 7.0.6 (Pending)

- Added support for loadable components.

## 7.0.5 - 2020-01-10

### Fixed
Expand Down
3 changes: 2 additions & 1 deletion packages/react-scripts/bin/react-scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) : [];
Expand All @@ -28,6 +28,7 @@ switch (script) {
case 'build':
case 'eject':
case 'start':
case 'start-ssr':
case 'test': {
const result = spawn.sync(
'node',
Expand Down
15 changes: 15 additions & 0 deletions packages/react-scripts/config/environmentHash.js
Original file line number Diff line number Diff line change
@@ -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'],
};
9 changes: 6 additions & 3 deletions packages/react-scripts/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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'),
Expand Down Expand Up @@ -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'),
Expand Down
41 changes: 34 additions & 7 deletions packages/react-scripts/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -55,6 +57,10 @@ 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 =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than an env variable this might be better as a config value instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it's experimental (mainly because mzgoddard/hard-source-webpack-plugin#419 + waiting for Webpack 5), I've found value in flipping it on/off at will - so was thinking the environment variables maybe gave more flexibility.

If there's cleaner way without needing to make a code change in the consuming project that would still be good.

process.env.USE_HARD_SOURCE_WEBPACK_PLUGIN === 'true';
const environmentHash = require('./environmentHash');

// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
Expand Down Expand Up @@ -183,7 +189,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.
Expand Down Expand Up @@ -281,11 +287,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
Expand Down Expand Up @@ -337,6 +345,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.
Expand Down Expand Up @@ -429,6 +438,7 @@ module.exports = function(webpackEnv) {
),
// @remove-on-eject-end
plugins: [
require.resolve('@loadable/babel-plugin'),
[
require.resolve('babel-plugin-named-asset-import'),
{
Expand Down Expand Up @@ -602,6 +612,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 <script> injected.
new HtmlWebpackPlugin(
Object.assign(
Expand Down Expand Up @@ -668,7 +692,10 @@ module.exports = function(webpackEnv) {
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
new webpack.DefinePlugin({
...env.stringified,
'typeof window': '"object"',
}),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
Expand Down
77 changes: 51 additions & 26 deletions packages/react-scripts/config/webpack.config.ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PnpWebpackPlugin = require('pnp-webpack-plugin');
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 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');
Expand All @@ -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);
Expand All @@ -54,6 +56,10 @@ 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 =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

process.env.USE_HARD_SOURCE_WEBPACK_PLUGIN === 'true';
const environmentHash = require('./environmentHash');

// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
Expand Down Expand Up @@ -82,7 +88,7 @@ module.exports = function(webpackEnv) {
: isEnvDevelopment && '/';
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
// const shouldUseRelativeAssetPaths = publicPath === './';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm why this has been removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was only used by MiniCssExtractPlugin


// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
Expand All @@ -100,16 +106,16 @@ module.exports = function(webpackEnv) {
preProcessorOptions = {}
) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
options: Object.assign(
{},
shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
),
},
// isEnvDevelopment && require.resolve('style-loader'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm why this has been removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In standard CRA, in development, styles are served client-side only, via the Webpack dev server. The CSS is served inside the .js assets.

I didn't want to get into changing that - so in development, SSR should just ignore styles completely.

// isEnvProduction && {
// loader: MiniCssExtractPlugin.loader,
// options: Object.assign(
// {},
// shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
// ),
// },
{
loader: require.resolve('css-loader'),
loader: require.resolve('css-loader/locals'),
options: cssOptions,
},
{
Expand Down Expand Up @@ -171,8 +177,8 @@ module.exports = function(webpackEnv) {
// 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'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// isEnvDevelopment &&
// require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
// paths.appIndexJs,
paths.appSsrJs,
Expand All @@ -182,20 +188,20 @@ module.exports = function(webpackEnv) {
].filter(Boolean),
output: {
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
path: paths.appBuildSsr,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
// filename: isEnvProduction
// ? 'static/js/[name].[chunkhash:8].js'
// : isEnvDevelopment && 'static/js/bundle.js',
filename: 'ssr.js',
filename: 'ssr.[hash:8].js',
libraryTarget: 'commonjs2',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[chunkhash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
: isEnvDevelopment && 'static/js/[name].[chunkhash:8].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development.
publicPath: publicPath,
Expand Down Expand Up @@ -344,6 +350,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.
Expand Down Expand Up @@ -436,6 +443,7 @@ module.exports = function(webpackEnv) {
),
// @remove-on-eject-end
plugins: [
require.resolve('@loadable/babel-plugin'),
[
require.resolve('babel-plugin-named-asset-import'),
{
Expand Down Expand Up @@ -609,6 +617,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 <script> injected.
// new HtmlWebpackPlugin(
// Object.assign(
Expand Down Expand Up @@ -655,9 +677,12 @@ module.exports = function(webpackEnv) {
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
new webpack.DefinePlugin({
...env.stringified,
'typeof window': '"undefined"',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm what this is doing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://webpack.js.org/plugins/define-plugin/#usage - defining typeof window is the closest I've seen to a 'standard' way of stripping out client/server-only code from their respective bundles.

}),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm why this has been removed?

Copy link
Author

@ajones513 ajones513 Apr 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above re: styles, and I don't think https://webpack.js.org/concepts/hot-module-replacement/ is designed for the server.

// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
Expand All @@ -668,14 +693,14 @@ module.exports = function(webpackEnv) {
// See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
// filename: 'static/css/[name].[contenthash:8].css',
filename: 'ssr.css',
// chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
// isEnvProduction &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm why this has been removed? Guessing it relates to the HardSourcePlugin?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just the styles again.

// new MiniCssExtractPlugin({
// // Options similar to the same options in webpackOptions.output
// // both options are optional
// // filename: 'static/css/[name].[contenthash:8].css',
// filename: 'ssr.css',
// // chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
// }),
// Generate a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
Expand Down
4 changes: 4 additions & 0 deletions packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"types": "./lib/react-app.d.ts",
"dependencies": {
"@babel/core": "7.2.2",
"@loadable/babel-plugin": "^5.6.0",
"@loadable/webpack-plugin": "^5.5.0",
"@svgr/webpack": "2.4.1",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "23.6.0",
Expand All @@ -38,13 +40,15 @@
"file-loader": "2.0.0",
"fork-ts-checker-webpack-plugin-alt": "0.4.14",
"fs-extra": "7.0.1",
"hard-source-webpack-plugin": "^0.13.1",
"html-webpack-plugin": "4.0.0-alpha.2",
"identity-obj-proxy": "3.0.0",
"jest": "23.6.0",
"jest-pnp-resolver": "1.0.2",
"jest-resolve": "23.6.0",
"lodash": "^4.17.11",
"mini-css-extract-plugin": "0.4.3",
"mkdirp": "^1.0.3",
"node-sass": "4.12.0",
"optimize-css-assets-webpack-plugin": "5.0.1",
"pnp-webpack-plugin": "1.1.0",
Expand Down
Loading