mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Clean up webpack config, increase build speed (#3487)
* Speed up next build * Document webpack config * Speed up next build * Remove comment * Add comment * Clean up rules * Add comments * Run in parallel * Push plugins seperately * Create a new chunk for react * Don’t uglify react since it’s already uglified. Move react to commons in development * Use the minified version directly * Re-add globpattern * Move loaders into a separate variable * Add comment linking to Dan’s explanation * Remove dot
This commit is contained in:
parent
ffe48048de
commit
57a0fc432c
|
@ -21,7 +21,6 @@ export default class CombineAssetsPlugin {
|
||||||
|
|
||||||
// We add each matched asset from this.input to a new bundle
|
// We add each matched asset from this.input to a new bundle
|
||||||
concat.add(asset)
|
concat.add(asset)
|
||||||
|
|
||||||
// The original assets are kept because they show up when analyzing the bundle using webpack-bundle-analyzer
|
// The original assets are kept because they show up when analyzing the bundle using webpack-bundle-analyzer
|
||||||
// See https://github.com/zeit/next.js/tree/canary/examples/with-webpack-bundle-analyzer
|
// See https://github.com/zeit/next.js/tree/canary/examples/with-webpack-bundle-analyzer
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,42 +29,25 @@ const interpolateNames = new Map(defaultPages.map((p) => {
|
||||||
|
|
||||||
const relativeResolve = rootModuleRelativePath(require)
|
const relativeResolve = rootModuleRelativePath(require)
|
||||||
|
|
||||||
export default async function createCompiler (dir, { buildId, dev = false, quiet = false, buildDir, conf = null } = {}) {
|
async function getPages ({dir, dev, pagesGlobPattern}) {
|
||||||
dir = realpathSync(resolve(dir))
|
let pages
|
||||||
const config = getConfig(dir, conf)
|
|
||||||
const defaultEntries = dev ? [
|
|
||||||
join(__dirname, '..', '..', 'client', 'webpack-hot-middleware-client'),
|
|
||||||
join(__dirname, '..', '..', 'client', 'on-demand-entries-client')
|
|
||||||
] : []
|
|
||||||
const mainJS = dev
|
|
||||||
? require.resolve('../../client/next-dev') : require.resolve('../../client/next')
|
|
||||||
|
|
||||||
let totalPages
|
|
||||||
|
|
||||||
const entry = async () => {
|
|
||||||
const entries = {
|
|
||||||
'main.js': [
|
|
||||||
...defaultEntries,
|
|
||||||
...config.clientBootstrap || [],
|
|
||||||
mainJS
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const pages = await glob(config.pagesGlobPattern, { cwd: dir })
|
|
||||||
const devPages = pages.filter((p) => p === 'pages/_document.js' || p === 'pages/_error.js')
|
|
||||||
|
|
||||||
// In the dev environment, on-demand-entry-handler will take care of
|
|
||||||
// managing pages.
|
|
||||||
if (dev) {
|
if (dev) {
|
||||||
for (const p of devPages) {
|
pages = await glob('pages/+(_document|_error).+(js|jsx)', { cwd: dir })
|
||||||
entries[join('bundles', p.replace('.jsx', '.js'))] = [`./${p}?entry`]
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
pages = await glob(pagesGlobPattern, { cwd: dir })
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPageEntries (pages) {
|
||||||
|
const entries = {}
|
||||||
for (const p of pages) {
|
for (const p of pages) {
|
||||||
entries[join('bundles', p.replace('.jsx', '.js'))] = [`./${p}?entry`]
|
entries[join('bundles', p.replace('.jsx', '.js'))] = [`./${p}?entry`]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// The default pages (_document.js and _error.js) are only added when they're not provided by the user
|
||||||
for (const p of defaultPages) {
|
for (const p of defaultPages) {
|
||||||
const entryName = join('bundles', 'pages', p)
|
const entryName = join('bundles', 'pages', p)
|
||||||
if (!entries[entryName]) {
|
if (!entries[entryName]) {
|
||||||
|
@ -72,13 +55,58 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
totalPages = pages.filter((p) => p !== documentPage).length
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function createCompiler (dir, { buildId, dev = false, quiet = false, buildDir, conf = null } = {}) {
|
||||||
|
// Resolve relative path to absolute path
|
||||||
|
dir = realpathSync(resolve(dir))
|
||||||
|
|
||||||
|
// Used to track the amount of pages for webpack commons chunk plugin
|
||||||
|
let totalPages
|
||||||
|
|
||||||
|
// Loads next.config.js and custom configuration provided in custom server initialization
|
||||||
|
const config = getConfig(dir, conf)
|
||||||
|
|
||||||
|
// Middlewares to handle on-demand entries and hot updates in development
|
||||||
|
const devEntries = dev ? [
|
||||||
|
join(__dirname, '..', '..', 'client', 'webpack-hot-middleware-client'),
|
||||||
|
join(__dirname, '..', '..', 'client', 'on-demand-entries-client')
|
||||||
|
] : []
|
||||||
|
|
||||||
|
const mainJS = require.resolve(`../../client/next${dev ? '-dev' : ''}`) // Uses client/next-dev in development for code splitting dev dependencies
|
||||||
|
|
||||||
|
const entry = async () => {
|
||||||
|
// Get entries for pages in production mode. In development only _document and _error are added. Because pages are added by on-demand-entry-handler.
|
||||||
|
const pages = await getPages({dir, dev, pagesGlobPattern: config.pagesGlobPattern})
|
||||||
|
const pageEntries = getPageEntries(pages)
|
||||||
|
|
||||||
|
// Used for commons chunk calculations
|
||||||
|
totalPages = pages.length
|
||||||
|
if (pages.indexOf(documentPage) !== -1) {
|
||||||
|
totalPages = totalPages - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = {
|
||||||
|
'main.js': [
|
||||||
|
...devEntries, // Adds hot middleware and ondemand entries in development
|
||||||
|
...config.clientBootstrap || [], // clientBootstrap can be used to load polyfills before code execution
|
||||||
|
mainJS // Main entrypoint in the client folder
|
||||||
|
],
|
||||||
|
...pageEntries
|
||||||
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
|
// Defines NODE_ENV as development/production. This is used by some npm modules to determine if they should optimize.
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production')
|
||||||
|
}),
|
||||||
|
new CaseSensitivePathPlugin(), // Since on macOS the filesystem is case-insensitive this will make sure your path are case-sensitive
|
||||||
new webpack.IgnorePlugin(/(precomputed)/, /node_modules.+(elliptic)/),
|
new webpack.IgnorePlugin(/(precomputed)/, /node_modules.+(elliptic)/),
|
||||||
|
// Provide legacy options to webpack
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
options: {
|
options: {
|
||||||
context: dir,
|
context: dir,
|
||||||
|
@ -87,12 +115,14 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
// Writes all generated files to disk, even in development. For SSR.
|
||||||
new WriteFilePlugin({
|
new WriteFilePlugin({
|
||||||
exitOnErrors: false,
|
exitOnErrors: false,
|
||||||
log: false,
|
log: false,
|
||||||
// required not to cache removed files
|
// required not to cache removed files
|
||||||
useHashIndex: false
|
useHashIndex: false
|
||||||
}),
|
}),
|
||||||
|
// Moves common modules into commons.js
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
name: 'commons',
|
name: 'commons',
|
||||||
filename: 'commons.js',
|
filename: 'commons.js',
|
||||||
|
@ -100,7 +130,11 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
// We need to move react-dom explicitly into common chunks.
|
// We need to move react-dom explicitly into common chunks.
|
||||||
// Otherwise, if some other page or module uses it, it might
|
// Otherwise, if some other page or module uses it, it might
|
||||||
// included in that bundle too.
|
// included in that bundle too.
|
||||||
if (module.context && module.context.indexOf(`${sep}react-dom${sep}`) >= 0) {
|
if (dev && module.context && module.context.indexOf(`${sep}react${sep}`) >= 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev && module.context && module.context.indexOf(`${sep}react-dom${sep}`) >= 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +154,27 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
return count >= totalPages * 0.5
|
return count >= totalPages * 0.5
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
// This chunk splits out react and react-dom in production to make sure it does not go through uglify. This saved multiple seconds on production builds.
|
||||||
|
// See https://twitter.com/dan_abramov/status/944040306420408325
|
||||||
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
|
name: 'react',
|
||||||
|
filename: 'react.js',
|
||||||
|
minChunks (module, count) {
|
||||||
|
if (dev) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module.resource && module.resource.includes(`${sep}react-dom${sep}`) && count >= 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module.resource && module.resource.includes(`${sep}react${sep}`) && count >= 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}),
|
||||||
// This chunk contains all the webpack related code. So, all the changes
|
// This chunk contains all the webpack related code. So, all the changes
|
||||||
// related to that happens to this chunk.
|
// related to that happens to this chunk.
|
||||||
// It won't touch commons.js and that gives us much better re-build perf.
|
// It won't touch commons.js and that gives us much better re-build perf.
|
||||||
|
@ -127,12 +182,11 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
name: 'manifest',
|
name: 'manifest',
|
||||||
filename: 'manifest.js'
|
filename: 'manifest.js'
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production')
|
// This adds Next.js route definitions to page bundles
|
||||||
}),
|
|
||||||
new PagesPlugin(),
|
new PagesPlugin(),
|
||||||
new DynamicChunksPlugin(),
|
// Implements support for dynamic imports
|
||||||
new CaseSensitivePathPlugin()
|
new DynamicChunksPlugin()
|
||||||
]
|
]
|
||||||
|
|
||||||
if (dev) {
|
if (dev) {
|
||||||
|
@ -147,11 +201,9 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
} else {
|
} else {
|
||||||
plugins.push(new webpack.IgnorePlugin(/react-hot-loader/))
|
plugins.push(new webpack.IgnorePlugin(/react-hot-loader/))
|
||||||
plugins.push(
|
plugins.push(
|
||||||
new CombineAssetsPlugin({
|
// Minifies javascript bundles
|
||||||
input: ['manifest.js', 'commons.js', 'main.js'],
|
|
||||||
output: 'app.js'
|
|
||||||
}),
|
|
||||||
new UglifyJSPlugin({
|
new UglifyJSPlugin({
|
||||||
|
exclude: /react\.js/,
|
||||||
parallel: true,
|
parallel: true,
|
||||||
sourceMap: false,
|
sourceMap: false,
|
||||||
uglifyOptions: {
|
uglifyOptions: {
|
||||||
|
@ -161,6 +213,14 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
plugins.push(
|
||||||
|
// Combines manifest.js commons.js and main.js into app.js in production
|
||||||
|
new CombineAssetsPlugin({
|
||||||
|
input: ['manifest.js', 'react.js', 'commons.js', 'main.js'],
|
||||||
|
output: 'app.js'
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// Implements scope hoisting which speeds up browser execution of javascript
|
||||||
plugins.push(new webpack.optimize.ModuleConcatenationPlugin())
|
plugins.push(new webpack.optimize.ModuleConcatenationPlugin())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +251,7 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
mainBabelOptions.presets.push(require.resolve('./babel/preset'))
|
mainBabelOptions.presets.push(require.resolve('./babel/preset'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules = (dev ? [{
|
const devLoaders = dev ? [{
|
||||||
test: /\.(js|jsx)(\?[^?]*)?$/,
|
test: /\.(js|jsx)(\?[^?]*)?$/,
|
||||||
loader: 'hot-self-accept-loader',
|
loader: 'hot-self-accept-loader',
|
||||||
include: [
|
include: [
|
||||||
|
@ -202,8 +262,9 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
test: /\.(js|jsx)(\?[^?]*)?$/,
|
test: /\.(js|jsx)(\?[^?]*)?$/,
|
||||||
loader: 'react-hot-loader/webpack',
|
loader: 'react-hot-loader/webpack',
|
||||||
exclude: /node_modules/
|
exclude: /node_modules/
|
||||||
}] : [])
|
}] : []
|
||||||
.concat([{
|
|
||||||
|
const loaders = [{
|
||||||
test: /\.json$/,
|
test: /\.json$/,
|
||||||
loader: 'json-loader'
|
loader: 'json-loader'
|
||||||
}, {
|
}, {
|
||||||
|
@ -316,7 +377,7 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
return /node_modules/.test(str)
|
return /node_modules/.test(str)
|
||||||
},
|
},
|
||||||
options: mainBabelOptions
|
options: mainBabelOptions
|
||||||
}])
|
}]
|
||||||
|
|
||||||
let webpackConfig = {
|
let webpackConfig = {
|
||||||
context: dir,
|
context: dir,
|
||||||
|
@ -339,6 +400,11 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
chunkFilename: '[name]-[chunkhash].js'
|
chunkFilename: '[name]-[chunkhash].js'
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
// This bypasses React's check for production mode. Since we know it is in production this way.
|
||||||
|
// This allows us to exclude React from being uglified. Saving multiple seconds per build.
|
||||||
|
'react-dom': dev ? 'react-dom/cjs/react-dom.development.js' : 'react-dom/cjs/react-dom.production.min.js'
|
||||||
|
},
|
||||||
extensions: ['.js', '.jsx', '.json'],
|
extensions: ['.js', '.jsx', '.json'],
|
||||||
modules: [
|
modules: [
|
||||||
nextNodeModulesDir,
|
nextNodeModulesDir,
|
||||||
|
@ -356,7 +422,10 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
||||||
},
|
},
|
||||||
plugins,
|
plugins,
|
||||||
module: {
|
module: {
|
||||||
rules
|
rules: [
|
||||||
|
...devLoaders,
|
||||||
|
...loaders
|
||||||
|
]
|
||||||
},
|
},
|
||||||
devtool: dev ? 'cheap-module-inline-source-map' : false,
|
devtool: dev ? 'cheap-module-inline-source-map' : false,
|
||||||
performance: { hints: false }
|
performance: { hints: false }
|
||||||
|
|
Loading…
Reference in a new issue