1
0
Fork 0
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:
Tomas Roos 2018-03-06 10:45:29 +01:00 committed by Tim Neutkens
parent a225da4bb1
commit 76582b8e43
9 changed files with 46 additions and 142 deletions

View file

@ -83,7 +83,6 @@
"http-status": "1.0.1",
"json-loader": "0.5.7",
"loader-utils": "1.1.0",
"md5-file": "3.2.3",
"minimist": "1.2.0",
"mkdirp-then": "1.2.0",
"mv": "2.1.1",

View file

@ -5,7 +5,6 @@ import webpack from 'webpack'
import getConfig from '../config'
import {PHASE_PRODUCTION_BUILD} from '../../lib/constants'
import getBaseWebpackConfig from './webpack'
import md5File from 'md5-file/promise'
export default async function build (dir, conf = null) {
const config = getConfig(PHASE_PRODUCTION_BUILD, dir, conf)
@ -26,7 +25,6 @@ export default async function build (dir, conf = null) {
await runCompiler(configs)
await writeBuildStats(dir, config)
await writeBuildId(dir, buildId, config)
} catch (err) {
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) {
const buildIdPath = join(dir, config.distDir, 'BUILD_ID')
await fs.writeFile(buildIdPath, buildId, 'utf8')

View file

@ -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
})
})
}
}

View file

@ -6,7 +6,6 @@ import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin'
import WriteFilePlugin from 'write-file-webpack-plugin'
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
import {getPages} from './webpack/utils'
import CombineAssetsPlugin from './plugins/combine-assets-plugin'
import PagesPlugin from './plugins/pages-plugin'
import NextJsSsrImportPlugin from './plugins/nextjs-ssr-import'
import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin'
@ -248,53 +247,17 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
new webpack.DefinePlugin({
'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(),
!isServer && new PagesPlugin(),
!isServer && new DynamicChunksPlugin(),
isServer && new NextJsSsrImportPlugin(),
!isServer && new webpack.optimize.CommonsChunkPlugin({
name: `commons`,
filename: `commons.js`,
// In dev mode, we don't move anything to the commons bundle.
// In production we move common modules into the existing main.js bundle
!dev && !isServer && new webpack.optimize.CommonsChunkPlugin({
name: 'main.js',
filename: 'main.js',
minChunks (module, count) {
// We need to move react-dom explicitly into common chunks.
// 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
}
// react
if (module.resource && module.resource.includes(`${sep}react-dom${sep}`) && count >= 0) {
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) {
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',
filename: 'manifest.js'
})

View file

@ -40,8 +40,8 @@ export class Head extends Component {
getChunkPreloadLink (filename) {
const { __NEXT_DATA__ } = this.context._documentProps
let { buildStats, assetPrefix, buildId } = __NEXT_DATA__
const hash = buildStats ? buildStats[filename].hash : buildId
let { assetPrefix, buildId } = __NEXT_DATA__
const hash = buildId
return (
<link
@ -58,14 +58,13 @@ export class Head extends Component {
if (dev) {
return [
this.getChunkPreloadLink('manifest.js'),
this.getChunkPreloadLink('commons.js'),
this.getChunkPreloadLink('main.js')
]
}
// In the production mode, we have a single asset with all the JS content.
return [
this.getChunkPreloadLink('app.js')
this.getChunkPreloadLink('main.js')
]
}
@ -126,8 +125,8 @@ export class NextScript extends Component {
getChunkScript (filename, additionalProps = {}) {
const { __NEXT_DATA__ } = this.context._documentProps
let { buildStats, assetPrefix, buildId } = __NEXT_DATA__
const hash = buildStats ? buildStats[filename].hash : buildId
let { assetPrefix, buildId } = __NEXT_DATA__
const hash = buildId
return (
<script
@ -143,14 +142,13 @@ export class NextScript extends Component {
if (dev) {
return [
this.getChunkScript('manifest.js'),
this.getChunkScript('commons.js'),
this.getChunkScript('main.js')
]
}
// In the production mode, we have a single asset with all the JS content.
// So, we can load the script with async
return [this.getChunkScript('app.js', { async: true })]
return [this.getChunkScript('main.js', { async: true })]
}
getDynamicChunks () {

View file

@ -27,20 +27,12 @@ export default async function (dir, options, configuration) {
}
const buildId = readFileSync(join(nextDir, 'BUILD_ID'), 'utf8')
const buildStats = require(join(nextDir, 'build-stats.json'))
// Initialize the output directory
const outDir = options.outdir
await del(join(outDir, '*'))
await mkdirp(join(outDir, '_next', buildStats['app.js'].hash))
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
if (existsSync(join(dir, 'static'))) {
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
if (existsSync(join(nextDir, 'static'))) {
log(' copying "static build" directory')
@ -88,7 +86,6 @@ export default async function (dir, options, configuration) {
const renderOpts = {
dir,
dist: nextConfig.distDir,
buildStats,
buildId,
nextExport: true,
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),

View file

@ -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.`)
process.exit(1)
}
this.buildStats = !dev ? require(join(this.dir, this.dist, 'build-stats.json')) : null
this.buildId = !dev ? this.readBuildId() : '-'
this.renderOpts = {
dev,
@ -58,7 +56,6 @@ export default class Server {
dir: this.dir,
dist: this.dist,
hotReloader: this.hotReloader,
buildStats: this.buildStats,
buildId: this.buildId,
availableChunks: dev ? {} : getAvailableChunks(this.dir, this.dist)
}
@ -170,27 +167,22 @@ export default class Server {
},
'/_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)
const p = join(this.dir, this.dist, 'main.js')
await this.serveStatic(req, res, p)
},
return await renderScriptError(req, res, '/_error', error, customFields, this.renderOpts)
}
'/_next/:hash/commons.js': async (req, res, params) => {
if (!this.dev) return this.send404(res)
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)
const p = join(this.dir, this.dist, 'main.js')
await this.serveStatic(req, res, p)
}
},
'/_next/:buildId/page/:path*.js.map': async (req, res, params) => {
@ -466,10 +458,6 @@ export default class Server {
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')
return true
}

View file

@ -37,7 +37,6 @@ async function doRender (req, res, pathname, query, {
err,
page,
buildId,
buildStats,
hotReloader,
assetPrefix,
runtimeConfig,
@ -108,7 +107,6 @@ async function doRender (req, res, pathname, query, {
pathname, // the requested path
query,
buildId,
buildStats,
assetPrefix,
runtimeConfig,
nextExport,

View file

@ -39,11 +39,11 @@ describe('Production Usage', () => {
describe('File locations', () => {
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', () => {
expect(existsSync(join(__dirname, '/../.next/app.js'))).toBeFalsy()
expect(existsSync(join(__dirname, '/../.next/main.js'))).toBeFalsy()
})
})
})