UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

341 lines (330 loc) 14.2 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php */ "use strict"; const RuntimeGlobals = require("../RuntimeGlobals"); const Template = require("../Template"); const HelperRuntimeModule = require("./HelperRuntimeModule"); /** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ /** @typedef {import("../Module").ExportsType} ExportsType */ /** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ /** * @param {ExportsType} exportsType exports type * @returns {string} mode */ function getMakeDeferredNamespaceModeFromExportsType(exportsType) { // number is from createFakeNamespaceObject mode ^ 1 if (exportsType === "namespace") return `/* ${exportsType} */ 8`; if (exportsType === "default-only") return `/* ${exportsType} */ 0`; if (exportsType === "default-with-named") return `/* ${exportsType} */ 2`; if (exportsType === "dynamic") return `/* ${exportsType} */ 6`; throw new Error(`Unknown exports type: ${exportsType}`); } /** * @param {string} moduleId moduleId * @param {ExportsType} exportsType exportsType * @param {(ModuleId | null)[]} asyncDepsIds asyncDepsIds * @param {RuntimeRequirements} runtimeRequirements runtime requirements * @returns {string} call make optimized deferred namespace object */ function getOptimizedDeferredModule( moduleId, exportsType, asyncDepsIds, runtimeRequirements ) { runtimeRequirements.add(RuntimeGlobals.makeOptimizedDeferredNamespaceObject); const mode = getMakeDeferredNamespaceModeFromExportsType(exportsType); return `${RuntimeGlobals.makeOptimizedDeferredNamespaceObject}(${moduleId}, ${mode}${ asyncDepsIds.length > 0 ? `, ${JSON.stringify(asyncDepsIds.filter((x) => x !== null))}` : "" })`; } class MakeOptimizedDeferredNamespaceObjectRuntimeModule extends HelperRuntimeModule { /** * @param {boolean} hasAsyncRuntime if async module is used. */ constructor(hasAsyncRuntime) { super("make optimized deferred namespace object"); /** @type {boolean} */ this.hasAsyncRuntime = hasAsyncRuntime; } /** * Generates runtime code for this runtime module. * @returns {string | null} runtime code */ generate() { if (!this.compilation) return null; const fn = RuntimeGlobals.makeOptimizedDeferredNamespaceObject; const hasAsync = this.hasAsyncRuntime; return Template.asString([ // Note: must be a function (not arrow), because this is used in body! `${fn} = function(moduleId, mode${hasAsync ? ", asyncDeps" : ""}) {`, Template.indent([ "var r = this;", hasAsync ? "var isAsync = asyncDeps && asyncDeps.length;" : "", "var obj = {", Template.indent([ "get a() {", Template.indent([ "var exports = r(moduleId);", hasAsync ? `if(isAsync) exports = exports[${RuntimeGlobals.asyncModuleExportSymbol}];` : "", // if exportsType is "namespace" we can generate the most optimized code, // on the second access, we can avoid trigger the getter. // we can also do this if exportsType is "dynamic" and there is a "__esModule" property on it. 'if(mode & 8 || (mode & 4 && exports.__esModule)) Object.defineProperty(this, "a", { value: exports });', "return exports;" ]), "}" ]), "};", hasAsync ? `if(isAsync) obj[${RuntimeGlobals.deferredModuleAsyncTransitiveDependenciesSymbol}] = asyncDeps;` : "", "return obj;" ]), "};" ]); } } class MakeDeferredNamespaceObjectRuntimeModule extends HelperRuntimeModule { /** * @param {boolean} hasAsyncRuntime if async module is used. */ constructor(hasAsyncRuntime) { super("make deferred namespace object"); /** @type {boolean} */ this.hasAsyncRuntime = hasAsyncRuntime; } /** * Generates runtime code for this runtime module. * @returns {string | null} runtime code */ generate() { if (!this.compilation) return null; const { runtimeTemplate } = this.compilation; const fn = RuntimeGlobals.makeDeferredNamespaceObject; const hasAsync = this.hasAsyncRuntime; const init = runtimeTemplate.supportsOptionalChaining() ? "init?.();" : "if (init) init();"; return `${fn} = ${runtimeTemplate.basicFunction("moduleId, mode", [ // Per the TC39 import-defer spec, deferred namespaces are // distinct from their eager counterparts and the same module // referenced from multiple defer-import sites must yield the // same object. Cache the Proxy / fake namespace per-moduleId so // repeated calls (including across files) share identity. // // Bit 16 (`createFakeNamespaceObject`'s "return value when // it's Promise-like" flag added by // `RuntimeTemplate.moduleNamespacePromise` for dynamic // imports) is irrelevant for deferred namespaces — the value // passed into `createFakeNamespaceObject` here is always the // resolved module exports (after unwrapping the async-module // export symbol when present), never a Promise. Strip it // once so all downstream behavior, the cache key, and the // `createFakeNamespaceObject` call below see the same shape // mode. This keeps static defer (mode 8) and dynamic // `await import.defer` (mode 8 | 16) sharing the same // Deferred Module Namespace object, while still keying by // `(moduleId, mode)` so distinct exports-type shapes // (e.g. one importer treats a CJS module as // "default-with-named", another as "namespace") get // distinct cache entries. "mode &= ~16;", "var byMode = __webpack_module_deferred_namespace_cache__[moduleId];", "if (byMode && byMode[mode] !== undefined) return byMode[mode];", "if (!byMode) byMode = __webpack_module_deferred_namespace_cache__[moduleId] = {};", "var cachedModule = __webpack_module_cache__[moduleId];", "if (cachedModule && cachedModule.error === undefined && !(mode & 8)) {", Template.indent([ "var exports = cachedModule.exports;", hasAsync ? `if (${RuntimeGlobals.asyncModuleExportSymbol} in exports) exports = exports[${RuntimeGlobals.asyncModuleExportSymbol}];` : "", `return byMode[mode] = ${RuntimeGlobals.createFakeNamespaceObject}(exports, mode);` ]), "}", "", `var init = ${runtimeTemplate.basicFunction("", [ `ns = ${RuntimeGlobals.require}(moduleId);`, hasAsync ? `if (${RuntimeGlobals.asyncModuleExportSymbol} in ns) ns = ns[${RuntimeGlobals.asyncModuleExportSymbol}];` : "", "init = null;", "if (mode & 8 || mode & 4 && ns.__esModule && typeof ns === 'object') {", Template.indent([ // Drop only the read-side traps after init: with the // resolved namespace's own keys mirrored onto // `ns_target` below, the default `Reflect` behavior // returns the right values via the live-binding // getters, so we no longer need to intercept `get` / // `has` / `ownKeys` / `getOwnPropertyDescriptor`. // // The mutation traps (`set`, `deleteProperty`, // `defineProperty`) are kept because per the TC39 // import-defer spec, `[[Set]]` / `[[Delete]]` / // `[[DefineOwnProperty]]` on a Deferred Module // Namespace Exotic Object never succeed — and the // proxy target itself remains extensible // (architecturally we cannot freeze it up-front), // so without these traps `ns.notExported = "x"` // after evaluation would silently create a property // on the target instead of returning false. "delete handler.get;", "delete handler.has;", "delete handler.ownKeys;", "delete handler.getOwnPropertyDescriptor;" ]), "} else {", Template.indent([ `ns = ${RuntimeGlobals.createFakeNamespaceObject}(ns, mode);` ]), "}", // Mirror own properties from the resolved namespace onto the proxy // target so that proxy invariants hold for callers that structurally // introspect via `Object.keys` / `Object.getOwnPropertyNames` / // `Object.getOwnPropertyDescriptor`: when our trap reports a // non-configurable descriptor for a key, the target must also have // that key with a matching descriptor. // // `__esModule` and `Symbol.toStringTag` are intentionally skipped: // the proxy synthesizes "Deferred Module" / true regardless of what // the underlying namespace exposes (per the TC39 import-defer // proposal, the [[StringTag]] of a Deferred Module Namespace // Exotic Object is "Deferred Module"), and the target was already // pre-populated with those values below. "var keys = Reflect.ownKeys(ns);", "for (var i = 0; i < keys.length; i++) {", Template.indent([ "var k = keys[i];", 'if (k === "__esModule" || k === Symbol.toStringTag) continue;', "if (!Object.prototype.hasOwnProperty.call(ns_target, k)) {", Template.indent([ "try { Object.defineProperty(ns_target, k, Reflect.getOwnPropertyDescriptor(ns, k)); } catch (_) {}" ]), "}" ]), "}" ])};`, "", // The proxy target is a fresh placeholder, separate from // `__webpack_module_deferred_exports__[moduleId]` (which is reused // by `__webpack_require__` as `module.exports` for deferred-loaded // modules and would conflict with our pre-populated synthetic // `__esModule` / `Symbol.toStringTag` non-configurable properties). // Using a dedicated target keeps the proxy invariant-compliant // without interfering with the module's own exports object. "var ns_target = { __proto__: null };", // Pre-populate the synthetic deferred-namespace properties with // fully non-configurable, non-writable, non-enumerable descriptors // (matching the TC39 import-defer spec for Module Namespace // Exotic Objects). The trap returns the same descriptors below. 'Object.defineProperty(ns_target, "__esModule", { value: true });', 'Object.defineProperty(ns_target, Symbol.toStringTag, { value: "Deferred Module" });', "var ns = ns_target;", "var handler = {", Template.indent([ "__proto__: null,", // Per the TC39 import-defer proposal, `IsSymbolLikeNamespaceKey` // returns true for any Symbol-keyed access (and for "then"); such // accesses go through `OrdinaryGetOwnProperty` and must not // trigger evaluation of the deferred module. The Symbol checks // below short-circuit to the pre-populated target without // running `init()`. `get: ${runtimeTemplate.basicFunction("_, name", [ "switch (name) {", Template.indent([ 'case "__esModule": return true;', 'case Symbol.toStringTag: return "Deferred Module";', 'case "then": return undefined;' ]), "}", 'if (typeof name === "symbol") return ns_target[name];', init, "return ns[name];" ])},`, `has: ${runtimeTemplate.basicFunction("_, name", [ "switch (name) {", Template.indent( [ 'case "__esModule":', "case Symbol.toStringTag:", hasAsync ? `case ${RuntimeGlobals.deferredModuleAsyncTransitiveDependenciesSymbol}:` : "", Template.indent("return true;"), 'case "then":', Template.indent("return false;") ].filter(Boolean) ), "}", 'if (typeof name === "symbol") return name in ns_target;', init, "return name in ns;" ])},`, `ownKeys: ${runtimeTemplate.basicFunction("", [ init, `var keys = Reflect.ownKeys(ns).filter(${runtimeTemplate.expressionFunction('x !== "then" && x !== Symbol.toStringTag', "x")}).concat([Symbol.toStringTag]);`, "return keys;" ])},`, `getOwnPropertyDescriptor: ${runtimeTemplate.basicFunction("_, name", [ "switch (name) {", Template.indent([ // Match the descriptors actually defined on `ns_target` // (non-configurable, non-writable, non-enumerable) so the // proxy invariant holds for both the trap result and any // post-init forwarding via the deleted-handler path. 'case "__esModule": return { value: true, writable: false, enumerable: false, configurable: false };', 'case Symbol.toStringTag: return { value: "Deferred Module", writable: false, enumerable: false, configurable: false };', 'case "then": return undefined;' ]), "}", 'if (typeof name === "symbol") return Reflect.getOwnPropertyDescriptor(ns_target, name);', init, "var desc = Reflect.getOwnPropertyDescriptor(ns, name);", 'if (mode & 2 && name == "default" && !desc) {', Template.indent("desc = { value: ns, configurable: true };"), "}", "return desc;" ])},`, // `defineProperty` always rejects, but per the TC39 spec it // must still trigger evaluation for string keys (the spec // algorithm calls `[[GetOwnProperty]]` first, which forces // evaluation on a deferred namespace). Symbol keys go through // OrdinaryDefineOwnProperty and do not trigger eval. `defineProperty: ${runtimeTemplate.basicFunction("_, name", [ 'if (typeof name === "symbol" || name === "then") return false;', init, "return false;" ])},`, // `deleteProperty` rejects, but per the TC39 spec it must // still trigger evaluation for string keys (the spec // algorithm calls `GetModuleExportsList` for non-symbol-like // keys, forcing evaluation on a deferred namespace). `deleteProperty: ${runtimeTemplate.basicFunction("_, name", [ 'if (typeof name === "symbol" || name === "then") return false;', init, "return false;" ])},`, // `set` always returns false without triggering evaluation — // the spec [[Set]] algorithm for Module Namespaces is just // "return false" (no [[GetOwnProperty]], no eval). `set: ${runtimeTemplate.returningFunction("false")},` ]), "}", // we don't fully emulate ES Module semantics in this Proxy to align with normal webpack esm namespace object. "return byMode[mode] = new Proxy(ns_target, handler);" ])};`; } } module.exports.MakeDeferredNamespaceObjectRuntimeModule = MakeDeferredNamespaceObjectRuntimeModule; module.exports.MakeOptimizedDeferredNamespaceObjectRuntimeModule = MakeOptimizedDeferredNamespaceObjectRuntimeModule; module.exports.getMakeDeferredNamespaceModeFromExportsType = getMakeDeferredNamespaceModeFromExportsType; module.exports.getOptimizedDeferredModule = getOptimizedDeferredModule;