Skip to content
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

Enable source maps in webpack chunking + bundling process #3793

Merged
merged 13 commits into from Mar 6, 2018
Merged
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -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",
Expand Down
16 changes: 0 additions & 16 deletions server/build/index.js
Expand Up @@ -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)
Expand All @@ -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`)
Expand Down Expand Up @@ -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')
Expand Down
33 changes: 0 additions & 33 deletions server/build/plugins/combine-assets-plugin.js

This file was deleted.

53 changes: 13 additions & 40 deletions server/build/webpack.js
Expand Up @@ -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'
Expand Down Expand Up @@ -248,65 +247,39 @@ 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) {
// react
if (module.resource && module.resource.includes(`${sep}react-dom${sep}`) && count >= 0) {
return true
}

if (module.context && module.context.indexOf(`${sep}react-dom${sep}`) >= 0) {
if (module.resource && module.resource.includes(`${sep}react${sep}`) && count >= 0) {
return true
}
// react end

// 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
}

// 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({
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
}
}),
!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'
})
Expand Down
14 changes: 6 additions & 8 deletions server/document.js
Expand Up @@ -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
Expand All @@ -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')
]
}

Expand Down Expand Up @@ -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
Expand All @@ -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 () {
Expand Down
15 changes: 6 additions & 9 deletions server/export.js
Expand Up @@ -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')
Expand All @@ -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')
Expand Down Expand Up @@ -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(/\/$/, ''),
Expand Down
42 changes: 15 additions & 27 deletions server/index.js
Expand Up @@ -49,16 +49,13 @@ 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,
staticMarkup,
dir: this.dir,
dist: this.dist,
hotReloader: this.hotReloader,
buildStats: this.buildStats,
buildId: this.buildId,
availableChunks: dev ? {} : getAvailableChunks(this.dir, this.dist)
}
Expand Down Expand Up @@ -170,27 +167,22 @@ export default class Server {
},

'/_next/:hash/main.js': async (req, res, params) => {
if (!this.dev) return this.send404(res)

this.handleBuildHash('main.js', params.hash, res)
const p = join(this.dir, this.dist, 'main.js')
await this.serveStatic(req, res, p)
},

'/_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)
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 }

return await renderScriptError(req, res, '/_error', error, customFields, this.renderOpts)
}

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) => {
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 0 additions & 2 deletions server/render.js
Expand Up @@ -37,7 +37,6 @@ async function doRender (req, res, pathname, query, {
err,
page,
buildId,
buildStats,
hotReloader,
assetPrefix,
runtimeConfig,
Expand Down Expand Up @@ -108,7 +107,6 @@ async function doRender (req, res, pathname, query, {
pathname, // the requested path
query,
buildId,
buildStats,
assetPrefix,
runtimeConfig,
nextExport,
Expand Down
4 changes: 2 additions & 2 deletions test/integration/dist-dir/test/index.test.js
Expand Up @@ -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()
})
})
})