mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Enable source maps in webpack chunking + bundling process (#3793)
* Removed combine-assets-plugin, since its very broken * Bundle everything into app.js on production build * Clean up * Removed app.js from server routes * Renamed app.js -> main.js and removed commons from loading * Remove commons and react CommonChunks * Removed the commons route * Killing the entire build-stats hack for app.js * Removed unused md5-file package
This commit is contained in:
parent
a225da4bb1
commit
76582b8e43
|
@ -83,7 +83,6 @@
|
||||||
"http-status": "1.0.1",
|
"http-status": "1.0.1",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
"loader-utils": "1.1.0",
|
"loader-utils": "1.1.0",
|
||||||
"md5-file": "3.2.3",
|
|
||||||
"minimist": "1.2.0",
|
"minimist": "1.2.0",
|
||||||
"mkdirp-then": "1.2.0",
|
"mkdirp-then": "1.2.0",
|
||||||
"mv": "2.1.1",
|
"mv": "2.1.1",
|
||||||
|
|
|
@ -5,7 +5,6 @@ import webpack from 'webpack'
|
||||||
import getConfig from '../config'
|
import getConfig from '../config'
|
||||||
import {PHASE_PRODUCTION_BUILD} from '../../lib/constants'
|
import {PHASE_PRODUCTION_BUILD} from '../../lib/constants'
|
||||||
import getBaseWebpackConfig from './webpack'
|
import getBaseWebpackConfig from './webpack'
|
||||||
import md5File from 'md5-file/promise'
|
|
||||||
|
|
||||||
export default async function build (dir, conf = null) {
|
export default async function build (dir, conf = null) {
|
||||||
const config = getConfig(PHASE_PRODUCTION_BUILD, dir, conf)
|
const config = getConfig(PHASE_PRODUCTION_BUILD, dir, conf)
|
||||||
|
@ -26,7 +25,6 @@ export default async function build (dir, conf = null) {
|
||||||
|
|
||||||
await runCompiler(configs)
|
await runCompiler(configs)
|
||||||
|
|
||||||
await writeBuildStats(dir, config)
|
|
||||||
await writeBuildId(dir, buildId, config)
|
await writeBuildId(dir, buildId, config)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`> Failed to build`)
|
console.error(`> Failed to build`)
|
||||||
|
@ -54,20 +52,6 @@ function runCompiler (compiler) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeBuildStats (dir, config) {
|
|
||||||
// Here we can't use hashes in webpack chunks.
|
|
||||||
// That's because the "app.js" is not tied to a chunk.
|
|
||||||
// It's created by merging a few assets. (commons.js and main.js)
|
|
||||||
// So, we need to generate the hash ourself.
|
|
||||||
const assetHashMap = {
|
|
||||||
'app.js': {
|
|
||||||
hash: await md5File(join(dir, config.distDir, 'app.js'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const buildStatsPath = join(dir, config.distDir, 'build-stats.json')
|
|
||||||
await fs.writeFile(buildStatsPath, JSON.stringify(assetHashMap), 'utf8')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writeBuildId (dir, buildId, config) {
|
async function writeBuildId (dir, buildId, config) {
|
||||||
const buildIdPath = join(dir, config.distDir, 'BUILD_ID')
|
const buildIdPath = join(dir, config.distDir, 'BUILD_ID')
|
||||||
await fs.writeFile(buildIdPath, buildId, 'utf8')
|
await fs.writeFile(buildIdPath, buildId, 'utf8')
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { ConcatSource } from 'webpack-sources'
|
|
||||||
|
|
||||||
// This plugin combines a set of assets into a single asset
|
|
||||||
// This should be only used with text assets,
|
|
||||||
// otherwise the result is unpredictable.
|
|
||||||
export default class CombineAssetsPlugin {
|
|
||||||
constructor ({ input, output }) {
|
|
||||||
this.input = input
|
|
||||||
this.output = output
|
|
||||||
}
|
|
||||||
|
|
||||||
apply (compiler) {
|
|
||||||
compiler.plugin('compilation', (compilation) => {
|
|
||||||
// This is triggered after uglify and other optimizers have ran.
|
|
||||||
compilation.plugin('after-optimize-chunk-assets', (chunks) => {
|
|
||||||
const concat = new ConcatSource()
|
|
||||||
|
|
||||||
this.input.forEach((name) => {
|
|
||||||
const asset = compilation.assets[name]
|
|
||||||
if (!asset) return
|
|
||||||
|
|
||||||
// We add each matched asset from this.input to a new bundle
|
|
||||||
concat.add(asset)
|
|
||||||
// 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
|
|
||||||
})
|
|
||||||
|
|
||||||
// Creates a new asset holding the concatted source
|
|
||||||
compilation.assets[this.output] = concat
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,6 @@ import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin'
|
||||||
import WriteFilePlugin from 'write-file-webpack-plugin'
|
import WriteFilePlugin from 'write-file-webpack-plugin'
|
||||||
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
|
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
|
||||||
import {getPages} from './webpack/utils'
|
import {getPages} from './webpack/utils'
|
||||||
import CombineAssetsPlugin from './plugins/combine-assets-plugin'
|
|
||||||
import PagesPlugin from './plugins/pages-plugin'
|
import PagesPlugin from './plugins/pages-plugin'
|
||||||
import NextJsSsrImportPlugin from './plugins/nextjs-ssr-import'
|
import NextJsSsrImportPlugin from './plugins/nextjs-ssr-import'
|
||||||
import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin'
|
import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin'
|
||||||
|
@ -248,53 +247,17 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production')
|
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production')
|
||||||
}),
|
}),
|
||||||
!isServer && new CombineAssetsPlugin({
|
|
||||||
input: ['manifest.js', 'react.js', 'commons.js', 'main.js'],
|
|
||||||
output: 'app.js'
|
|
||||||
}),
|
|
||||||
!dev && new webpack.optimize.ModuleConcatenationPlugin(),
|
!dev && new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
!isServer && new PagesPlugin(),
|
!isServer && new PagesPlugin(),
|
||||||
!isServer && new DynamicChunksPlugin(),
|
!isServer && new DynamicChunksPlugin(),
|
||||||
isServer && new NextJsSsrImportPlugin(),
|
isServer && new NextJsSsrImportPlugin(),
|
||||||
!isServer && new webpack.optimize.CommonsChunkPlugin({
|
// In dev mode, we don't move anything to the commons bundle.
|
||||||
name: `commons`,
|
// In production we move common modules into the existing main.js bundle
|
||||||
filename: `commons.js`,
|
!dev && !isServer && new webpack.optimize.CommonsChunkPlugin({
|
||||||
|
name: 'main.js',
|
||||||
|
filename: 'main.js',
|
||||||
minChunks (module, count) {
|
minChunks (module, count) {
|
||||||
// We need to move react-dom explicitly into common chunks.
|
// react
|
||||||
// Otherwise, if some other page or module uses it, it might
|
|
||||||
// included in that bundle too.
|
|
||||||
if (module.context && module.context.indexOf(`${sep}react${sep}`) >= 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module.context && module.context.indexOf(`${sep}react-dom${sep}`) >= 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the dev we use on-demand-entries.
|
|
||||||
// So, it makes no sense to use commonChunks based on the minChunks count.
|
|
||||||
// Instead, we move all the code in node_modules into each of the pages.
|
|
||||||
if (dev) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are one or two pages, only move modules to common if they are
|
|
||||||
// used in all of the pages. Otherwise, move modules used in at-least
|
|
||||||
// 1/2 of the total pages into commons.
|
|
||||||
if (totalPages <= 2) {
|
|
||||||
return count >= totalPages
|
|
||||||
}
|
|
||||||
return count >= totalPages * 0.5
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
!isServer && 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) {
|
if (module.resource && module.resource.includes(`${sep}react-dom${sep}`) && count >= 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -302,11 +265,21 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
if (module.resource && module.resource.includes(`${sep}react${sep}`) && count >= 0) {
|
if (module.resource && module.resource.includes(`${sep}react${sep}`) && count >= 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// react end
|
||||||
|
|
||||||
return false
|
// commons
|
||||||
|
// If there are one or two pages, only move modules to common if they are
|
||||||
|
// used in all of the pages. Otherwise, move modules used in at-least
|
||||||
|
// 1/2 of the total pages into commons.
|
||||||
|
if (totalPages <= 2) {
|
||||||
|
return count >= totalPages
|
||||||
|
}
|
||||||
|
return count >= totalPages * 0.5
|
||||||
|
// commons end
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
!isServer && new webpack.optimize.CommonsChunkPlugin({
|
// We use a manifest file in development to speed up HMR
|
||||||
|
dev && !isServer && new webpack.optimize.CommonsChunkPlugin({
|
||||||
name: 'manifest',
|
name: 'manifest',
|
||||||
filename: 'manifest.js'
|
filename: 'manifest.js'
|
||||||
})
|
})
|
||||||
|
|
|
@ -40,8 +40,8 @@ export class Head extends Component {
|
||||||
|
|
||||||
getChunkPreloadLink (filename) {
|
getChunkPreloadLink (filename) {
|
||||||
const { __NEXT_DATA__ } = this.context._documentProps
|
const { __NEXT_DATA__ } = this.context._documentProps
|
||||||
let { buildStats, assetPrefix, buildId } = __NEXT_DATA__
|
let { assetPrefix, buildId } = __NEXT_DATA__
|
||||||
const hash = buildStats ? buildStats[filename].hash : buildId
|
const hash = buildId
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<link
|
<link
|
||||||
|
@ -58,14 +58,13 @@ export class Head extends Component {
|
||||||
if (dev) {
|
if (dev) {
|
||||||
return [
|
return [
|
||||||
this.getChunkPreloadLink('manifest.js'),
|
this.getChunkPreloadLink('manifest.js'),
|
||||||
this.getChunkPreloadLink('commons.js'),
|
|
||||||
this.getChunkPreloadLink('main.js')
|
this.getChunkPreloadLink('main.js')
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the production mode, we have a single asset with all the JS content.
|
// In the production mode, we have a single asset with all the JS content.
|
||||||
return [
|
return [
|
||||||
this.getChunkPreloadLink('app.js')
|
this.getChunkPreloadLink('main.js')
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +125,8 @@ export class NextScript extends Component {
|
||||||
|
|
||||||
getChunkScript (filename, additionalProps = {}) {
|
getChunkScript (filename, additionalProps = {}) {
|
||||||
const { __NEXT_DATA__ } = this.context._documentProps
|
const { __NEXT_DATA__ } = this.context._documentProps
|
||||||
let { buildStats, assetPrefix, buildId } = __NEXT_DATA__
|
let { assetPrefix, buildId } = __NEXT_DATA__
|
||||||
const hash = buildStats ? buildStats[filename].hash : buildId
|
const hash = buildId
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<script
|
<script
|
||||||
|
@ -143,14 +142,13 @@ export class NextScript extends Component {
|
||||||
if (dev) {
|
if (dev) {
|
||||||
return [
|
return [
|
||||||
this.getChunkScript('manifest.js'),
|
this.getChunkScript('manifest.js'),
|
||||||
this.getChunkScript('commons.js'),
|
|
||||||
this.getChunkScript('main.js')
|
this.getChunkScript('main.js')
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the production mode, we have a single asset with all the JS content.
|
// In the production mode, we have a single asset with all the JS content.
|
||||||
// So, we can load the script with async
|
// So, we can load the script with async
|
||||||
return [this.getChunkScript('app.js', { async: true })]
|
return [this.getChunkScript('main.js', { async: true })]
|
||||||
}
|
}
|
||||||
|
|
||||||
getDynamicChunks () {
|
getDynamicChunks () {
|
||||||
|
|
|
@ -27,20 +27,12 @@ export default async function (dir, options, configuration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildId = readFileSync(join(nextDir, 'BUILD_ID'), 'utf8')
|
const buildId = readFileSync(join(nextDir, 'BUILD_ID'), 'utf8')
|
||||||
const buildStats = require(join(nextDir, 'build-stats.json'))
|
|
||||||
|
|
||||||
// Initialize the output directory
|
// Initialize the output directory
|
||||||
const outDir = options.outdir
|
const outDir = options.outdir
|
||||||
await del(join(outDir, '*'))
|
await del(join(outDir, '*'))
|
||||||
await mkdirp(join(outDir, '_next', buildStats['app.js'].hash))
|
|
||||||
await mkdirp(join(outDir, '_next', buildId))
|
await mkdirp(join(outDir, '_next', buildId))
|
||||||
|
|
||||||
// Copy files
|
|
||||||
await cp(
|
|
||||||
join(nextDir, 'app.js'),
|
|
||||||
join(outDir, '_next', buildStats['app.js'].hash, 'app.js')
|
|
||||||
)
|
|
||||||
|
|
||||||
// Copy static directory
|
// Copy static directory
|
||||||
if (existsSync(join(dir, 'static'))) {
|
if (existsSync(join(dir, 'static'))) {
|
||||||
log(' copying "static" directory')
|
log(' copying "static" directory')
|
||||||
|
@ -51,6 +43,12 @@ export default async function (dir, options, configuration) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy main.js
|
||||||
|
await cp(
|
||||||
|
join(nextDir, 'main.js'),
|
||||||
|
join(outDir, '_next', buildId, 'main.js')
|
||||||
|
)
|
||||||
|
|
||||||
// Copy .next/static directory
|
// Copy .next/static directory
|
||||||
if (existsSync(join(nextDir, 'static'))) {
|
if (existsSync(join(nextDir, 'static'))) {
|
||||||
log(' copying "static build" directory')
|
log(' copying "static build" directory')
|
||||||
|
@ -88,7 +86,6 @@ export default async function (dir, options, configuration) {
|
||||||
const renderOpts = {
|
const renderOpts = {
|
||||||
dir,
|
dir,
|
||||||
dist: nextConfig.distDir,
|
dist: nextConfig.distDir,
|
||||||
buildStats,
|
|
||||||
buildId,
|
buildId,
|
||||||
nextExport: true,
|
nextExport: true,
|
||||||
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
|
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
|
||||||
|
|
|
@ -49,8 +49,6 @@ export default class Server {
|
||||||
console.error(`> Could not find a valid build in the '${this.dist}' directory! Try building your app with 'next build' before starting the server.`)
|
console.error(`> Could not find a valid build in the '${this.dist}' directory! Try building your app with 'next build' before starting the server.`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.buildStats = !dev ? require(join(this.dir, this.dist, 'build-stats.json')) : null
|
|
||||||
this.buildId = !dev ? this.readBuildId() : '-'
|
this.buildId = !dev ? this.readBuildId() : '-'
|
||||||
this.renderOpts = {
|
this.renderOpts = {
|
||||||
dev,
|
dev,
|
||||||
|
@ -58,7 +56,6 @@ export default class Server {
|
||||||
dir: this.dir,
|
dir: this.dir,
|
||||||
dist: this.dist,
|
dist: this.dist,
|
||||||
hotReloader: this.hotReloader,
|
hotReloader: this.hotReloader,
|
||||||
buildStats: this.buildStats,
|
|
||||||
buildId: this.buildId,
|
buildId: this.buildId,
|
||||||
availableChunks: dev ? {} : getAvailableChunks(this.dir, this.dist)
|
availableChunks: dev ? {} : getAvailableChunks(this.dir, this.dist)
|
||||||
}
|
}
|
||||||
|
@ -170,27 +167,22 @@ export default class Server {
|
||||||
},
|
},
|
||||||
|
|
||||||
'/_next/:hash/main.js': async (req, res, params) => {
|
'/_next/:hash/main.js': async (req, res, params) => {
|
||||||
if (!this.dev) return this.send404(res)
|
if (this.dev) {
|
||||||
|
this.handleBuildHash('main.js', params.hash, res)
|
||||||
|
const p = join(this.dir, this.dist, 'main.js')
|
||||||
|
await this.serveStatic(req, res, p)
|
||||||
|
} else {
|
||||||
|
const buildId = params.hash
|
||||||
|
if (!this.handleBuildId(buildId, res)) {
|
||||||
|
const error = new Error('INVALID_BUILD_ID')
|
||||||
|
const customFields = { buildIdMismatched: true }
|
||||||
|
|
||||||
this.handleBuildHash('main.js', params.hash, res)
|
return await renderScriptError(req, res, '/_error', error, customFields, this.renderOpts)
|
||||||
const p = join(this.dir, this.dist, 'main.js')
|
}
|
||||||
await this.serveStatic(req, res, p)
|
|
||||||
},
|
|
||||||
|
|
||||||
'/_next/:hash/commons.js': async (req, res, params) => {
|
const p = join(this.dir, this.dist, 'main.js')
|
||||||
if (!this.dev) return this.send404(res)
|
await this.serveStatic(req, res, p)
|
||||||
|
}
|
||||||
this.handleBuildHash('commons.js', params.hash, res)
|
|
||||||
const p = join(this.dir, this.dist, 'commons.js')
|
|
||||||
await this.serveStatic(req, res, p)
|
|
||||||
},
|
|
||||||
|
|
||||||
'/_next/:hash/app.js': async (req, res, params) => {
|
|
||||||
if (this.dev) return this.send404(res)
|
|
||||||
|
|
||||||
this.handleBuildHash('app.js', params.hash, res)
|
|
||||||
const p = join(this.dir, this.dist, 'app.js')
|
|
||||||
await this.serveStatic(req, res, p)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'/_next/:buildId/page/:path*.js.map': async (req, res, params) => {
|
'/_next/:buildId/page/:path*.js.map': async (req, res, params) => {
|
||||||
|
@ -466,10 +458,6 @@ export default class Server {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hash !== this.buildStats[filename].hash) {
|
|
||||||
throw new Error(`Invalid Build File Hash(${hash}) for chunk: ${filename}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader('Cache-Control', 'max-age=31536000, immutable')
|
res.setHeader('Cache-Control', 'max-age=31536000, immutable')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ async function doRender (req, res, pathname, query, {
|
||||||
err,
|
err,
|
||||||
page,
|
page,
|
||||||
buildId,
|
buildId,
|
||||||
buildStats,
|
|
||||||
hotReloader,
|
hotReloader,
|
||||||
assetPrefix,
|
assetPrefix,
|
||||||
runtimeConfig,
|
runtimeConfig,
|
||||||
|
@ -108,7 +107,6 @@ async function doRender (req, res, pathname, query, {
|
||||||
pathname, // the requested path
|
pathname, // the requested path
|
||||||
query,
|
query,
|
||||||
buildId,
|
buildId,
|
||||||
buildStats,
|
|
||||||
assetPrefix,
|
assetPrefix,
|
||||||
runtimeConfig,
|
runtimeConfig,
|
||||||
nextExport,
|
nextExport,
|
||||||
|
|
|
@ -39,11 +39,11 @@ describe('Production Usage', () => {
|
||||||
|
|
||||||
describe('File locations', () => {
|
describe('File locations', () => {
|
||||||
it('should build the app within the given `dist` directory', () => {
|
it('should build the app within the given `dist` directory', () => {
|
||||||
expect(existsSync(join(__dirname, '/../dist/app.js'))).toBeTruthy()
|
expect(existsSync(join(__dirname, '/../dist/main.js'))).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not build the app within the default `.next` directory', () => {
|
it('should not build the app within the default `.next` directory', () => {
|
||||||
expect(existsSync(join(__dirname, '/../.next/app.js'))).toBeFalsy()
|
expect(existsSync(join(__dirname, '/../.next/main.js'))).toBeFalsy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue