ReadFileChunkLoadingRuntimeModule.js (9798B)
1 /* 2 MIT License http://www.opensource.org/licenses/mit-license.php 3 */ 4 5 "use strict"; 6 7 const RuntimeGlobals = require("../RuntimeGlobals"); 8 const RuntimeModule = require("../RuntimeModule"); 9 const Template = require("../Template"); 10 const { 11 chunkHasJs, 12 getChunkFilenameTemplate 13 } = require("../javascript/JavascriptModulesPlugin"); 14 const { getInitialChunkIds } = require("../javascript/StartupHelpers"); 15 const compileBooleanMatcher = require("../util/compileBooleanMatcher"); 16 const { getUndoPath } = require("../util/identifier"); 17 18 /** @typedef {import("../Chunk")} Chunk */ 19 20 class ReadFileChunkLoadingRuntimeModule extends RuntimeModule { 21 constructor(runtimeRequirements) { 22 super("readFile chunk loading", RuntimeModule.STAGE_ATTACH); 23 this.runtimeRequirements = runtimeRequirements; 24 } 25 26 /** 27 * @private 28 * @param {Chunk} chunk chunk 29 * @param {string} rootOutputDir root output directory 30 * @returns {string} generated code 31 */ 32 _generateBaseUri(chunk, rootOutputDir) { 33 const options = chunk.getEntryOptions(); 34 if (options && options.baseUri) { 35 return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; 36 } 37 38 return `${RuntimeGlobals.baseURI} = require("url").pathToFileURL(${ 39 rootOutputDir 40 ? `__dirname + ${JSON.stringify("/" + rootOutputDir)}` 41 : "__filename" 42 });`; 43 } 44 45 /** 46 * @returns {string} runtime code 47 */ 48 generate() { 49 const { chunkGraph, chunk } = this; 50 const { runtimeTemplate } = this.compilation; 51 const fn = RuntimeGlobals.ensureChunkHandlers; 52 const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI); 53 const withExternalInstallChunk = this.runtimeRequirements.has( 54 RuntimeGlobals.externalInstallChunk 55 ); 56 const withOnChunkLoad = this.runtimeRequirements.has( 57 RuntimeGlobals.onChunksLoaded 58 ); 59 const withLoading = this.runtimeRequirements.has( 60 RuntimeGlobals.ensureChunkHandlers 61 ); 62 const withHmr = this.runtimeRequirements.has( 63 RuntimeGlobals.hmrDownloadUpdateHandlers 64 ); 65 const withHmrManifest = this.runtimeRequirements.has( 66 RuntimeGlobals.hmrDownloadManifest 67 ); 68 const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); 69 const hasJsMatcher = compileBooleanMatcher(conditionMap); 70 const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); 71 72 const outputName = this.compilation.getPath( 73 getChunkFilenameTemplate(chunk, this.compilation.outputOptions), 74 { 75 chunk, 76 contentHashType: "javascript" 77 } 78 ); 79 const rootOutputDir = getUndoPath( 80 outputName, 81 this.compilation.outputOptions.path, 82 false 83 ); 84 85 const stateExpression = withHmr 86 ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_readFileVm` 87 : undefined; 88 89 return Template.asString([ 90 withBaseURI 91 ? this._generateBaseUri(chunk, rootOutputDir) 92 : "// no baseURI", 93 "", 94 "// object to store loaded chunks", 95 '// "0" means "already loaded", Promise means loading', 96 `var installedChunks = ${ 97 stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" 98 }{`, 99 Template.indent( 100 Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( 101 ",\n" 102 ) 103 ), 104 "};", 105 "", 106 withOnChunkLoad 107 ? `${ 108 RuntimeGlobals.onChunksLoaded 109 }.readFileVm = ${runtimeTemplate.returningFunction( 110 "installedChunks[chunkId] === 0", 111 "chunkId" 112 )};` 113 : "// no on chunks loaded", 114 "", 115 withLoading || withExternalInstallChunk 116 ? `var installChunk = ${runtimeTemplate.basicFunction("chunk", [ 117 "var moreModules = chunk.modules, chunkIds = chunk.ids, runtime = chunk.runtime;", 118 "for(var moduleId in moreModules) {", 119 Template.indent([ 120 `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, 121 Template.indent([ 122 `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` 123 ]), 124 "}" 125 ]), 126 "}", 127 `if(runtime) runtime(__webpack_require__);`, 128 "for(var i = 0; i < chunkIds.length; i++) {", 129 Template.indent([ 130 "if(installedChunks[chunkIds[i]]) {", 131 Template.indent(["installedChunks[chunkIds[i]][0]();"]), 132 "}", 133 "installedChunks[chunkIds[i]] = 0;" 134 ]), 135 "}", 136 withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : "" 137 ])};` 138 : "// no chunk install function needed", 139 "", 140 withLoading 141 ? Template.asString([ 142 "// ReadFile + VM.run chunk loading for javascript", 143 `${fn}.readFileVm = function(chunkId, promises) {`, 144 hasJsMatcher !== false 145 ? Template.indent([ 146 "", 147 "var installedChunkData = installedChunks[chunkId];", 148 'if(installedChunkData !== 0) { // 0 means "already installed".', 149 Template.indent([ 150 '// array of [resolve, reject, promise] means "currently loading"', 151 "if(installedChunkData) {", 152 Template.indent(["promises.push(installedChunkData[2]);"]), 153 "} else {", 154 Template.indent([ 155 hasJsMatcher === true 156 ? "if(true) { // all chunks have JS" 157 : `if(${hasJsMatcher("chunkId")}) {`, 158 Template.indent([ 159 "// load the chunk and return promise to it", 160 "var promise = new Promise(function(resolve, reject) {", 161 Template.indent([ 162 "installedChunkData = installedChunks[chunkId] = [resolve, reject];", 163 `var filename = require('path').join(__dirname, ${JSON.stringify( 164 rootOutputDir 165 )} + ${ 166 RuntimeGlobals.getChunkScriptFilename 167 }(chunkId));`, 168 "require('fs').readFile(filename, 'utf-8', function(err, content) {", 169 Template.indent([ 170 "if(err) return reject(err);", 171 "var chunk = {};", 172 "require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" + 173 "(chunk, require, require('path').dirname(filename), filename);", 174 "installChunk(chunk);" 175 ]), 176 "});" 177 ]), 178 "});", 179 "promises.push(installedChunkData[2] = promise);" 180 ]), 181 "} else installedChunks[chunkId] = 0;" 182 ]), 183 "}" 184 ]), 185 "}" 186 ]) 187 : Template.indent(["installedChunks[chunkId] = 0;"]), 188 "};" 189 ]) 190 : "// no chunk loading", 191 "", 192 withExternalInstallChunk 193 ? Template.asString([ 194 "module.exports = __webpack_require__;", 195 `${RuntimeGlobals.externalInstallChunk} = installChunk;` 196 ]) 197 : "// no external install chunk", 198 "", 199 withHmr 200 ? Template.asString([ 201 "function loadUpdateChunk(chunkId, updatedModulesList) {", 202 Template.indent([ 203 "return new Promise(function(resolve, reject) {", 204 Template.indent([ 205 `var filename = require('path').join(__dirname, ${JSON.stringify( 206 rootOutputDir 207 )} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId));`, 208 "require('fs').readFile(filename, 'utf-8', function(err, content) {", 209 Template.indent([ 210 "if(err) return reject(err);", 211 "var update = {};", 212 "require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" + 213 "(update, require, require('path').dirname(filename), filename);", 214 "var updatedModules = update.modules;", 215 "var runtime = update.runtime;", 216 "for(var moduleId in updatedModules) {", 217 Template.indent([ 218 `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`, 219 Template.indent([ 220 `currentUpdate[moduleId] = updatedModules[moduleId];`, 221 "if(updatedModulesList) updatedModulesList.push(moduleId);" 222 ]), 223 "}" 224 ]), 225 "}", 226 "if(runtime) currentUpdateRuntime.push(runtime);", 227 "resolve();" 228 ]), 229 "});" 230 ]), 231 "});" 232 ]), 233 "}", 234 "", 235 Template.getFunctionContent( 236 require("../hmr/JavascriptHotModuleReplacement.runtime.js") 237 ) 238 .replace(/\$key\$/g, "readFileVm") 239 .replace(/\$installedChunks\$/g, "installedChunks") 240 .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") 241 .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) 242 .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) 243 .replace( 244 /\$ensureChunkHandlers\$/g, 245 RuntimeGlobals.ensureChunkHandlers 246 ) 247 .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) 248 .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) 249 .replace( 250 /\$hmrDownloadUpdateHandlers\$/g, 251 RuntimeGlobals.hmrDownloadUpdateHandlers 252 ) 253 .replace( 254 /\$hmrInvalidateModuleHandlers\$/g, 255 RuntimeGlobals.hmrInvalidateModuleHandlers 256 ) 257 ]) 258 : "// no HMR", 259 "", 260 withHmrManifest 261 ? Template.asString([ 262 `${RuntimeGlobals.hmrDownloadManifest} = function() {`, 263 Template.indent([ 264 "return new Promise(function(resolve, reject) {", 265 Template.indent([ 266 `var filename = require('path').join(__dirname, ${JSON.stringify( 267 rootOutputDir 268 )} + ${RuntimeGlobals.getUpdateManifestFilename}());`, 269 "require('fs').readFile(filename, 'utf-8', function(err, content) {", 270 Template.indent([ 271 "if(err) {", 272 Template.indent([ 273 'if(err.code === "ENOENT") return resolve();', 274 "return reject(err);" 275 ]), 276 "}", 277 "try { resolve(JSON.parse(content)); }", 278 "catch(e) { reject(e); }" 279 ]), 280 "});" 281 ]), 282 "});" 283 ]), 284 "}" 285 ]) 286 : "// no HMR manifest" 287 ]); 288 } 289 } 290 291 module.exports = ReadFileChunkLoadingRuntimeModule;