You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
9.6 KiB
291 lines
9.6 KiB
/* |
|
MIT License http://www.opensource.org/licenses/mit-license.php |
|
*/ |
|
|
|
"use strict"; |
|
|
|
const RuntimeGlobals = require("../RuntimeGlobals"); |
|
const RuntimeModule = require("../RuntimeModule"); |
|
const Template = require("../Template"); |
|
const { |
|
chunkHasJs, |
|
getChunkFilenameTemplate |
|
} = require("../javascript/JavascriptModulesPlugin"); |
|
const { getInitialChunkIds } = require("../javascript/StartupHelpers"); |
|
const compileBooleanMatcher = require("../util/compileBooleanMatcher"); |
|
const { getUndoPath } = require("../util/identifier"); |
|
|
|
/** @typedef {import("../Chunk")} Chunk */ |
|
|
|
class ReadFileChunkLoadingRuntimeModule extends RuntimeModule { |
|
constructor(runtimeRequirements) { |
|
super("readFile chunk loading", RuntimeModule.STAGE_ATTACH); |
|
this.runtimeRequirements = runtimeRequirements; |
|
} |
|
|
|
/** |
|
* @private |
|
* @param {Chunk} chunk chunk |
|
* @param {string} rootOutputDir root output directory |
|
* @returns {string} generated code |
|
*/ |
|
_generateBaseUri(chunk, rootOutputDir) { |
|
const options = chunk.getEntryOptions(); |
|
if (options && options.baseUri) { |
|
return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; |
|
} |
|
|
|
return `${RuntimeGlobals.baseURI} = require("url").pathToFileURL(${ |
|
rootOutputDir |
|
? `__dirname + ${JSON.stringify("/" + rootOutputDir)}` |
|
: "__filename" |
|
});`; |
|
} |
|
|
|
/** |
|
* @returns {string} runtime code |
|
*/ |
|
generate() { |
|
const { chunkGraph, chunk } = this; |
|
const { runtimeTemplate } = this.compilation; |
|
const fn = RuntimeGlobals.ensureChunkHandlers; |
|
const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI); |
|
const withExternalInstallChunk = this.runtimeRequirements.has( |
|
RuntimeGlobals.externalInstallChunk |
|
); |
|
const withOnChunkLoad = this.runtimeRequirements.has( |
|
RuntimeGlobals.onChunksLoaded |
|
); |
|
const withLoading = this.runtimeRequirements.has( |
|
RuntimeGlobals.ensureChunkHandlers |
|
); |
|
const withHmr = this.runtimeRequirements.has( |
|
RuntimeGlobals.hmrDownloadUpdateHandlers |
|
); |
|
const withHmrManifest = this.runtimeRequirements.has( |
|
RuntimeGlobals.hmrDownloadManifest |
|
); |
|
const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); |
|
const hasJsMatcher = compileBooleanMatcher(conditionMap); |
|
const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); |
|
|
|
const outputName = this.compilation.getPath( |
|
getChunkFilenameTemplate(chunk, this.compilation.outputOptions), |
|
{ |
|
chunk, |
|
contentHashType: "javascript" |
|
} |
|
); |
|
const rootOutputDir = getUndoPath( |
|
outputName, |
|
this.compilation.outputOptions.path, |
|
false |
|
); |
|
|
|
const stateExpression = withHmr |
|
? `${RuntimeGlobals.hmrRuntimeStatePrefix}_readFileVm` |
|
: undefined; |
|
|
|
return Template.asString([ |
|
withBaseURI |
|
? this._generateBaseUri(chunk, rootOutputDir) |
|
: "// no baseURI", |
|
"", |
|
"// object to store loaded chunks", |
|
'// "0" means "already loaded", Promise means loading', |
|
`var installedChunks = ${ |
|
stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" |
|
}{`, |
|
Template.indent( |
|
Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( |
|
",\n" |
|
) |
|
), |
|
"};", |
|
"", |
|
withOnChunkLoad |
|
? `${ |
|
RuntimeGlobals.onChunksLoaded |
|
}.readFileVm = ${runtimeTemplate.returningFunction( |
|
"installedChunks[chunkId] === 0", |
|
"chunkId" |
|
)};` |
|
: "// no on chunks loaded", |
|
"", |
|
withLoading || withExternalInstallChunk |
|
? `var installChunk = ${runtimeTemplate.basicFunction("chunk", [ |
|
"var moreModules = chunk.modules, chunkIds = chunk.ids, runtime = chunk.runtime;", |
|
"for(var moduleId in moreModules) {", |
|
Template.indent([ |
|
`if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, |
|
Template.indent([ |
|
`${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` |
|
]), |
|
"}" |
|
]), |
|
"}", |
|
`if(runtime) runtime(__webpack_require__);`, |
|
"for(var i = 0; i < chunkIds.length; i++) {", |
|
Template.indent([ |
|
"if(installedChunks[chunkIds[i]]) {", |
|
Template.indent(["installedChunks[chunkIds[i]][0]();"]), |
|
"}", |
|
"installedChunks[chunkIds[i]] = 0;" |
|
]), |
|
"}", |
|
withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : "" |
|
])};` |
|
: "// no chunk install function needed", |
|
"", |
|
withLoading |
|
? Template.asString([ |
|
"// ReadFile + VM.run chunk loading for javascript", |
|
`${fn}.readFileVm = function(chunkId, promises) {`, |
|
hasJsMatcher !== false |
|
? Template.indent([ |
|
"", |
|
"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([ |
|
hasJsMatcher === true |
|
? "if(true) { // all chunks have JS" |
|
: `if(${hasJsMatcher("chunkId")}) {`, |
|
Template.indent([ |
|
"// load the chunk and return promise to it", |
|
"var promise = new Promise(function(resolve, reject) {", |
|
Template.indent([ |
|
"installedChunkData = installedChunks[chunkId] = [resolve, reject];", |
|
`var filename = require('path').join(__dirname, ${JSON.stringify( |
|
rootOutputDir |
|
)} + ${ |
|
RuntimeGlobals.getChunkScriptFilename |
|
}(chunkId));`, |
|
"require('fs').readFile(filename, 'utf-8', function(err, content) {", |
|
Template.indent([ |
|
"if(err) return reject(err);", |
|
"var chunk = {};", |
|
"require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" + |
|
"(chunk, require, require('path').dirname(filename), filename);", |
|
"installChunk(chunk);" |
|
]), |
|
"});" |
|
]), |
|
"});", |
|
"promises.push(installedChunkData[2] = promise);" |
|
]), |
|
"} else installedChunks[chunkId] = 0;" |
|
]), |
|
"}" |
|
]), |
|
"}" |
|
]) |
|
: Template.indent(["installedChunks[chunkId] = 0;"]), |
|
"};" |
|
]) |
|
: "// no chunk loading", |
|
"", |
|
withExternalInstallChunk |
|
? Template.asString([ |
|
"module.exports = __webpack_require__;", |
|
`${RuntimeGlobals.externalInstallChunk} = installChunk;` |
|
]) |
|
: "// no external install chunk", |
|
"", |
|
withHmr |
|
? Template.asString([ |
|
"function loadUpdateChunk(chunkId, updatedModulesList) {", |
|
Template.indent([ |
|
"return new Promise(function(resolve, reject) {", |
|
Template.indent([ |
|
`var filename = require('path').join(__dirname, ${JSON.stringify( |
|
rootOutputDir |
|
)} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId));`, |
|
"require('fs').readFile(filename, 'utf-8', function(err, content) {", |
|
Template.indent([ |
|
"if(err) return reject(err);", |
|
"var update = {};", |
|
"require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" + |
|
"(update, require, require('path').dirname(filename), filename);", |
|
"var updatedModules = update.modules;", |
|
"var runtime = update.runtime;", |
|
"for(var moduleId in updatedModules) {", |
|
Template.indent([ |
|
`if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`, |
|
Template.indent([ |
|
`currentUpdate[moduleId] = updatedModules[moduleId];`, |
|
"if(updatedModulesList) updatedModulesList.push(moduleId);" |
|
]), |
|
"}" |
|
]), |
|
"}", |
|
"if(runtime) currentUpdateRuntime.push(runtime);", |
|
"resolve();" |
|
]), |
|
"});" |
|
]), |
|
"});" |
|
]), |
|
"}", |
|
"", |
|
Template.getFunctionContent( |
|
require("../hmr/JavascriptHotModuleReplacement.runtime.js") |
|
) |
|
.replace(/\$key\$/g, "readFileVm") |
|
.replace(/\$installedChunks\$/g, "installedChunks") |
|
.replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") |
|
.replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) |
|
.replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) |
|
.replace( |
|
/\$ensureChunkHandlers\$/g, |
|
RuntimeGlobals.ensureChunkHandlers |
|
) |
|
.replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) |
|
.replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) |
|
.replace( |
|
/\$hmrDownloadUpdateHandlers\$/g, |
|
RuntimeGlobals.hmrDownloadUpdateHandlers |
|
) |
|
.replace( |
|
/\$hmrInvalidateModuleHandlers\$/g, |
|
RuntimeGlobals.hmrInvalidateModuleHandlers |
|
) |
|
]) |
|
: "// no HMR", |
|
"", |
|
withHmrManifest |
|
? Template.asString([ |
|
`${RuntimeGlobals.hmrDownloadManifest} = function() {`, |
|
Template.indent([ |
|
"return new Promise(function(resolve, reject) {", |
|
Template.indent([ |
|
`var filename = require('path').join(__dirname, ${JSON.stringify( |
|
rootOutputDir |
|
)} + ${RuntimeGlobals.getUpdateManifestFilename}());`, |
|
"require('fs').readFile(filename, 'utf-8', function(err, content) {", |
|
Template.indent([ |
|
"if(err) {", |
|
Template.indent([ |
|
'if(err.code === "ENOENT") return resolve();', |
|
"return reject(err);" |
|
]), |
|
"}", |
|
"try { resolve(JSON.parse(content)); }", |
|
"catch(e) { reject(e); }" |
|
]), |
|
"});" |
|
]), |
|
"});" |
|
]), |
|
"}" |
|
]) |
|
: "// no HMR manifest" |
|
]); |
|
} |
|
} |
|
|
|
module.exports = ReadFileChunkLoadingRuntimeModule;
|
|
|