UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

1,264 lines (1,185 loc) 40.3 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ 'use strict' const { OriginalSource, RawSource } = require('webpack-sources') const AsyncDependenciesBlock = require('./AsyncDependenciesBlock') const { makeWebpackError } = require('./HookWebpackError') const Module = require('./Module') const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require('./ModuleTypeConstants') const RuntimeGlobals = require('./RuntimeGlobals') const Template = require('./Template') const WebpackError = require('./WebpackError') const { compareLocations, concatComparators, compareSelect, keepOriginalOrder, compareModulesById } = require('./util/comparators') const { contextify, parseResource, makePathsRelative } = require('./util/identifier') const makeSerializable = require('./util/makeSerializable') /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ /** @typedef {import("./ChunkGraph")} ChunkGraph */ /** @typedef {import("./ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ /** @typedef {import("./Compilation")} Compilation */ /** @typedef {import("./DependencyTemplates")} DependencyTemplates */ /** @typedef {import("./Module").BuildMeta} BuildMeta */ /** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ /** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ /** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ /** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ /** @typedef {import("./ModuleGraph")} ModuleGraph */ /** @typedef {import("./RequestShortener")} RequestShortener */ /** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ /** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ /** @typedef {import("./dependencies/ContextElementDependency")} ContextElementDependency */ /** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ /** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ /** @template T @typedef {import("./util/LazySet")<T>} LazySet<T> */ /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ /** @typedef {"sync" | "eager" | "weak" | "async-weak" | "lazy" | "lazy-once"} ContextMode Context mode */ /** * @typedef {Object} ContextOptions * @property {ContextMode} mode * @property {boolean} recursive * @property {RegExp} regExp * @property {"strict"|boolean=} namespaceObject * @property {string=} addon * @property {string=} chunkName * @property {RegExp=} include * @property {RegExp=} exclude * @property {RawChunkGroupOptions=} groupOptions * @property {string=} typePrefix * @property {string=} category * @property {(string[][] | null)=} referencedExports exports referenced from modules (won't be mangled) * @property {string=} layer */ /** * @typedef {Object} ContextModuleOptionsExtras * @property {false|string|string[]} resource * @property {string=} resourceQuery * @property {string=} resourceFragment * @property {TODO} resolveOptions */ /** @typedef {ContextOptions & ContextModuleOptionsExtras} ContextModuleOptions */ /** * @callback ResolveDependenciesCallback * @param {(Error | null)=} err * @param {ContextElementDependency[]=} dependencies */ /** * @callback ResolveDependencies * @param {InputFileSystem} fs * @param {ContextModuleOptions} options * @param {ResolveDependenciesCallback} callback */ const SNAPSHOT_OPTIONS = { timestamp: true } const TYPES = new Set(['javascript']) class ContextModule extends Module { /** * @param {ResolveDependencies} resolveDependencies function to get dependencies in this context * @param {ContextModuleOptions} options options object */ constructor(resolveDependencies, options) { if (!options || typeof options.resource === 'string') { const parsed = parseResource( options ? /** @type {string} */ (options.resource) : '' ) const resource = parsed.path const resourceQuery = (options && options.resourceQuery) || parsed.query const resourceFragment = (options && options.resourceFragment) || parsed.fragment const layer = options && options.layer super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, resource, layer) /** @type {ContextModuleOptions} */ this.options = { ...options, resource, resourceQuery, resourceFragment } } else { super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, undefined, options.layer) /** @type {ContextModuleOptions} */ this.options = { ...options, resource: options.resource, resourceQuery: options.resourceQuery || '', resourceFragment: options.resourceFragment || '' } } // Info from Factory this.resolveDependencies = resolveDependencies if (options && options.resolveOptions !== undefined) { this.resolveOptions = options.resolveOptions } if (options && typeof options.mode !== 'string') { throw new Error('options.mode is a required option') } this._identifier = this._createIdentifier() this._forceBuild = true } /** * @returns {Set<string>} types available (do not mutate) */ getSourceTypes() { return TYPES } /** * Assuming this module is in the cache. Update the (cached) module with * the fresh module from the factory. Usually updates internal references * and properties. * @param {Module} module fresh module * @returns {void} */ updateCacheModule(module) { const m = /** @type {ContextModule} */ (module) this.resolveDependencies = m.resolveDependencies this.options = m.options } /** * Assuming this module is in the cache. Remove internal references to allow freeing some memory. */ cleanupForCache() { super.cleanupForCache() this.resolveDependencies = undefined } _prettyRegExp(regexString, stripSlash = true) { const str = (regexString + '').replace(/!/g, '%21').replace(/\|/g, '%7C') return stripSlash ? str.substring(1, str.length - 1) : str } _createIdentifier() { let identifier = this.context || (typeof this.options.resource === 'string' || this.options.resource === false ? `${this.options.resource}` : this.options.resource.join('|')) if (this.options.resourceQuery) { identifier += `|${this.options.resourceQuery}` } if (this.options.resourceFragment) { identifier += `|${this.options.resourceFragment}` } if (this.options.mode) { identifier += `|${this.options.mode}` } if (!this.options.recursive) { identifier += '|nonrecursive' } if (this.options.addon) { identifier += `|${this.options.addon}` } if (this.options.regExp) { identifier += `|${this._prettyRegExp(this.options.regExp, false)}` } if (this.options.include) { identifier += `|include: ${this._prettyRegExp( this.options.include, false )}` } if (this.options.exclude) { identifier += `|exclude: ${this._prettyRegExp( this.options.exclude, false )}` } if (this.options.referencedExports) { identifier += `|referencedExports: ${JSON.stringify( this.options.referencedExports )}` } if (this.options.chunkName) { identifier += `|chunkName: ${this.options.chunkName}` } if (this.options.groupOptions) { identifier += `|groupOptions: ${JSON.stringify( this.options.groupOptions )}` } if (this.options.namespaceObject === 'strict') { identifier += '|strict namespace object' } else if (this.options.namespaceObject) { identifier += '|namespace object' } if (this.layer) { identifier += `|layer: ${this.layer}` } return identifier } /** * @returns {string} a unique identifier of the module */ identifier() { return this._identifier } /** * @param {RequestShortener} requestShortener the request shortener * @returns {string} a user readable identifier of the module */ readableIdentifier(requestShortener) { let identifier if (this.context) { identifier = requestShortener.shorten(this.context) + '/' } else if ( typeof this.options.resource === 'string' || this.options.resource === false ) { identifier = requestShortener.shorten(`${this.options.resource}`) + '/' } else { identifier = this.options.resource .map(r => requestShortener.shorten(r) + '/') .join(' ') } if (this.options.resourceQuery) { identifier += ` ${this.options.resourceQuery}` } if (this.options.mode) { identifier += ` ${this.options.mode}` } if (!this.options.recursive) { identifier += ' nonrecursive' } if (this.options.addon) { identifier += ` ${requestShortener.shorten(this.options.addon)}` } if (this.options.regExp) { identifier += ` ${this._prettyRegExp(this.options.regExp)}` } if (this.options.include) { identifier += ` include: ${this._prettyRegExp(this.options.include)}` } if (this.options.exclude) { identifier += ` exclude: ${this._prettyRegExp(this.options.exclude)}` } if (this.options.referencedExports) { identifier += ` referencedExports: ${this.options.referencedExports .map(e => e.join('.')) .join(', ')}` } if (this.options.chunkName) { identifier += ` chunkName: ${this.options.chunkName}` } if (this.options.groupOptions) { const groupOptions = this.options.groupOptions for (const key of Object.keys(groupOptions)) { identifier += ` ${key}: ${groupOptions[key]}` } } if (this.options.namespaceObject === 'strict') { identifier += ' strict namespace object' } else if (this.options.namespaceObject) { identifier += ' namespace object' } return identifier } /** * @param {LibIdentOptions} options options * @returns {string | null} an identifier for library inclusion */ libIdent(options) { let identifier if (this.context) { identifier = contextify( options.context, this.context, options.associatedObjectForCache ) } else if (typeof this.options.resource === 'string') { identifier = contextify( options.context, this.options.resource, options.associatedObjectForCache ) } else if (this.options.resource === false) { identifier = 'false' } else { identifier = this.options.resource .map(res => contextify(options.context, res, options.associatedObjectForCache) ) .join(' ') } if (this.layer) identifier = `(${this.layer})/${identifier}` if (this.options.mode) { identifier += ` ${this.options.mode}` } if (this.options.recursive) { identifier += ' recursive' } if (this.options.addon) { identifier += ` ${contextify( options.context, this.options.addon, options.associatedObjectForCache )}` } if (this.options.regExp) { identifier += ` ${this._prettyRegExp(this.options.regExp)}` } if (this.options.include) { identifier += ` include: ${this._prettyRegExp(this.options.include)}` } if (this.options.exclude) { identifier += ` exclude: ${this._prettyRegExp(this.options.exclude)}` } if (this.options.referencedExports) { identifier += ` referencedExports: ${this.options.referencedExports .map(e => e.join('.')) .join(', ')}` } return identifier } /** * @returns {void} */ invalidateBuild() { this._forceBuild = true } /** * @param {NeedBuildContext} context context info * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild * @returns {void} */ needBuild({ fileSystemInfo }, callback) { // build if enforced if (this._forceBuild) return callback(null, true) // always build when we have no snapshot and context if (!this.buildInfo.snapshot) { return callback(null, Boolean(this.context || this.options.resource)) } fileSystemInfo.checkSnapshotValid(this.buildInfo.snapshot, (err, valid) => { callback(err, !valid) }) } /** * @param {WebpackOptions} options webpack options * @param {Compilation} compilation the compilation * @param {ResolverWithOptions} resolver the resolver * @param {InputFileSystem} fs the file system * @param {function(WebpackError=): void} callback callback function * @returns {void} */ build(options, compilation, resolver, fs, callback) { this._forceBuild = false /** @type {BuildMeta} */ this.buildMeta = { exportsType: 'default', defaultObject: 'redirect-warn' } this.buildInfo = { snapshot: undefined } this.dependencies.length = 0 this.blocks.length = 0 const startTime = Date.now() this.resolveDependencies(fs, this.options, (err, dependencies) => { if (err) { return callback( makeWebpackError(err, 'ContextModule.resolveDependencies') ) } // abort if something failed // this will create an empty context if (!dependencies) { callback() return } // enhance dependencies with meta info for (const dep of dependencies) { dep.loc = { name: dep.userRequest } dep.request = this.options.addon + dep.request } dependencies.sort( concatComparators( compareSelect(a => a.loc, compareLocations), keepOriginalOrder(this.dependencies) ) ) if (this.options.mode === 'sync' || this.options.mode === 'eager') { // if we have an sync or eager context // just add all dependencies and continue this.dependencies = dependencies } else if (this.options.mode === 'lazy-once') { // for the lazy-once mode create a new async dependency block // and add that block to this context if (dependencies.length > 0) { const block = new AsyncDependenciesBlock({ ...this.options.groupOptions, name: this.options.chunkName }) for (const dep of dependencies) { block.addDependency(dep) } this.addBlock(block) } } else if ( this.options.mode === 'weak' || this.options.mode === 'async-weak' ) { // we mark all dependencies as weak for (const dep of dependencies) { dep.weak = true } this.dependencies = dependencies } else if (this.options.mode === 'lazy') { // if we are lazy create a new async dependency block per dependency // and add all blocks to this context let index = 0 for (const dep of dependencies) { let chunkName = this.options.chunkName if (chunkName) { if (!/\[(index|request)\]/.test(chunkName)) { chunkName += '[index]' } chunkName = chunkName.replace(/\[index\]/g, `${index++}`) chunkName = chunkName.replace( /\[request\]/g, Template.toPath(dep.userRequest) ) } const block = new AsyncDependenciesBlock( { ...this.options.groupOptions, name: chunkName }, dep.loc, dep.userRequest ) block.addDependency(dep) this.addBlock(block) } } else { callback( new WebpackError(`Unsupported mode "${this.options.mode}" in context`) ) return } if (!this.context && !this.options.resource) return callback() compilation.fileSystemInfo.createSnapshot( startTime, null, this.context ? [this.context] : typeof this.options.resource === 'string' ? [this.options.resource] : /** @type {string[]} */ (this.options.resource), null, SNAPSHOT_OPTIONS, (err, snapshot) => { if (err) return callback(err) this.buildInfo.snapshot = snapshot callback() } ) }) } /** * @param {LazySet<string>} fileDependencies set where file dependencies are added to * @param {LazySet<string>} contextDependencies set where context dependencies are added to * @param {LazySet<string>} missingDependencies set where missing dependencies are added to * @param {LazySet<string>} buildDependencies set where build dependencies are added to */ addCacheDependencies( fileDependencies, contextDependencies, missingDependencies, buildDependencies ) { if (this.context) { contextDependencies.add(this.context) } else if (typeof this.options.resource === 'string') { contextDependencies.add(this.options.resource) } else if (this.options.resource === false) { } else { for (const res of this.options.resource) contextDependencies.add(res) } } /** * @param {ContextElementDependency[]} dependencies all dependencies * @param {ChunkGraph} chunkGraph chunk graph * @returns {TODO} TODO */ getUserRequestMap(dependencies, chunkGraph) { const moduleGraph = chunkGraph.moduleGraph // if we filter first we get a new array // therefore we don't need to create a clone of dependencies explicitly // therefore the order of this is !important! const sortedDependencies = dependencies .filter(dependency => moduleGraph.getModule(dependency)) .sort((a, b) => { if (a.userRequest === b.userRequest) { return 0 } return a.userRequest < b.userRequest ? -1 : 1 }) const map = Object.create(null) for (const dep of sortedDependencies) { const module = moduleGraph.getModule(dep) map[dep.userRequest] = chunkGraph.getModuleId(module) } return map } /** * @param {ContextElementDependency[]} dependencies all dependencies * @param {ChunkGraph} chunkGraph chunk graph * @returns {TODO} TODO */ getFakeMap(dependencies, chunkGraph) { if (!this.options.namespaceObject) { return 9 } const moduleGraph = chunkGraph.moduleGraph // bitfield let hasType = 0 const comparator = compareModulesById(chunkGraph) // if we filter first we get a new array // therefore we don't need to create a clone of dependencies explicitly // therefore the order of this is !important! const sortedModules = dependencies .map(dependency => moduleGraph.getModule(dependency)) .filter(Boolean) .sort(comparator) const fakeMap = Object.create(null) for (const module of sortedModules) { const exportsType = module.getExportsType( moduleGraph, this.options.namespaceObject === 'strict' ) const id = chunkGraph.getModuleId(module) switch (exportsType) { case 'namespace': fakeMap[id] = 9 hasType |= 1 break case 'dynamic': fakeMap[id] = 7 hasType |= 2 break case 'default-only': fakeMap[id] = 1 hasType |= 4 break case 'default-with-named': fakeMap[id] = 3 hasType |= 8 break default: throw new Error(`Unexpected exports type ${exportsType}`) } } if (hasType === 1) { return 9 } if (hasType === 2) { return 7 } if (hasType === 4) { return 1 } if (hasType === 8) { return 3 } if (hasType === 0) { return 9 } return fakeMap } getFakeMapInitStatement(fakeMap) { return typeof fakeMap === 'object' ? `var fakeMap = ${JSON.stringify(fakeMap, null, '\t')};` : '' } getReturn(type, asyncModule) { if (type === 9) { return `${RuntimeGlobals.require}(id)` } return `${RuntimeGlobals.createFakeNamespaceObject}(id, ${type}${asyncModule ? ' | 16' : '' })` } getReturnModuleObjectSource( fakeMap, asyncModule, fakeMapDataExpression = 'fakeMap[id]' ) { if (typeof fakeMap === 'number') { return `return ${this.getReturn(fakeMap, asyncModule)};` } return `return ${RuntimeGlobals.createFakeNamespaceObject }(id, ${fakeMapDataExpression}${asyncModule ? ' | 16' : ''})` } /** * @param {TODO} dependencies TODO * @param {TODO} id TODO * @param {ChunkGraph} chunkGraph the chunk graph * @returns {string} source code */ getSyncSource(dependencies, id, chunkGraph) { const map = this.getUserRequestMap(dependencies, chunkGraph) const fakeMap = this.getFakeMap(dependencies, chunkGraph) const returnModuleObject = this.getReturnModuleObjectSource(fakeMap) return `var map = ${JSON.stringify(map, null, '\t')}; ${this.getFakeMapInitStatement(fakeMap)} function webpackContext(req) { var id = webpackContextResolve(req); ${returnModuleObject} } function webpackContextResolve(req) { if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } return map[req]; } webpackContext.keys = function webpackContextKeys() { return Object.keys(map); }; webpackContext.resolve = webpackContextResolve; module.exports = webpackContext; webpackContext.id = ${JSON.stringify(id)}; __webpack_require__.webpackContext = webpackContext` } /** * @param {TODO} dependencies TODO * @param {TODO} id TODO * @param {ChunkGraph} chunkGraph the chunk graph * @returns {string} source code */ getWeakSyncSource(dependencies, id, chunkGraph) { const map = this.getUserRequestMap(dependencies, chunkGraph) const fakeMap = this.getFakeMap(dependencies, chunkGraph) const returnModuleObject = this.getReturnModuleObjectSource(fakeMap) return `var map = ${JSON.stringify(map, null, '\t')}; ${this.getFakeMapInitStatement(fakeMap)} function webpackContext(req) { var id = webpackContextResolve(req); if(!${RuntimeGlobals.moduleFactories}[id]) { var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)"); e.code = 'MODULE_NOT_FOUND'; throw e; } ${returnModuleObject} } function webpackContextResolve(req) { if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } return map[req]; } webpackContext.keys = function webpackContextKeys() { return Object.keys(map); }; webpackContext.resolve = webpackContextResolve; webpackContext.id = ${JSON.stringify(id)}; module.exports = webpackContext; __webpack_require__.webpackContext = webpackContext` } /** * @param {TODO} dependencies TODO * @param {TODO} id TODO * @param {Object} context context * @param {ChunkGraph} context.chunkGraph the chunk graph * @param {RuntimeTemplate} context.runtimeTemplate the chunk graph * @returns {string} source code */ getAsyncWeakSource(dependencies, id, { chunkGraph, runtimeTemplate }) { const arrow = runtimeTemplate.supportsArrowFunction() const map = this.getUserRequestMap(dependencies, chunkGraph) const fakeMap = this.getFakeMap(dependencies, chunkGraph) const returnModuleObject = this.getReturnModuleObjectSource(fakeMap, true) return `var map = ${JSON.stringify(map, null, '\t')}; ${this.getFakeMapInitStatement(fakeMap)} function webpackAsyncContext(req) { return webpackAsyncContextResolve(req).then(${arrow ? 'id =>' : 'function(id)' } { if(!${RuntimeGlobals.moduleFactories}[id]) { var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)"); e.code = 'MODULE_NOT_FOUND'; throw e; } ${returnModuleObject} }); } function webpackAsyncContextResolve(req) { // Here Promise.resolve().then() is used instead of new Promise() to prevent // uncaught exception popping up in devtools return Promise.resolve().then(${arrow ? '() =>' : 'function()'} { if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } return map[req]; }); } webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( 'Object.keys(map)' )}; webpackAsyncContext.resolve = webpackAsyncContextResolve; webpackAsyncContext.id = ${JSON.stringify(id)}; module.exports = webpackAsyncContext;` } /** * @param {TODO} dependencies TODO * @param {TODO} id TODO * @param {Object} context context * @param {ChunkGraph} context.chunkGraph the chunk graph * @param {RuntimeTemplate} context.runtimeTemplate the chunk graph * @returns {string} source code */ getEagerSource(dependencies, id, { chunkGraph, runtimeTemplate }) { const arrow = runtimeTemplate.supportsArrowFunction() const map = this.getUserRequestMap(dependencies, chunkGraph) const fakeMap = this.getFakeMap(dependencies, chunkGraph) const thenFunction = fakeMap !== 9 ? `${arrow ? 'id =>' : 'function(id)'} { ${this.getReturnModuleObjectSource(fakeMap)} }` : RuntimeGlobals.require return `var map = ${JSON.stringify(map, null, '\t')}; ${this.getFakeMapInitStatement(fakeMap)} function webpackAsyncContext(req) { return webpackAsyncContextResolve(req).then(${thenFunction}); } function webpackAsyncContextResolve(req) { // Here Promise.resolve().then() is used instead of new Promise() to prevent // uncaught exception popping up in devtools return Promise.resolve().then(${arrow ? '() =>' : 'function()'} { if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } return map[req]; }); } webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( 'Object.keys(map)' )}; webpackAsyncContext.resolve = webpackAsyncContextResolve; webpackAsyncContext.id = ${JSON.stringify(id)}; module.exports = webpackAsyncContext;` } /** * @param {TODO} block TODO * @param {TODO} dependencies TODO * @param {TODO} id TODO * @param {Object} options options object * @param {RuntimeTemplate} options.runtimeTemplate the runtime template * @param {ChunkGraph} options.chunkGraph the chunk graph * @returns {string} source code */ getLazyOnceSource(block, dependencies, id, { runtimeTemplate, chunkGraph }) { const promise = runtimeTemplate.blockPromise({ chunkGraph, block, message: 'lazy-once context', runtimeRequirements: new Set() }) const arrow = runtimeTemplate.supportsArrowFunction() const map = this.getUserRequestMap(dependencies, chunkGraph) const fakeMap = this.getFakeMap(dependencies, chunkGraph) const thenFunction = fakeMap !== 9 ? `${arrow ? 'id =>' : 'function(id)'} { ${this.getReturnModuleObjectSource(fakeMap, true)}; }` : RuntimeGlobals.require return `var map = ${JSON.stringify(map, null, '\t')}; ${this.getFakeMapInitStatement(fakeMap)} function webpackAsyncContext(req) { return webpackAsyncContextResolve(req).then(${thenFunction}); } function webpackAsyncContextResolve(req) { return ${promise}.then(${arrow ? '() =>' : 'function()'} { if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } return map[req]; }); } webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( 'Object.keys(map)' )}; webpackAsyncContext.resolve = webpackAsyncContextResolve; webpackAsyncContext.id = ${JSON.stringify(id)}; module.exports = webpackAsyncContext;` } /** * @param {TODO} blocks TODO * @param {TODO} id TODO * @param {Object} context context * @param {ChunkGraph} context.chunkGraph the chunk graph * @param {RuntimeTemplate} context.runtimeTemplate the chunk graph * @returns {string} source code */ getLazySource(blocks, id, { chunkGraph, runtimeTemplate }) { const moduleGraph = chunkGraph.moduleGraph const arrow = runtimeTemplate.supportsArrowFunction() let hasMultipleOrNoChunks = false let hasNoChunk = true const fakeMap = this.getFakeMap( blocks.map(b => b.dependencies[0]), chunkGraph ) const hasFakeMap = typeof fakeMap === 'object' const items = blocks .map(block => { const dependency = block.dependencies[0] return { dependency: dependency, module: moduleGraph.getModule(dependency), block: block, userRequest: dependency.userRequest, chunks: undefined } }) .filter(item => item.module) for (const item of items) { const chunkGroup = chunkGraph.getBlockChunkGroup(item.block) const chunks = (chunkGroup && chunkGroup.chunks) || [] item.chunks = chunks if (chunks.length > 0) { hasNoChunk = false } if (chunks.length !== 1) { hasMultipleOrNoChunks = true } } const shortMode = hasNoChunk && !hasFakeMap const sortedItems = items.sort((a, b) => { if (a.userRequest === b.userRequest) return 0 return a.userRequest < b.userRequest ? -1 : 1 }) const map = Object.create(null) for (const item of sortedItems) { const moduleId = chunkGraph.getModuleId(item.module) if (shortMode) { map[item.userRequest] = moduleId } else { const arrayStart = [moduleId] if (hasFakeMap) { arrayStart.push(fakeMap[moduleId]) } map[item.userRequest] = arrayStart.concat( item.chunks.map(chunk => chunk.id) ) } } const chunksStartPosition = hasFakeMap ? 2 : 1 const requestPrefix = hasNoChunk ? 'Promise.resolve()' : hasMultipleOrNoChunks ? `Promise.all(ids.slice(${chunksStartPosition}).map(${RuntimeGlobals.ensureChunk}))` : `${RuntimeGlobals.ensureChunk}(ids[${chunksStartPosition}])` const returnModuleObject = this.getReturnModuleObjectSource( fakeMap, true, shortMode ? 'invalid' : 'ids[1]' ) const webpackAsyncContext = requestPrefix === 'Promise.resolve()' ? ` function webpackAsyncContext(req) { return Promise.resolve().then(${arrow ? '() =>' : 'function()'} { if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } ${shortMode ? 'var id = map[req];' : 'var ids = map[req], id = ids[0];'} ${returnModuleObject} }); }` : `function webpackAsyncContext(req) { if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { return Promise.resolve().then(${arrow ? '() =>' : 'function()'} { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; }); } var ids = map[req], id = ids[0]; return ${requestPrefix}.then(${arrow ? '() =>' : 'function()'} { ${returnModuleObject} }); }` return `var map = ${JSON.stringify(map, null, '\t')}; ${webpackAsyncContext} webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( 'Object.keys(map)' )}; webpackAsyncContext.id = ${JSON.stringify(id)}; module.exports = webpackAsyncContext;` } getSourceForEmptyContext(id, runtimeTemplate) { return ` function webpackEmptyContext(req) { console.warn('webpackEmptyContext req:', req); try { while(req.startsWith('/')){ req = req.substring(1) } if (global && global.webpack_use_moudle){ let id = global.webpack_use_moudle[req] if (!id){ const arr = req.startsWith('./') ? req.substring(2) : req.startsWith('/') ? req.substring(1) : req const realStr = [] for (const key in global.webpack_use_moudle) { if (key.includes(arr)){ realStr.push(key) } } if (realStr.length > 0){ if (realStr.length === 1){ console.warn('webpackEmptyContext req realStr:', realStr[0]) if (global.webpack_use_moudle[realStr[0]] === -1){ req = realStr[0] } else { return __webpack_require__(global.webpack_use_moudle[realStr[0]]) } } } } else { return __webpack_require__(id) } } } catch (err){} if (!req.startsWith('./')){ req = './' + req; } if (__webpack_require__.webpackContext && __webpack_require__.webpackContext.resolve(req)){ return __webpack_require__.webpackContext(req); } if (!__webpack_require__.g){ __webpack_require__.g = global; } if (__webpack_require__.g && __webpack_require__.g.require){ return __webpack_require__.g.require(req) } var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } webpackEmptyContext.keys = ${runtimeTemplate.returningFunction('[]')}; webpackEmptyContext.resolve = webpackEmptyContext; webpackEmptyContext.id = ${JSON.stringify(id)}; module.exports = webpackEmptyContext;` } getSourceForEmptyAsyncContext(id, runtimeTemplate) { const arrow = runtimeTemplate.supportsArrowFunction() return `function webpackEmptyAsyncContext(req) { // Here Promise.resolve().then() is used instead of new Promise() to prevent console.warn('webpackEmptyContext req:', req); try { while(req.startsWith('/')){ req = req.substring(1) } if (global && global.webpack_use_moudle){ let id = global.webpack_use_moudle[req] if (!id){ const arr = req.startsWith('./') ? req.substring(2) : req.startsWith('/') ? req.substring(1) : req const realStr = [] for (const key in global.webpack_use_moudle) { if (key.includes(arr)){ realStr.push(key) } } if (realStr.length > 0){ if (realStr.length === 1){ console.warn('webpackEmptyContext req realStr:', realStr[0]) return __webpack_require__(global.webpack_use_moudle[realStr[0]]) } } } } } catch (err){} // uncaught exception popping up in devtools if (!req.startsWith('./')){ req = './' + req; } if (__webpack_require__.webpackContext && __webpack_require__.webpackContext.resolve(req)){ return __webpack_require__.webpackContext(req); } if (!__webpack_require__.g){ __webpack_require__.g = global; } if (__webpack_require__.g && __webpack_require__.g.require){ return __webpack_require__.g.require(req) } return Promise.resolve().then(${arrow ? '() =>' : 'function()'} { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; }); } webpackEmptyAsyncContext.keys = ${runtimeTemplate.returningFunction('[]')}; webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext; webpackEmptyAsyncContext.id = ${JSON.stringify(id)}; module.exports = webpackEmptyAsyncContext;` } /** * @param {string} asyncMode module mode * @param {CodeGenerationContext} context context info * @returns {string} the source code */ getSourceString(asyncMode, { runtimeTemplate, chunkGraph }) { const id = chunkGraph.getModuleId(this) if (asyncMode === 'lazy') { if (this.blocks && this.blocks.length > 0) { return this.getLazySource(this.blocks, id, { runtimeTemplate, chunkGraph }) } return this.getSourceForEmptyAsyncContext(id, runtimeTemplate) } if (asyncMode === 'eager') { if (this.dependencies && this.dependencies.length > 0) { return this.getEagerSource(this.dependencies, id, { chunkGraph, runtimeTemplate }) } return this.getSourceForEmptyAsyncContext(id, runtimeTemplate) } if (asyncMode === 'lazy-once') { const block = this.blocks[0] if (block) { return this.getLazyOnceSource(block, block.dependencies, id, { runtimeTemplate, chunkGraph }) } return this.getSourceForEmptyAsyncContext(id, runtimeTemplate) } if (asyncMode === 'async-weak') { if (this.dependencies && this.dependencies.length > 0) { return this.getAsyncWeakSource(this.dependencies, id, { chunkGraph, runtimeTemplate }) } return this.getSourceForEmptyAsyncContext(id, runtimeTemplate) } if (asyncMode === 'weak') { if (this.dependencies && this.dependencies.length > 0) { return this.getWeakSyncSource(this.dependencies, id, chunkGraph) } } if (this.dependencies && this.dependencies.length > 0) { return this.getSyncSource(this.dependencies, id, chunkGraph) } return this.getSourceForEmptyContext(id, runtimeTemplate) } /** * @param {string} sourceString source content * @param {Compilation=} compilation the compilation * @returns {Source} generated source */ getSource(sourceString, compilation) { if (this.useSourceMap || this.useSimpleSourceMap) { return new OriginalSource( sourceString, `webpack://${makePathsRelative( (compilation && compilation.compiler.context) || '', this.identifier(), compilation && compilation.compiler.root )}` ) } return new RawSource(sourceString) } /** * @param {CodeGenerationContext} context context for code generation * @returns {CodeGenerationResult} result */ codeGeneration(context) { const { chunkGraph, compilation } = context const sources = new Map() sources.set( 'javascript', this.getSource( this.getSourceString(this.options.mode, context), compilation ) ) const set = new Set() const allDeps = this.dependencies.length > 0 ? /** @type {ContextElementDependency[]} */ (this.dependencies).slice() : [] for (const block of this.blocks) { for (const dep of block.dependencies) { allDeps.push(/** @type {ContextElementDependency} */(dep)) } } set.add(RuntimeGlobals.module) set.add(RuntimeGlobals.hasOwnProperty) if (allDeps.length > 0) { const asyncMode = this.options.mode set.add(RuntimeGlobals.require) if (asyncMode === 'weak') { set.add(RuntimeGlobals.moduleFactories) } else if (asyncMode === 'async-weak') { set.add(RuntimeGlobals.moduleFactories) set.add(RuntimeGlobals.ensureChunk) } else if (asyncMode === 'lazy' || asyncMode === 'lazy-once') { set.add(RuntimeGlobals.ensureChunk) } if (this.getFakeMap(allDeps, chunkGraph) !== 9) { set.add(RuntimeGlobals.createFakeNamespaceObject) } } return { sources, runtimeRequirements: set } } /** * @param {string=} type the source type for which the size should be estimated * @returns {number} the estimated size of the module (must be non-zero) */ size(type) { // base penalty let size = 160 // if we don't have dependencies we stop here. for (const dependency of this.dependencies) { const element = /** @type {ContextElementDependency} */ (dependency) size += 5 + element.userRequest.length } return size } /** * @param {ObjectSerializerContext} context context */ serialize(context) { const { write } = context write(this._identifier) write(this._forceBuild) super.serialize(context) } /** * @param {ObjectDeserializerContext} context context */ deserialize(context) { const { read } = context this._identifier = read() this._forceBuild = read() super.deserialize(context) } } makeSerializable(ContextModule, 'webpack/lib/ContextModule') module.exports = ContextModule