Skip to content

Commit 53a2c5a

Browse files
kpdeckertimneutkens
authored andcommitted
Combine source maps (#3178)
* Propagate source maps through combine assets step * Use constant development build id * Move combine assets step before uglify step This ensures that uglify will catch these changes. * Move dynamic chunks step before uglify step This ensures that uglify will catch these changes. * Use chunk templates for page and dynamic chunks This is a little more in line with how webpack generates its bootstrap and should have better compatibility with other plugins and source map generation. * Register combined source map with chunks This ensures that a sourcemap is fully generated. * Do not minimize combined map inputs
1 parent 4c18678 commit 53a2c5a

File tree

6 files changed

+110
-86
lines changed

6 files changed

+110
-86
lines changed
Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { ConcatSource } from 'webpack-sources'
2+
13
// This plugin combines a set of assets into a single asset
24
// This should be only used with text assets,
35
// otherwise the result is unpredictable.
@@ -8,23 +10,28 @@ export default class CombineAssetsPlugin {
810
}
911

1012
apply (compiler) {
11-
compiler.plugin('after-compile', (compilation, callback) => {
12-
let newSource = ''
13-
this.input.forEach((name) => {
14-
const asset = compilation.assets[name]
15-
if (!asset) return
13+
compiler.plugin('compilation', (compilation) => {
14+
compilation.plugin('additional-chunk-assets', (chunks) => {
15+
const concat = new ConcatSource()
1616

17-
newSource += `${asset.source()}\n`
17+
this.input.forEach((name) => {
18+
const asset = compilation.assets[name]
19+
if (!asset) return
1820

19-
// We keep existing assets since that helps when analyzing the bundle
20-
})
21+
concat.add(asset)
2122

22-
compilation.assets[this.output] = {
23-
source: () => newSource,
24-
size: () => newSource.length
25-
}
23+
// We keep existing assets since that helps when analyzing the bundle
24+
})
2625

27-
callback()
26+
compilation.additionalChunkAssets.push(this.output)
27+
compilation.assets[this.output] = concat
28+
29+
// Register the combined file as an output of the associated chunks
30+
chunks.filter((chunk) => {
31+
return chunk.files.reduce((prev, file) => prev || this.input.includes(file), false)
32+
})
33+
.forEach((chunk) => chunk.files.push(this.output))
34+
})
2835
})
2936
}
3037
}
Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,48 @@
1-
export default class PagesPlugin {
1+
import { ConcatSource } from 'webpack-sources'
2+
3+
const isImportChunk = /^chunks[/\\].*\.js$/
4+
const matchChunkName = /^chunks[/\\](.*)$/
5+
6+
class DynamicChunkTemplatePlugin {
7+
apply (chunkTemplate) {
8+
chunkTemplate.plugin('render', function (modules, chunk) {
9+
if (!isImportChunk.test(chunk.name)) {
10+
return modules
11+
}
12+
13+
const chunkName = matchChunkName.exec(chunk.name)[1]
14+
const source = new ConcatSource()
15+
16+
source.add(`
17+
__NEXT_REGISTER_CHUNK('${chunkName}', function() {
18+
`)
19+
source.add(modules)
20+
source.add(`
21+
})
22+
`)
23+
24+
return source
25+
})
26+
}
27+
}
28+
29+
export default class DynamicChunksPlugin {
230
apply (compiler) {
3-
const isImportChunk = /^chunks[/\\].*\.js$/
4-
const matchChunkName = /^chunks[/\\](.*)$/
5-
6-
compiler.plugin('after-compile', (compilation, callback) => {
7-
const chunks = Object
8-
.keys(compilation.namedChunks)
9-
.map(key => compilation.namedChunks[key])
10-
.filter(chunk => isImportChunk.test(chunk.name))
11-
12-
chunks.forEach((chunk) => {
13-
const asset = compilation.assets[chunk.name]
14-
if (!asset) return
15-
16-
const chunkName = matchChunkName.exec(chunk.name)[1]
17-
18-
const content = asset.source()
19-
const newContent = `
20-
window.__NEXT_REGISTER_CHUNK('${chunkName}', function() {
21-
${content}
22-
})
23-
`
24-
// Replace the exisiting chunk with the new content
25-
compilation.assets[chunk.name] = {
26-
source: () => newContent,
27-
size: () => newContent.length
28-
}
29-
30-
// This is to support, webpack dynamic import support with HMR
31-
compilation.assets[`chunks/${chunk.id}`] = {
32-
source: () => newContent,
33-
size: () => newContent.length
34-
}
31+
compiler.plugin('compilation', (compilation) => {
32+
compilation.chunkTemplate.apply(new DynamicChunkTemplatePlugin())
33+
34+
compilation.plugin('additional-chunk-assets', (chunks) => {
35+
chunks = chunks.filter(chunk =>
36+
isImportChunk.test(chunk.name) && compilation.assets[chunk.name]
37+
)
38+
39+
chunks.forEach((chunk) => {
40+
// This is to support, webpack dynamic import support with HMR
41+
const copyFilename = `chunks/${chunk.name}`
42+
compilation.additionalChunkAssets.push(copyFilename)
43+
compilation.assets[copyFilename] = compilation.assets[chunk.name]
44+
})
3545
})
36-
callback()
3746
})
3847
}
3948
}
Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,51 @@
1+
import { ConcatSource } from 'webpack-sources'
12
import {
23
IS_BUNDLED_PAGE,
34
MATCH_ROUTE_NAME
45
} from '../../utils'
56

6-
export default class PagesPlugin {
7-
apply (compiler) {
8-
compiler.plugin('after-compile', (compilation, callback) => {
9-
const pages = Object
10-
.keys(compilation.namedChunks)
11-
.map(key => compilation.namedChunks[key])
12-
.filter(chunk => IS_BUNDLED_PAGE.test(chunk.name))
7+
class PageChunkTemplatePlugin {
8+
apply (chunkTemplate) {
9+
chunkTemplate.plugin('render', function (modules, chunk) {
10+
if (!IS_BUNDLED_PAGE.test(chunk.name)) {
11+
return modules
12+
}
1313

14-
pages.forEach((chunk) => {
15-
const page = compilation.assets[chunk.name]
16-
const pageName = MATCH_ROUTE_NAME.exec(chunk.name)[1]
17-
let routeName = pageName
14+
let routeName = MATCH_ROUTE_NAME.exec(chunk.name)[1]
1815

1916
// We need to convert \ into / when we are in windows
2017
// to get the proper route name
2118
// Here we need to do windows check because it's possible
2219
// to have "\" in the filename in unix.
2320
// Anyway if someone did that, he'll be having issues here.
2421
// But that's something we cannot avoid.
25-
if (/^win/.test(process.platform)) {
26-
routeName = routeName.replace(/\\/g, '/')
27-
}
28-
29-
routeName = `/${routeName.replace(/(^|\/)index$/, '')}`
30-
31-
const content = page.source()
32-
const newContent = `
33-
window.__NEXT_REGISTER_PAGE('${routeName}', function() {
34-
var comp = ${content}
35-
return { page: comp.default }
36-
})
37-
`
38-
// Replace the exisiting chunk with the new content
39-
compilation.assets[chunk.name] = {
40-
source: () => newContent,
41-
size: () => newContent.length
42-
}
43-
})
44-
callback()
22+
if (/^win/.test(process.platform)) {
23+
routeName = routeName.replace(/\\/g, '/')
24+
}
25+
26+
routeName = `/${routeName.replace(/(^|\/)index$/, '')}`
27+
28+
const source = new ConcatSource()
29+
30+
source.add(`
31+
__NEXT_REGISTER_PAGE('${routeName}', function() {
32+
var comp =
33+
`)
34+
source.add(modules)
35+
source.add(`
36+
return { page: comp.default }
37+
})
38+
`)
39+
40+
return source
41+
})
42+
}
43+
}
44+
45+
export default class PagesPlugin {
46+
apply (compiler) {
47+
compiler.plugin('compilation', (compilation) => {
48+
compilation.chunkTemplate.apply(new PageChunkTemplatePlugin())
4549
})
4650
}
4751
}

server/build/webpack.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
149149
output: 'app.js'
150150
}),
151151
new webpack.optimize.UglifyJsPlugin({
152+
exclude: ['manifest.js', 'commons.js', 'main.js'],
152153
compress: { warnings: false },
153154
sourceMap: false
154155
})

server/index.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,11 @@ export default class Server {
408408
}
409409

410410
handleBuildId (buildId, res) {
411-
if (this.dev) return true
411+
if (this.dev) {
412+
res.setHeader('Cache-Control', 'no-store, must-revalidate')
413+
return true
414+
}
415+
412416
if (buildId !== this.renderOpts.buildId) {
413417
return false
414418
}
@@ -428,13 +432,17 @@ export default class Server {
428432
}
429433

430434
handleBuildHash (filename, hash, res) {
431-
if (this.dev) return
435+
if (this.dev) {
436+
res.setHeader('Cache-Control', 'no-store, must-revalidate')
437+
return true
438+
}
432439

433440
if (hash !== this.buildStats[filename].hash) {
434441
throw new Error(`Invalid Build File Hash(${hash}) for chunk: ${filename}`)
435442
}
436443

437444
res.setHeader('Cache-Control', 'max-age=365000000, immutable')
445+
return true
438446
}
439447

440448
send404 (res) {

server/render.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,6 @@ async function doRender (req, res, pathname, query, {
9696
}
9797

9898
const docProps = await loadGetInitialProps(Document, { ...ctx, renderPage })
99-
// While developing, we should not cache any assets.
100-
// So, we use a different buildId for each page load.
101-
// With that we can ensure, we have unique URL for assets per every page load.
102-
// So, it'll prevent issues like this: https://git.io/vHLtb
103-
const devBuildId = Date.now()
10499

105100
if (res.finished) return
106101

@@ -110,7 +105,7 @@ async function doRender (req, res, pathname, query, {
110105
props,
111106
pathname,
112107
query,
113-
buildId: dev ? devBuildId : buildId,
108+
buildId,
114109
buildStats,
115110
assetPrefix,
116111
nextExport,

0 commit comments

Comments
 (0)