new-webpack: functionize (#564)

Summary:
This will enable us to differentiate the production and development
behavior where necessary (primarily, only running minification in prod).

Best reviewed with `git show -w`.

Test Plan:
The diff with `git show -w` and the fact that `yarn flow` passes should
be sufficient. If you really want to be thorough, run Webpack with this
config file and `NODE_ENV` set to `production`.

wchargin-branch: webpack-functionize
This commit is contained in:
William Chargin 2018-07-30 16:22:55 -07:00 committed by GitHub
parent e2a94c2aa8
commit 7bf0ed3c84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 242 additions and 227 deletions

View File

@ -30,234 +30,249 @@ if (env.stringified["process.env"].NODE_ENV !== '"production"') {
// This is the production configuration. // This is the production configuration.
// It compiles slowly and is focused on producing a fast and minimal bundle. // It compiles slowly and is focused on producing a fast and minimal bundle.
// The development configuration is different and lives in a separate file. // The development configuration is different and lives in a separate file.
module.exports = { function makeConfig(_unused_mode /*: "production" | "development" */) {
// Don't attempt to continue if there are any errors. return {
bail: true, // Don't attempt to continue if there are any errors.
// We generate sourcemaps in production. This is slow but gives good results. bail: true,
// You can exclude the *.map files from the build during deployment. // We generate sourcemaps in production. This is slow but gives good results.
devtool: shouldUseSourceMap ? "source-map" : false, // You can exclude the *.map files from the build during deployment.
// In production, we only want to load the polyfills and the app code. devtool: shouldUseSourceMap ? "source-map" : false,
entry: { // In production, we only want to load the polyfills and the app code.
main: [require.resolve("./polyfills"), paths.appIndexJs], entry: {
ssr: [require.resolve("./polyfills"), paths.appServerSideRenderingIndexJs], main: [require.resolve("./polyfills"), paths.appIndexJs],
}, ssr: [
output: { require.resolve("./polyfills"),
// The build folder. paths.appServerSideRenderingIndexJs,
path: paths.appBuild, ],
// Generated JS file names (with nested folders). },
// There will be one main bundle, and one file per asynchronous chunk. output: {
// We don't currently advertise code splitting but Webpack supports it. // The build folder.
filename: "static/js/[name].[chunkhash:8].js", path: paths.appBuild,
chunkFilename: "static/js/[name].[chunkhash:8].chunk.js", // Generated JS file names (with nested folders).
// We inferred the "public path" (such as / or /my-project) from homepage. // There will be one main bundle, and one file per asynchronous chunk.
publicPath: publicPath, // We don't currently advertise code splitting but Webpack supports it.
// Point sourcemap entries to original disk location (format as URL on Windows) filename: "static/js/[name].[chunkhash:8].js",
devtoolModuleFilenameTemplate: ( chunkFilename: "static/js/[name].[chunkhash:8].chunk.js",
info /*: // We inferred the "public path" (such as / or /my-project) from homepage.
{| publicPath: publicPath,
// https://webpack.js.org/configuration/output/#output-devtoolmodulefilenametemplate // Point sourcemap entries to original disk location (format as URL on Windows)
+absoluteResourcePath: string, devtoolModuleFilenameTemplate: (
+allLoaders: string, info /*:
+hash: string, {|
+id: string, // https://webpack.js.org/configuration/output/#output-devtoolmodulefilenametemplate
+loaders: string, +absoluteResourcePath: string,
+resource: string, +allLoaders: string,
+resourcePath: string, +hash: string,
+namespace: string, +id: string,
|} +loaders: string,
*/ +resource: string,
) => +resourcePath: string,
path +namespace: string,
.relative(paths.appSrc, info.absoluteResourcePath) |}
.replace(/\\/g, "/"), */
// We need to use a UMD module to build the static site. ) =>
libraryTarget: "umd", path
}, .relative(paths.appSrc, info.absoluteResourcePath)
resolve: { .replace(/\\/g, "/"),
// This allows you to set a fallback for where Webpack should look for modules. // We need to use a UMD module to build the static site.
// We placed these paths second because we want `node_modules` to "win" libraryTarget: "umd",
// if there are any conflicts. This matches Node resolution mechanism. },
// https://github.com/facebookincubator/create-react-app/issues/253 resolve: {
modules: [ // This allows you to set a fallback for where Webpack should look for modules.
"node_modules", // We placed these paths second because we want `node_modules` to "win"
paths.appNodeModules, // if there are any conflicts. This matches Node resolution mechanism.
...(process.env.NODE_PATH || "").split(path.delimiter).filter(Boolean), // https://github.com/facebookincubator/create-react-app/issues/253
], modules: [
// These are the reasonable defaults supported by the Node ecosystem. "node_modules",
// We also include JSX as a common component filename extension to support paths.appNodeModules,
// some tools, although we do not recommend using it, see: ...(process.env.NODE_PATH || "").split(path.delimiter).filter(Boolean),
// https://github.com/facebookincubator/create-react-app/issues/290 ],
// `web` extension prefixes have been added for better support // These are the reasonable defaults supported by the Node ecosystem.
// for React Native Web. // We also include JSX as a common component filename extension to support
extensions: [".web.js", ".mjs", ".js", ".json", ".web.jsx", ".jsx"], // some tools, although we do not recommend using it, see:
alias: { // https://github.com/facebookincubator/create-react-app/issues/290
// Support React Native Web // `web` extension prefixes have been added for better support
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ // for React Native Web.
"react-native": "react-native-web", extensions: [".web.js", ".mjs", ".js", ".json", ".web.jsx", ".jsx"],
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
"react-native": "react-native-web",
},
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
module: {
strictExportPresence: true,
rules: [
// TODO: Disable require.ensure as it's not a standard language feature.
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
// { parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|jsx|mjs)$/,
enforce: "pre",
use: [
{
options: {
formatter: eslintFormatter,
eslintPath: require.resolve("eslint"),
},
loader: require.resolve("eslint-loader"),
},
],
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve("url-loader"),
options: {
limit: 10000,
name: "static/media/[name].[hash:8].[ext]",
},
},
// Process JS with Babel.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve("babel-loader"),
options: {
compact: true,
},
},
{
test: /\.css$/,
loader: "css-loader", // TODO(@wchargin): add csso-loader
},
// "file" loader makes sure assets end up in the `build` folder.
// When you `import` an asset, you get its filename.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve("file-loader"),
// Exclude `js` files to keep "css" loader working as it injects
// it's runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
options: {
name: "static/media/[name].[hash:8].[ext]",
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
}, },
plugins: [ plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/). new StaticSiteGeneratorPlugin({
// This often causes confusion because we only process files within src/ with babel. entry: "ssr",
// To fix this, we prevent you from importing files out of src/ -- if you'd like to, paths: require(paths.appRouteData).routeData.map(({path}) => path),
// please link the files into your node_modules/ and let module-resolution kick in. locals: {},
// Make sure your source files are compiled, as they will not be processed in any way. }),
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), // Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV was set to production here.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// Minify the code.
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebookincubator/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
},
mangle: {
safari10: true,
},
output: {
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebookincubator/create-react-app/issues/2488
ascii_only: true,
},
sourceMap: shouldUseSourceMap,
}),
// 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`.
new ManifestPlugin({
fileName: "asset-manifest.json",
}),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
new SWPrecacheWebpackPlugin({
// By default, a cache-busting query parameter is appended to requests
// used to populate the caches, to ensure the responses are fresh.
// If a URL is already hashed by Webpack, then there is no concern
// about it being stale, and the cache-busting can be skipped.
dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: "service-worker.js",
logger(message) {
if (message.indexOf("Total precache size is") === 0) {
// This message occurs for every build and is a bit too noisy.
return;
}
if (message.indexOf("Skipping static resource") === 0) {
// This message obscures real errors so we ignore it.
// https://github.com/facebookincubator/create-react-app/issues/2612
return;
}
console.log(message);
},
minify: true,
// For unknown URLs, fallback to the index page
navigateFallback: publicUrl + "/index.html",
// Ignores URLs starting from /__ (useful for Firebase):
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
navigateFallbackWhitelist: [/^(?!\/__).*/],
// Don't precache sourcemaps (they're large) and build asset manifest:
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
], ],
}, // Some libraries import Node modules but don't use them in the browser.
module: { // Tell Webpack to provide empty mocks for them so importing them works.
strictExportPresence: true, node: {
rules: [ dgram: "empty",
// TODO: Disable require.ensure as it's not a standard language feature. fs: "empty",
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. net: "empty",
// { parser: { requireEnsure: false } }, tls: "empty",
child_process: "empty",
},
};
}
// First, run the linter. function getMode() {
// It's important to do this before Babel processes the JS. const mode = process.env.NODE_ENV;
{ if (mode !== "production" && mode !== "development") {
test: /\.(js|jsx|mjs)$/, throw new Error("unknown mode: " + String(mode));
enforce: "pre", }
use: [ return mode;
{ }
options: {
formatter: eslintFormatter, module.exports = makeConfig(getMode());
eslintPath: require.resolve("eslint"),
},
loader: require.resolve("eslint-loader"),
},
],
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve("url-loader"),
options: {
limit: 10000,
name: "static/media/[name].[hash:8].[ext]",
},
},
// Process JS with Babel.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve("babel-loader"),
options: {
compact: true,
},
},
{
test: /\.css$/,
loader: "css-loader", // TODO(@wchargin): add csso-loader
},
// "file" loader makes sure assets end up in the `build` folder.
// When you `import` an asset, you get its filename.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve("file-loader"),
// Exclude `js` files to keep "css" loader working as it injects
// it's runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
options: {
name: "static/media/[name].[hash:8].[ext]",
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
new StaticSiteGeneratorPlugin({
entry: "ssr",
paths: require(paths.appRouteData).routeData.map(({path}) => path),
locals: {},
}),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV was set to production here.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// Minify the code.
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebookincubator/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
},
mangle: {
safari10: true,
},
output: {
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebookincubator/create-react-app/issues/2488
ascii_only: true,
},
sourceMap: shouldUseSourceMap,
}),
// 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`.
new ManifestPlugin({
fileName: "asset-manifest.json",
}),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
new SWPrecacheWebpackPlugin({
// By default, a cache-busting query parameter is appended to requests
// used to populate the caches, to ensure the responses are fresh.
// If a URL is already hashed by Webpack, then there is no concern
// about it being stale, and the cache-busting can be skipped.
dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: "service-worker.js",
logger(message) {
if (message.indexOf("Total precache size is") === 0) {
// This message occurs for every build and is a bit too noisy.
return;
}
if (message.indexOf("Skipping static resource") === 0) {
// This message obscures real errors so we ignore it.
// https://github.com/facebookincubator/create-react-app/issues/2612
return;
}
console.log(message);
},
minify: true,
// For unknown URLs, fallback to the index page
navigateFallback: publicUrl + "/index.html",
// Ignores URLs starting from /__ (useful for Firebase):
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
navigateFallbackWhitelist: [/^(?!\/__).*/],
// Don't precache sourcemaps (they're large) and build asset manifest:
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: "empty",
fs: "empty",
net: "empty",
tls: "empty",
child_process: "empty",
},
};