Skip to content

Instantly share code, notes, and snippets.

@niieani
Last active August 28, 2017 13:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save niieani/ce388f9fcf668da4040b4cbec09f4351 to your computer and use it in GitHub Desktop.
Save niieani/ce388f9fcf668da4040b4cbec09f4351 to your computer and use it in GitHub Desktop.
MoveChunkExecutionWebpackPlugin
/* eslint-disable no-param-reassign */
/*
You should inline the chunk that this outputs into your HTML
then execute window.onReady() when all chunks are loaded, e.g.
<script async onload="executeWhenAllChunksLoaded()" src="...">
where 'executeWhenAllChunksLoaded' runs 'onReady()' once all chunks have loaded
You may use HtmlWebpackPlugin in combination with ScriptExtHtmlWebpackPlugin to do the inlining.
*/
export default class MoveChunkExecutionPlugin {
static NextIdent = 0
ident = `${__filename}/${MoveChunkExecutionPlugin.NextIdent++}`
constructor(toChunkName) {
this.toChunkName = toChunkName
this.toChunkFileName = `${toChunkName}.js`
}
apply(compiler) {
const self = this
compiler.plugin('this-compilation', (compilation) => {
compilation.plugin('before-chunk-assets' /* ['before-chunk-ids'] */, function apply() {
// extract only once
if (compilation[self.ident]) return
compilation[self.ident] = true
const {chunks} = this
const entryChunks = chunks.filter((chunk) => chunk.hasEntryModule())
const entryModuleIds = entryChunks.map((chunk) => {
const {id} = chunk.entryModule
// remove entryModule
chunk.entryModule = undefined
// but lie about having one (for JsonpMainTemplatePlugin's entryPointInChildren()):
chunk.hasEntryModule = () => true
return id
})
const onReady = `window.onReady = function(){ webpackJsonp([], {}, ${JSON.stringify(entryModuleIds)}); }`
compilation.assets[self.toChunkFileName] = {
source: () => onReady,
size: () => onReady.length,
}
})
// alter list of chunks in HtmlWebpackPlugin:
compilation.plugin('html-webpack-plugin-alter-chunks', (chunks, {plugin} = {}) => {
const assets = compilation.assets
const pseudoChunk = {
names: [this.toChunkName],
files: [this.toChunkFileName],
size: assets[this.toChunkFileName].size(),
hash: undefined,
}
return [
...chunks.filter((chunk) => chunk.entry),
pseudoChunk,
...chunks.filter((chunk) => !chunk.entry),
]
})
})
}
}
@niieani
Copy link
Author

niieani commented Aug 28, 2017

We’ve tested <script async src=...> execution for entry chunks, but it turned out the performance is actually a tiny bit worse (at least in Chrome) than when using non-async script tags. This is the plugin I wrote to support this scenario.

To run this, we’d add ‘onload’ hooks for each async script to count how many we have loaded and run: ‘window.onReady()’ as soon as all chunks were ready.

We think the reason for lower performance is that Chrome gives a lower priority to async scripts, while non-async <script src=...> still download and parse in parallel. So there seems to be no real benefit to using the ‘async’ option when blocking the execution until all entry chunks are ready.

Another weird thing we noticed with ‘async’ entry chunks is that even with HTTP/2.0 Chrome seems to download only 6 chunks simultaneously and only start downloading more after the first batch is ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment