Nuxt/packages/bridge/src/async-loading.ts
2021-10-02 18:01:17 +02:00

142 lines
5.8 KiB
TypeScript

// Based on https://github.com/webpack/webpack/blob/v4.46.0/lib/node/NodeMainTemplatePlugin.js#L81-L191
import { createRequire } from 'module'
import type { Compiler } from 'webpack'
export class AsyncLoadingPlugin {
private opts: any
private Template: any
constructor (opts) {
this.opts = opts
const _require = createRequire(import.meta.url)
const TemplatePath = _require.resolve('webpack/lib/Template', { paths: [...this.opts.modulesDir] })
this.Template = _require(TemplatePath)
}
apply (compiler: Compiler) {
compiler.hooks.compilation.tap('AsyncLoading', (compilation) => {
const mainTemplate = compilation.mainTemplate
mainTemplate.hooks.requireEnsure.tap(
'AsyncLoading',
(_source, chunk, hash) => {
const Template = this.Template
const chunkFilename = mainTemplate.outputOptions.chunkFilename
const chunkMaps = chunk.getChunkMaps()
const insertMoreModules = [
'var moreModules = chunk.modules, chunkIds = chunk.ids;',
'for(var moduleId in moreModules) {',
Template.indent(
mainTemplate.renderAddModule(
hash,
chunk,
'moduleId',
'moreModules[moduleId]'
)
),
'}'
]
return Template.asString([
'// Async chunk loading for Nitro',
'',
'var installedChunkData = installedChunks[chunkId];',
'if(installedChunkData !== 0) { // 0 means "already installed".',
Template.indent([
'// array of [resolve, reject, promise] means "currently loading"',
'if(installedChunkData) {',
Template.indent(['promises.push(installedChunkData[2]);']),
'} else {',
Template.indent([
'// load the chunk and return promise to it',
'var promise = new Promise(function(resolve, reject) {',
Template.indent([
'installedChunkData = installedChunks[chunkId] = [resolve, reject];',
'import(' +
mainTemplate.getAssetPath(
JSON.stringify(`./${chunkFilename}`),
{
hash: `" + ${mainTemplate.renderCurrentHashCode(
hash
)} + "`,
hashWithLength: length =>
`" + ${mainTemplate.renderCurrentHashCode(
hash,
length
)} + "`,
chunk: {
id: '" + chunkId + "',
hash: `" + ${JSON.stringify(
chunkMaps.hash
)}[chunkId] + "`,
hashWithLength: (length) => {
const shortChunkHashMap = {}
for (const chunkId of Object.keys(chunkMaps.hash)) {
if (typeof chunkMaps.hash[chunkId] === 'string') {
shortChunkHashMap[chunkId] = chunkMaps.hash[
chunkId
].substr(0, length)
}
}
return `" + ${JSON.stringify(
shortChunkHashMap
)}[chunkId] + "`
},
contentHash: {
javascript: `" + ${JSON.stringify(
chunkMaps.contentHash.javascript
)}[chunkId] + "`
},
contentHashWithLength: {
javascript: (length) => {
const shortContentHashMap = {}
const contentHash =
chunkMaps.contentHash.javascript
for (const chunkId of Object.keys(contentHash)) {
if (typeof contentHash[chunkId] === 'string') {
shortContentHashMap[chunkId] = contentHash[
chunkId
].substr(0, length)
}
}
return `" + ${JSON.stringify(
shortContentHashMap
)}[chunkId] + "`
}
},
name: `" + (${JSON.stringify(
chunkMaps.name
)}[chunkId]||chunkId) + "`
},
contentHashType: 'javascript'
}
) +
').then(chunk => {',
Template.indent(
insertMoreModules
.concat([
'var callbacks = [];',
'for(var i = 0; i < chunkIds.length; i++) {',
Template.indent([
'if(installedChunks[chunkIds[i]])',
Template.indent([
'callbacks = callbacks.concat(installedChunks[chunkIds[i]][0]);'
]),
'installedChunks[chunkIds[i]] = 0;'
]),
'}',
'for(i = 0; i < callbacks.length; i++)',
Template.indent('callbacks[i]();')
])
),
'});'
]),
'});',
'promises.push(installedChunkData[2] = promise);'
]),
'}'
]),
'}'
])
})
})
}
}