+12
-0
@@ -5,2 +5,14 @@ #!/usr/bin/env node | ||
| // Cache compiled module bytecode on disk (available since Node.js 22.8) | ||
| // eslint-disable-next-line n/no-unsupported-features/node-builtins | ||
| const { enableCompileCache } = require("module"); | ||
| if (enableCompileCache) { | ||
| try { | ||
| enableCompileCache(); | ||
| } catch (_error) { | ||
| // Nothing | ||
| } | ||
| } | ||
| /** | ||
@@ -7,0 +19,0 @@ * @param {string} command process to run |
+17
-15
@@ -55,2 +55,15 @@ /* | ||
| /** | ||
| * Active state to string. Module-level (depends only on ModuleGraphConnection) | ||
| * so it isn't re-allocated on every getModuleGraphHash call, incl. cache hits. | ||
| * @param {ConnectionState} state state | ||
| * @returns {"F" | "T" | "O"} result | ||
| */ | ||
| const activeStateToString = (state) => { | ||
| if (state === false) return "F"; | ||
| if (state === true) return "T"; | ||
| if (state === ModuleGraphConnection.TRANSITIVE_ONLY) return "O"; | ||
| throw new Error("Not implemented active state"); | ||
| }; | ||
| const compareModuleIterables = compareIterables(compareModulesByIdentifier); | ||
@@ -1825,18 +1838,7 @@ | ||
| /** | ||
| * Active state to string. | ||
| * @param {ConnectionState} state state | ||
| * @returns {"F" | "T" | "O"} result | ||
| */ | ||
| const activeStateToString = (state) => { | ||
| if (state === false) return "F"; | ||
| if (state === true) return "T"; | ||
| if (state === ModuleGraphConnection.TRANSITIVE_ONLY) return "O"; | ||
| throw new Error("Not implemented active state"); | ||
| }; | ||
| const strict = | ||
| module.buildMeta && | ||
| /** @type {JavascriptModuleBuildMeta} */ (module.buildMeta) | ||
| .strictHarmonyModule; | ||
| return cgm.graphHashesWithConnections.provide(runtime, () => { | ||
| const strict = | ||
| module.buildMeta && | ||
| /** @type {JavascriptModuleBuildMeta} */ (module.buildMeta) | ||
| .strictHarmonyModule; | ||
| const graphHash = this._getModuleGraphHashBigInt( | ||
@@ -1843,0 +1845,0 @@ cgm, |
@@ -184,2 +184,33 @@ /* | ||
| }); | ||
| // `const`/`let` are block-pre-walked, by which point the name is no | ||
| // longer free and `hooks.pattern` is not dispatched for it — tag | ||
| // them through the kind-specific declaration hooks instead. | ||
| // Returning nothing lets the variable be defined, keeping the tag. | ||
| for (const hookMap of [ | ||
| parser.hooks.varDeclarationConst, | ||
| parser.hooks.varDeclarationLet | ||
| ]) { | ||
| hookMap.for(RuntimeGlobals.require).tap(PLUGIN_NAME, (ident) => { | ||
| parser.tagVariable(ident.name, nestedWebpackIdentifierTag, { | ||
| name: `__nested_webpack_require_${ | ||
| /** @type {Range} */ (ident.range)[0] | ||
| }__`, | ||
| declaration: { | ||
| updated: false, | ||
| loc: parser.getLocation(ident), | ||
| range: /** @type {Range} */ (ident.range) | ||
| } | ||
| }); | ||
| }); | ||
| hookMap.for(RuntimeGlobals.exports).tap(PLUGIN_NAME, (ident) => { | ||
| parser.tagVariable(ident.name, nestedWebpackIdentifierTag, { | ||
| name: "__nested_webpack_exports__", | ||
| declaration: { | ||
| updated: false, | ||
| loc: parser.getLocation(ident), | ||
| range: /** @type {Range} */ (ident.range) | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| // Update single `var __webpack_require__ = {};` and `var __webpack_exports__ = {};` without expression | ||
@@ -186,0 +217,0 @@ parser.hooks.declarator.tap(PLUGIN_NAME, (declarator) => { |
@@ -41,8 +41,9 @@ /* | ||
| const entryExternalsToHoist = new Set(); | ||
| hooks.addContainerEntryDependency.tap(PLUGIN_NAME, (dep) => { | ||
| // Both hooks feed the same trace set, so they share one callback. | ||
| /** @type {(dep: Dependency) => void} */ | ||
| const traceDep = (dep) => { | ||
| depsToTrace.add(dep); | ||
| }); | ||
| hooks.addFederationRuntimeDependency.tap(PLUGIN_NAME, (dep) => { | ||
| depsToTrace.add(dep); | ||
| }); | ||
| }; | ||
| hooks.addContainerEntryDependency.tap(PLUGIN_NAME, traceDep); | ||
| hooks.addFederationRuntimeDependency.tap(PLUGIN_NAME, traceDep); | ||
@@ -80,9 +81,8 @@ compilation.hooks.addEntry.tap(PLUGIN_NAME, (entryDep) => { | ||
| hoistModulesInChunks(compilation, depsToTrace, entryExternalsToHoist) { | ||
| const { chunkGraph, moduleGraph } = compilation; | ||
| const { moduleGraph } = compilation; | ||
| // loop over entry points | ||
| // Entry externals: hoist the external modules (e.g. RemoteModule) they reference. | ||
| for (const dep of entryExternalsToHoist) { | ||
| const entryModule = moduleGraph.getModule(dep); | ||
| if (!entryModule) continue; | ||
| // get all the external module types and hoist them to the runtime chunk, this will get RemoteModule externals | ||
| const allReferencedModules = getAllReferencedModules( | ||
@@ -94,29 +94,10 @@ compilation, | ||
| ); | ||
| const containerRuntimes = chunkGraph.getModuleRuntimes(entryModule); | ||
| /** @type {Set<string>} */ | ||
| const runtimes = new Set(); | ||
| for (const runtimeSpec of containerRuntimes) { | ||
| forEachRuntime(runtimeSpec, (runtimeKey) => { | ||
| if (runtimeKey) { | ||
| runtimes.add(runtimeKey); | ||
| } | ||
| }); | ||
| } | ||
| for (const runtime of runtimes) { | ||
| const runtimeChunk = compilation.namedChunks.get(runtime); | ||
| if (!runtimeChunk) continue; | ||
| for (const module of allReferencedModules) { | ||
| if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) { | ||
| chunkGraph.connectChunkAndModule(runtimeChunk, module); | ||
| } | ||
| } | ||
| } | ||
| this.cleanUpChunks(compilation, allReferencedModules); | ||
| this.hoistReferencedModules( | ||
| compilation, | ||
| entryModule, | ||
| allReferencedModules | ||
| ); | ||
| } | ||
| // handle container entry specifically | ||
| // Container entries: hoist the initial graph plus its external references. | ||
| for (const dep of depsToTrace) { | ||
@@ -131,3 +112,2 @@ const containerEntryModule = moduleGraph.getModule(dep); | ||
| ); | ||
| const allRemoteReferences = getAllReferencedModules( | ||
@@ -139,32 +119,44 @@ compilation, | ||
| ); | ||
| for (const remote of allRemoteReferences) { | ||
| allReferencedModules.add(remote); | ||
| } | ||
| this.hoistReferencedModules( | ||
| compilation, | ||
| containerEntryModule, | ||
| allReferencedModules | ||
| ); | ||
| } | ||
| } | ||
| const containerRuntimes = | ||
| chunkGraph.getModuleRuntimes(containerEntryModule); | ||
| /** @type {Set<string>} */ | ||
| const runtimes = new Set(); | ||
| /** | ||
| * Connect `referencedModules` into each runtime chunk of `entryModule`, then | ||
| * prune the chunks they were hoisted out of. The two passes above build | ||
| * `referencedModules` differently but hoist them identically. | ||
| * @param {Compilation} compilation The webpack compilation instance. | ||
| * @param {Module} entryModule The module whose runtimes receive the modules. | ||
| * @param {Set<Module>} referencedModules The modules to hoist. | ||
| */ | ||
| hoistReferencedModules(compilation, entryModule, referencedModules) { | ||
| const { chunkGraph } = compilation; | ||
| /** @type {Set<string>} */ | ||
| const runtimes = new Set(); | ||
| for (const runtimeSpec of chunkGraph.getModuleRuntimes(entryModule)) { | ||
| forEachRuntime(runtimeSpec, (runtimeKey) => { | ||
| if (runtimeKey) { | ||
| runtimes.add(runtimeKey); | ||
| } | ||
| }); | ||
| } | ||
| for (const runtimeSpec of containerRuntimes) { | ||
| forEachRuntime(runtimeSpec, (runtimeKey) => { | ||
| if (runtimeKey) { | ||
| runtimes.add(runtimeKey); | ||
| } | ||
| }); | ||
| } | ||
| for (const runtime of runtimes) { | ||
| const runtimeChunk = compilation.namedChunks.get(runtime); | ||
| if (!runtimeChunk) continue; | ||
| for (const runtime of runtimes) { | ||
| const runtimeChunk = compilation.namedChunks.get(runtime); | ||
| if (!runtimeChunk) continue; | ||
| for (const module of allReferencedModules) { | ||
| if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) { | ||
| chunkGraph.connectChunkAndModule(runtimeChunk, module); | ||
| } | ||
| for (const module of referencedModules) { | ||
| if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) { | ||
| chunkGraph.connectChunkAndModule(runtimeChunk, module); | ||
| } | ||
| } | ||
| this.cleanUpChunks(compilation, allReferencedModules); | ||
| } | ||
| this.cleanUpChunks(compilation, referencedModules); | ||
| } | ||
@@ -171,0 +163,0 @@ |
+37
-11
@@ -632,2 +632,22 @@ /* | ||
| /** | ||
| * Prefixes an evaluation error with the offending define key so a bare | ||
| * "Unexpected token" becomes actionable. The original error is mutated and | ||
| * returned (not re-wrapped) so its `loc`/`stack` survive for ModuleParseError's | ||
| * code frame instead of being dumped into the message. | ||
| * @param {string} key the defined key | ||
| * @param {CodeValue} code the defined value | ||
| * @param {unknown} error the original evaluation error | ||
| * @returns {Error} the annotated error | ||
| */ | ||
| const annotateEvaluationError = (key, code, error) => { | ||
| const value = typeof code === "string" ? code : JSON.stringify(code); | ||
| const prefix = `DefinePlugin: failed to evaluate value for "${key}" (\`${value}\`)`; | ||
| if (error instanceof Error) { | ||
| error.message = `${prefix}: ${error.message}`; | ||
| return error; | ||
| } | ||
| return new WebpackError(`${prefix}: ${error}`); | ||
| }; | ||
| /** | ||
| * Defines the define plugin hooks type used by this module. | ||
@@ -958,13 +978,19 @@ * @typedef {object} DefinePluginHooks | ||
| addValueDependency(originalKey); | ||
| const codeCode = toCode( | ||
| code, | ||
| parser, | ||
| compilation.valueCacheVersions, | ||
| originalKey, | ||
| runtimeTemplate, | ||
| logger, | ||
| null | ||
| ); | ||
| const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`; | ||
| const res = parser.evaluate(typeofCode); | ||
| /** @type {BasicEvaluatedExpression} */ | ||
| let res; | ||
| try { | ||
| const codeCode = toCode( | ||
| code, | ||
| parser, | ||
| compilation.valueCacheVersions, | ||
| originalKey, | ||
| runtimeTemplate, | ||
| logger, | ||
| null | ||
| ); | ||
| const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`; | ||
| res = parser.evaluate(typeofCode); | ||
| } catch (err) { | ||
| throw annotateEvaluationError(originalKey, code, err); | ||
| } | ||
| if (!res.isString()) return; | ||
@@ -971,0 +997,0 @@ return toConstantDependency( |
@@ -243,2 +243,21 @@ /* | ||
| } | ||
| // The imported namespace ESM hasn't been flagged by | ||
| // FlagDependencyExportsPlugin yet (no exports determined), so its | ||
| // `"module.exports"` unwrap eligibility is still unknown. Star-reexporting | ||
| // now would add `__esModule` (and names) the monotonic merge can't retract | ||
| // once the module turns out to unwrap, making the result order-dependent | ||
| // across runtimes; defer — the `from.module` dependency re-queues us once | ||
| // its exports (owned names, or dynamic `other`) become known. | ||
| if ( | ||
| importedModule && | ||
| importedModule.getExportsType(moduleGraph, false) === "namespace" | ||
| ) { | ||
| const importedExportsInfo = moduleGraph.getExportsInfo(importedModule); | ||
| if ( | ||
| importedExportsInfo.otherExportsInfo.provided === false && | ||
| importedExportsInfo.ownedExports[Symbol.iterator]().next().done | ||
| ) { | ||
| return { exports: [], dependencies: [from.module] }; | ||
| } | ||
| } | ||
| const reexportInfo = this.getStarReexports( | ||
@@ -245,0 +264,0 @@ moduleGraph, |
@@ -182,3 +182,4 @@ /* | ||
| templateContext, | ||
| ids.slice(0, -1) | ||
| ids.slice(0, -1), | ||
| connection | ||
| ); | ||
@@ -185,0 +186,0 @@ source.replace( |
@@ -194,4 +194,7 @@ /* | ||
| .getReadOnlyExportInfo(firstTarget.export[0]); | ||
| /** @type {Set<ExportInfo>} */ | ||
| const visited = new Set([exportInfo, current]); | ||
| // Allocated lazily: a single-extra-hop chain that terminates (the common | ||
| // `barrel -> leaf local binding` case) returns before `next` is ever | ||
| // computed, so the visited set is pure waste there. | ||
| /** @type {Set<ExportInfo> | undefined} */ | ||
| let visited; | ||
| for (;;) { | ||
@@ -214,2 +217,3 @@ const target = current.findTarget(moduleGraph, RETURNS_TRUE); | ||
| .getReadOnlyExportInfo(target.export[0]); | ||
| if (visited === undefined) visited = new Set([exportInfo, current]); | ||
| if (visited.has(next)) return false; | ||
@@ -640,3 +644,4 @@ visited.add(next); | ||
| /** @type {IgnoredExports} */ | ||
| const ignoredExports = new Set(["default", ...this.activeExports]); | ||
| const ignoredExports = new Set(this.activeExports); | ||
| ignoredExports.add("default"); | ||
@@ -1393,3 +1398,8 @@ /** @type {Hidden | undefined} */ | ||
| case "normal-reexport": | ||
| case "normal-reexport": { | ||
| // loop-invariants hoisted out of the per-item loop below (a barrel | ||
| // re-exporting N names otherwise repeats these lookups N times) | ||
| const connection = moduleGraph.getConnection(dep); | ||
| const selfExportsInfo = moduleGraph.getExportsInfo(module); | ||
| const importedExportsInfo = moduleGraph.getExportsInfo(importedModule); | ||
| for (const { | ||
@@ -1403,3 +1413,2 @@ name, | ||
| if (checked) { | ||
| const connection = moduleGraph.getConnection(dep); | ||
| const key = `harmony reexport (checked) ${importVar} ${name}`; | ||
@@ -1433,7 +1442,5 @@ const runtimeCondition = dep.weak | ||
| "reexport safe", | ||
| moduleGraph.getExportsInfo(module).getUsedName(name, runtime), | ||
| selfExportsInfo.getUsedName(name, runtime), | ||
| importVar, | ||
| moduleGraph | ||
| .getExportsInfo(importedModule) | ||
| .getUsedName(ids, runtime), | ||
| importedExportsInfo.getUsedName(ids, runtime), | ||
| runtimeRequirements, | ||
@@ -1446,2 +1453,3 @@ ids | ||
| break; | ||
| } | ||
@@ -1448,0 +1456,0 @@ case "dynamic-reexport": { |
@@ -194,3 +194,3 @@ /* | ||
| if (importVar) return importVar; | ||
| importVar = `${Template.toIdentifier(`${this.userRequest}`)}__WEBPACK_${ | ||
| importVar = `${Template.toIdentifier(this.userRequest)}__WEBPACK_${ | ||
| isDeferred ? "DEFERRED_" : "" | ||
@@ -197,0 +197,0 @@ }IMPORTED_MODULE_${importVarMap.size}__`; |
@@ -35,2 +35,3 @@ /* | ||
| /** @typedef {import("../ModuleGraph")} ModuleGraph */ | ||
| /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ | ||
| /** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ | ||
@@ -479,3 +480,4 @@ /** @typedef {import("../errors/WebpackError")} WebpackError */ | ||
| templateContext, | ||
| trimmedIds | ||
| trimmedIds, | ||
| connection | ||
| ); | ||
@@ -526,9 +528,10 @@ if (dep.shorthand) { | ||
| ); | ||
| // loop-invariant: resolve the imported module's exports info once | ||
| const destructuredExportsInfo = moduleGraph.getExportsInfo( | ||
| /** @type {Module} */ (moduleGraph.getModule(dep)) | ||
| ); | ||
| for (const { ids, shorthand, range } of replacementsInDestructuring) { | ||
| /** @type {Ids} */ | ||
| const concatedIds = [...prefixedIds, ...ids]; | ||
| const module = /** @type {Module} */ (moduleGraph.getModule(dep)); | ||
| const used = moduleGraph | ||
| .getExportsInfo(module) | ||
| .getUsedName(concatedIds, runtime); | ||
| const used = destructuredExportsInfo.getUsedName(concatedIds, runtime); | ||
| if (!used) { | ||
@@ -565,8 +568,8 @@ return; | ||
| * @param {Ids} ids ids | ||
| * @param {ModuleGraphConnection | undefined} connection the resolved connection for dep | ||
| * @returns {string} generated code | ||
| */ | ||
| _getCodeForIds(dep, source, templateContext, ids) { | ||
| _getCodeForIds(dep, source, templateContext, ids, connection) { | ||
| const { moduleGraph, module, runtime, concatenationScope } = | ||
| templateContext; | ||
| const connection = moduleGraph.getConnection(dep); | ||
| /** @type {string} */ | ||
@@ -573,0 +576,0 @@ let exportExpr; |
+22
-5
@@ -69,2 +69,7 @@ /* | ||
| // Shared empty visited-set for `findTarget`: `_findTarget` only reads | ||
| // `alreadyVisited` (`.has`), never mutates it, so a fresh `new Set()` per call | ||
| // is pure churn on the per-export reexport hot path. Must stay never-mutated. | ||
| const EMPTY_VISITED = /** @type {Set<ExportInfo>} */ (new Set()); | ||
| const CIRCULAR = Symbol("circular target"); | ||
@@ -1403,3 +1408,5 @@ | ||
| setTarget(key, connection, exportName, priority = 0) { | ||
| if (exportName) exportName = [...exportName]; | ||
| // `equals` compares element-wise, so the incoming `exportName` can be used | ||
| // directly for the change-check; only copy it when actually stored, so the | ||
| // common no-op (unchanged target) path allocates nothing. | ||
| if (!this._target) { | ||
@@ -1409,3 +1416,5 @@ this._target = /** @type {Target} */ (new Map()); | ||
| connection, | ||
| export: /** @type {ExportInfoName[]} */ (exportName), | ||
| export: /** @type {ExportInfoName[]} */ ( | ||
| exportName ? [...exportName] : exportName | ||
| ), | ||
| priority | ||
@@ -1420,3 +1429,5 @@ }); | ||
| connection, | ||
| export: /** @type {ExportInfoName[]} */ (exportName), | ||
| export: /** @type {ExportInfoName[]} */ ( | ||
| exportName ? [...exportName] : exportName | ||
| ), | ||
| priority | ||
@@ -1435,3 +1446,5 @@ }); | ||
| oldTarget.connection = connection; | ||
| oldTarget.export = /** @type {ExportInfoName[]} */ (exportName); | ||
| oldTarget.export = /** @type {ExportInfoName[]} */ ( | ||
| exportName ? [...exportName] : exportName | ||
| ); | ||
| oldTarget.priority = priority; | ||
@@ -1586,3 +1599,7 @@ this._maxTarget = undefined; | ||
| findTarget(moduleGraph, validTargetModuleFilter) { | ||
| return this._findTarget(moduleGraph, validTargetModuleFilter, new Set()); | ||
| return this._findTarget( | ||
| moduleGraph, | ||
| validTargetModuleFilter, | ||
| EMPTY_VISITED | ||
| ); | ||
| } | ||
@@ -1589,0 +1606,0 @@ |
@@ -33,2 +33,15 @@ /* | ||
| // Hoisted stateless predicates for setUsedConditionally on the innermost | ||
| // used-export loop, so they aren't re-allocated per export. | ||
| /** | ||
| * @param {import("./ExportsInfo").UsageStateType} used usage state | ||
| * @returns {boolean} whether unused | ||
| */ | ||
| const IS_UNUSED = (used) => used === UsageState.Unused; | ||
| /** | ||
| * @param {import("./ExportsInfo").UsageStateType} used usage state | ||
| * @returns {boolean} whether not used | ||
| */ | ||
| const IS_NOT_USED = (used) => used !== UsageState.Used; | ||
| class FlagDependencyUsagePlugin { | ||
@@ -166,3 +179,3 @@ /** | ||
| exportInfo.setUsedConditionally( | ||
| (used) => used === UsageState.Unused, | ||
| IS_UNUSED, | ||
| UsageState.OnlyPropertiesUsed, | ||
@@ -186,3 +199,3 @@ runtime | ||
| exportInfo.setUsedConditionally( | ||
| (v) => v !== UsageState.Used, | ||
| IS_NOT_USED, | ||
| UsageState.Used, | ||
@@ -235,4 +248,5 @@ runtime | ||
| // (and marked used) instead of being dropped by the escape marker. | ||
| /** @type {Set<Module>} */ | ||
| const mangleableEscapeModules = new Set(); | ||
| // Lazily allocated — usually empty. | ||
| /** @type {Set<Module> | undefined} */ | ||
| let mangleableEscapeModules; | ||
@@ -280,3 +294,5 @@ /** @type {ArrayQueue<DependenciesBlock>} */ | ||
| map.set(module, EXPORTS_OBJECT_REFERENCED); | ||
| mangleableEscapeModules.delete(module); | ||
| if (mangleableEscapeModules) { | ||
| mangleableEscapeModules.delete(module); | ||
| } | ||
| continue; | ||
@@ -292,2 +308,5 @@ } | ||
| ) { | ||
| if (mangleableEscapeModules === undefined) { | ||
| mangleableEscapeModules = new Set(); | ||
| } | ||
| mangleableEscapeModules.add(module); | ||
@@ -367,9 +386,11 @@ continue; | ||
| } | ||
| for (const module of mangleableEscapeModules) { | ||
| processReferencedModule( | ||
| module, | ||
| EXPORTS_OBJECT_REFERENCED_MANGLEABLE, | ||
| runtime, | ||
| forceSideEffects | ||
| ); | ||
| if (mangleableEscapeModules) { | ||
| for (const module of mangleableEscapeModules) { | ||
| processReferencedModule( | ||
| module, | ||
| EXPORTS_OBJECT_REFERENCED_MANGLEABLE, | ||
| runtime, | ||
| forceSideEffects | ||
| ); | ||
| } | ||
| } | ||
@@ -376,0 +397,0 @@ }; |
@@ -97,3 +97,5 @@ /* | ||
| }); | ||
| req.socket.setNoDelay(true); | ||
| // Not all runtimes expose `setNoDelay` on the request socket (e.g. Deno's | ||
| // HTTP server); it's only a latency optimization, so skip it when absent. | ||
| if (req.socket.setNoDelay) req.socket.setNoDelay(true); | ||
| res.writeHead(200, { | ||
@@ -172,8 +174,21 @@ "content-type": "text/event-stream", | ||
| server.off("request", requestListener); | ||
| server.close((err) => { | ||
| callback(err); | ||
| }); | ||
| for (const socket of sockets) { | ||
| socket.destroy(new Error("Server is disposing")); | ||
| } | ||
| // Some runtimes (e.g. Deno) don't emit "connection", so `sockets` | ||
| // misses the open SSE connections and `server.close` would hang; | ||
| // force-close everything before waiting for it. | ||
| if (server.closeAllConnections) server.closeAllConnections(); | ||
| server.close((err) => { | ||
| // `closeAllConnections()` already stops the server on some runtimes | ||
| // (e.g. Bun), so `close` then reports ERR_SERVER_NOT_RUNNING; the | ||
| // server is closed either way, so that's not a dispose failure. | ||
| callback( | ||
| err && | ||
| /** @type {NodeJS.ErrnoException} */ (err).code !== | ||
| "ERR_SERVER_NOT_RUNNING" | ||
| ? err | ||
| : null | ||
| ); | ||
| }); | ||
| }, | ||
@@ -180,0 +195,0 @@ module(originalModule) { |
@@ -164,2 +164,3 @@ /* | ||
| /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ | ||
| /** @typedef {import("../Module")} Module */ | ||
| /** @typedef {import("../Module").SourceType} SourceType */ | ||
@@ -205,2 +206,23 @@ /** @typedef {import("../Module").SourceTypes} SourceTypes */ | ||
| /** @type {WeakMap<Compilation, Map<string, Module>>} */ | ||
| const modulesByIdentifierCache = new WeakMap(); | ||
| /** | ||
| * `module.identifier()` → module lookup, memoized per compilation so the asset | ||
| * URL sentinel resolver doesn't re-scan the whole module graph per HTML module. | ||
| * @param {Compilation} compilation compilation | ||
| * @returns {Map<string, Module>} modules keyed by identifier | ||
| */ | ||
| const getModulesByIdentifier = (compilation) => { | ||
| let modulesByIdentifier = modulesByIdentifierCache.get(compilation); | ||
| if (modulesByIdentifier === undefined) { | ||
| modulesByIdentifier = new Map(); | ||
| for (const module of compilation.modules) { | ||
| modulesByIdentifier.set(module.identifier(), module); | ||
| } | ||
| modulesByIdentifierCache.set(compilation, modulesByIdentifier); | ||
| } | ||
| return modulesByIdentifier; | ||
| }; | ||
| // Hoisted so `resolveChunkUrlSentinels` (per chunk × module) doesn't allocate a | ||
@@ -418,5 +440,3 @@ // fresh `RegExp` each call. `String#replace` resets `lastIndex`, so sharing the | ||
| if (!content.includes("__WEBPACK_HTML_ASSET_URL__")) return content; | ||
| /** @type {Map<string, import("../Module")>} */ | ||
| const byIdentifier = new Map(); | ||
| for (const m of compilation.modules) byIdentifier.set(m.identifier(), m); | ||
| const byIdentifier = getModulesByIdentifier(compilation); | ||
| const codeGenerationResults = | ||
@@ -423,0 +443,0 @@ /** @type {import("../CodeGenerationResults")} */ |
+24
-10
@@ -21,2 +21,13 @@ /* | ||
| // Numbers longer than this are written in exponential form ("...e+xx"), so a | ||
| // longer string can't be a plain integer id. | ||
| const MAX_NUMERIC_STRING_LENGTH = 21; | ||
| // Char codes bounding the "looks like a plain number" fast check in `avoidNumber`. | ||
| const CC_HYPHEN_MINUS = 45; | ||
| const CC_DIGIT_ONE = 49; | ||
| const CC_DIGIT_NINE = 57; | ||
| // Long ids are truncated to this length and disambiguated with a short hash. | ||
| const MAX_SHORTENED_STRING_LENGTH = 100; | ||
| const SHORTENED_STRING_HASH_LENGTH = 6; | ||
| /** | ||
@@ -42,10 +53,8 @@ * Returns hash. | ||
| const avoidNumber = (str) => { | ||
| // max length of a number is 21 chars, bigger numbers a written as "...e+xx" | ||
| if (str.length > 21) return str; | ||
| if (str.length > MAX_NUMERIC_STRING_LENGTH) return str; | ||
| const firstChar = str.charCodeAt(0); | ||
| // skip everything that doesn't look like a number | ||
| // charCodes: "-": 45, "1": 49, "9": 57 | ||
| if (firstChar < 49) { | ||
| if (firstChar !== 45) return str; | ||
| } else if (firstChar > 57) { | ||
| // Skip everything that doesn't start like a number ("-" or a digit). | ||
| if (firstChar < CC_DIGIT_ONE) { | ||
| if (firstChar !== CC_HYPHEN_MINUS) return str; | ||
| } else if (firstChar > CC_DIGIT_NINE) { | ||
| return str; | ||
@@ -75,7 +84,12 @@ } | ||
| const shortenLongString = (string, delimiter, hashFunction) => { | ||
| if (string.length < 100) return string; | ||
| if (string.length < MAX_SHORTENED_STRING_LENGTH) return string; | ||
| return ( | ||
| string.slice(0, 100 - 6 - delimiter.length) + | ||
| string.slice( | ||
| 0, | ||
| MAX_SHORTENED_STRING_LENGTH - | ||
| SHORTENED_STRING_HASH_LENGTH - | ||
| delimiter.length | ||
| ) + | ||
| delimiter + | ||
| getHash(string, 6, hashFunction) | ||
| getHash(string, SHORTENED_STRING_HASH_LENGTH, hashFunction) | ||
| ); | ||
@@ -82,0 +96,0 @@ }; |
+20
-32
@@ -32,26 +32,2 @@ /* | ||
| /** | ||
| * Extract fragment index. | ||
| * @template T | ||
| * @param {T} fragment the init fragment | ||
| * @param {number} index index | ||
| * @returns {[T, number]} tuple with both | ||
| */ | ||
| const extractFragmentIndex = (fragment, index) => [fragment, index]; | ||
| /** | ||
| * Sorts fragment with index. | ||
| * @template T | ||
| * @param {[MaybeMergeableInitFragment<T>, number]} a first pair | ||
| * @param {[MaybeMergeableInitFragment<T>, number]} b second pair | ||
| * @returns {number} sort value | ||
| */ | ||
| const sortFragmentWithIndex = ([a, i], [b, j]) => { | ||
| const stageCmp = a.stage - b.stage; | ||
| if (stageCmp !== 0) return stageCmp; | ||
| const positionCmp = a.position - b.position; | ||
| if (positionCmp !== 0) return positionCmp; | ||
| return i - j; | ||
| }; | ||
| /** | ||
| * Represents InitFragment. | ||
@@ -111,12 +87,24 @@ * @template GenerateContext | ||
| if (initFragments.length > 0) { | ||
| // Sort fragments by position. If 2 fragments have the same position, | ||
| // use their index. | ||
| const sortedFragments = initFragments | ||
| .map(extractFragmentIndex) | ||
| .sort(sortFragmentWithIndex); | ||
| // Sort fragment indices by (stage, position), falling back to the | ||
| // original index — one flat number array instead of N [fragment, index] | ||
| // tuples for a stable-by-index order. | ||
| const sortedIndices = initFragments.map((_, index) => index); | ||
| sortedIndices.sort((a, b) => { | ||
| const fragmentA = initFragments[a]; | ||
| const fragmentB = initFragments[b]; | ||
| const stageCmp = fragmentA.stage - fragmentB.stage; | ||
| if (stageCmp !== 0) return stageCmp; | ||
| const positionCmp = fragmentA.position - fragmentB.position; | ||
| if (positionCmp !== 0) return positionCmp; | ||
| return a - b; | ||
| }); | ||
| // Deduplicate fragments. If a fragment has no key, it is always included. | ||
| /** @type {Map<InitFragmentKey | symbol, MaybeMergeableInitFragment<Context> | MaybeMergeableInitFragment<Context>[]>} */ | ||
| // Keyless fragments get a unique numeric key; number keys never collide | ||
| // with the string `InitFragmentKey`s used for keyed fragments. | ||
| /** @type {Map<InitFragmentKey | number, MaybeMergeableInitFragment<Context> | MaybeMergeableInitFragment<Context>[]>} */ | ||
| const keyedFragments = new Map(); | ||
| for (const [fragment] of sortedFragments) { | ||
| let keylessKey = 0; | ||
| for (const index of sortedIndices) { | ||
| const fragment = initFragments[index]; | ||
| if (typeof fragment.mergeAll === "function") { | ||
@@ -147,3 +135,3 @@ if (!fragment.key) { | ||
| } | ||
| keyedFragments.set(fragment.key || Symbol("fragment key"), fragment); | ||
| keyedFragments.set(fragment.key || keylessKey++, fragment); | ||
| } | ||
@@ -150,0 +138,0 @@ |
@@ -80,3 +80,7 @@ /* | ||
| "resolve({", | ||
| Template.indent(["arrayBuffer() { return buffer; }"]), | ||
| Template.indent([ | ||
| // Return a real ArrayBuffer: some runtimes (e.g. Deno) | ||
| // reject a Node Buffer view here as "not a buffer source". | ||
| "arrayBuffer() { return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); }" | ||
| ]), | ||
| "});" | ||
@@ -103,3 +107,7 @@ ]), | ||
| "resolve({", | ||
| Template.indent(["arrayBuffer() { return buffer; }"]), | ||
| Template.indent([ | ||
| // Return a real ArrayBuffer: some runtimes (e.g. Deno) | ||
| // reject a Node Buffer view here as "not a buffer source". | ||
| "arrayBuffer() { return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); }" | ||
| ]), | ||
| "});" | ||
@@ -106,0 +114,0 @@ ]), |
@@ -80,3 +80,7 @@ /* | ||
| "resolve({", | ||
| Template.indent(["arrayBuffer() { return buffer; }"]), | ||
| Template.indent([ | ||
| // Return a real ArrayBuffer: some runtimes (e.g. Deno) | ||
| // reject a Node Buffer view here as "not a buffer source". | ||
| "arrayBuffer() { return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); }" | ||
| ]), | ||
| "});" | ||
@@ -103,3 +107,7 @@ ]), | ||
| "resolve({", | ||
| Template.indent(["arrayBuffer() { return buffer; }"]), | ||
| Template.indent([ | ||
| // Return a real ArrayBuffer: some runtimes (e.g. Deno) | ||
| // reject a Node Buffer view here as "not a buffer source". | ||
| "arrayBuffer() { return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); }" | ||
| ]), | ||
| "});" | ||
@@ -106,0 +114,0 @@ ]), |
@@ -56,4 +56,12 @@ /* | ||
| const chunkGraph = compilation.chunkGraph; | ||
| /** @type {{ a: Chunk, b: Chunk, improvement: number }[]} */ | ||
| const combinations = []; | ||
| // Only the single best pair is merged per pass (the hook re-runs | ||
| // after each merge), so track the max directly instead of building | ||
| // and sorting the full O(chunks²) list of pairs every pass. A strict | ||
| // `>` keeps the first-encountered best, matching the previous stable | ||
| // descending sort's `[0]`. | ||
| /** @type {Chunk | undefined} */ | ||
| let bestA; | ||
| /** @type {Chunk | undefined} */ | ||
| let bestB; | ||
| let bestImprovement = -Infinity; | ||
| for (const a of chunks) { | ||
@@ -77,19 +85,15 @@ if (a.canBeInitial()) continue; | ||
| const improvement = (aSize + bSize) / abSize; | ||
| combinations.push({ | ||
| a, | ||
| b, | ||
| improvement | ||
| }); | ||
| if (improvement > bestImprovement) { | ||
| bestImprovement = improvement; | ||
| bestA = a; | ||
| bestB = b; | ||
| } | ||
| } | ||
| } | ||
| combinations.sort((a, b) => b.improvement - a.improvement); | ||
| if (bestA === undefined) return; | ||
| if (bestImprovement < minSizeReduce) return; | ||
| const pair = combinations[0]; | ||
| if (!pair) return; | ||
| if (pair.improvement < minSizeReduce) return; | ||
| chunkGraph.integrateChunks(pair.b, pair.a); | ||
| compilation.chunks.delete(pair.a); | ||
| chunkGraph.integrateChunks(/** @type {Chunk} */ (bestB), bestA); | ||
| compilation.chunks.delete(bestA); | ||
| return true; | ||
@@ -96,0 +100,0 @@ } |
@@ -74,2 +74,19 @@ /* | ||
| /** | ||
| * Record why `module` can't be added (so retried lookups hit the cache), bump | ||
| * the matching statistic, and return the problem to bubble up the recursion. | ||
| * Only reached on failure, so it stays off the hot success path. | ||
| * @param {Map<Module, Problem>} failureCache per-module failure cache | ||
| * @param {Statistics} statistics running statistics | ||
| * @param {keyof Statistics} statKey statistic to increment | ||
| * @param {Module} module the module that couldn't be added | ||
| * @param {Problem} problem the failure to cache and return | ||
| * @returns {Problem} the same problem | ||
| */ | ||
| const cacheFailure = (failureCache, statistics, statKey, module, problem) => { | ||
| statistics[statKey]++; | ||
| failureCache.set(module, problem); | ||
| return problem; | ||
| }; | ||
| /** | ||
| * Format bailout reason. | ||
@@ -691,5 +708,9 @@ * @param {string} msg message | ||
| if (!possibleModules.has(module)) { | ||
| statistics.invalidModule++; | ||
| failureCache.set(module, module); // cache failures for performance | ||
| return module; | ||
| return cacheFailure( | ||
| failureCache, | ||
| statistics, | ||
| "invalidModule", | ||
| module, | ||
| module | ||
| ); | ||
| } | ||
@@ -712,5 +733,9 @@ | ||
| )} imports a module in this configuration via import defer`; | ||
| statistics.incorrectDependency++; | ||
| failureCache.set(module, problem); // cache failures for performance | ||
| return problem; | ||
| return cacheFailure( | ||
| failureCache, | ||
| statistics, | ||
| "incorrectDependency", | ||
| module, | ||
| problem | ||
| ); | ||
| } | ||
@@ -747,5 +772,9 @@ | ||
| }; | ||
| statistics.incorrectChunks++; | ||
| failureCache.set(module, problem); // cache failures for performance | ||
| return problem; | ||
| return cacheFailure( | ||
| failureCache, | ||
| statistics, | ||
| "incorrectChunks", | ||
| module, | ||
| problem | ||
| ); | ||
| } | ||
@@ -789,5 +818,9 @@ | ||
| }; | ||
| statistics.incorrectDependency++; | ||
| failureCache.set(module, problem); // cache failures for performance | ||
| return problem; | ||
| return cacheFailure( | ||
| failureCache, | ||
| statistics, | ||
| "incorrectDependency", | ||
| module, | ||
| problem | ||
| ); | ||
| } | ||
@@ -849,5 +882,9 @@ } | ||
| }; | ||
| statistics.incorrectChunksOfImporter++; | ||
| failureCache.set(module, problem); // cache failures for performance | ||
| return problem; | ||
| return cacheFailure( | ||
| failureCache, | ||
| statistics, | ||
| "incorrectChunksOfImporter", | ||
| module, | ||
| problem | ||
| ); | ||
| } | ||
@@ -902,5 +939,9 @@ | ||
| }; | ||
| statistics.incorrectModuleDependency++; | ||
| failureCache.set(module, problem); // cache failures for performance | ||
| return problem; | ||
| return cacheFailure( | ||
| failureCache, | ||
| statistics, | ||
| "incorrectModuleDependency", | ||
| module, | ||
| problem | ||
| ); | ||
| } | ||
@@ -956,5 +997,9 @@ | ||
| ).join(", ")}`; | ||
| statistics.incorrectRuntimeCondition++; | ||
| failureCache.set(module, problem); // cache failures for performance | ||
| return problem; | ||
| return cacheFailure( | ||
| failureCache, | ||
| statistics, | ||
| "incorrectRuntimeCondition", | ||
| module, | ||
| problem | ||
| ); | ||
| } | ||
@@ -992,5 +1037,9 @@ } | ||
| if (backup !== undefined) config.rollback(backup); | ||
| statistics.importerFailed++; | ||
| failureCache.set(module, problem); // cache failures for performance | ||
| return problem; | ||
| return cacheFailure( | ||
| failureCache, | ||
| statistics, | ||
| "importerFailed", | ||
| module, | ||
| problem | ||
| ); | ||
| } | ||
@@ -997,0 +1046,0 @@ } |
@@ -103,3 +103,64 @@ /* | ||
| // Initial-graph auto hints target JS chunk output files only. | ||
| const JS_CHUNK_FILE_REGEXP = /\.m?jsx?$/i; | ||
| /** | ||
| * True for the URL-referenced-asset deps that can carry resource-hint flags. | ||
| * Type guard so callers keep the narrowed union without re-listing the classes. | ||
| * @param {import("../Dependency")} dep dependency | ||
| * @returns {dep is URLDependency | CssUrlDependency | HtmlSourceDependency} whether it is a URL asset dep | ||
| */ | ||
| const isUrlAssetDep = (dep) => | ||
| dep instanceof URLDependency || | ||
| dep instanceof CssUrlDependency || | ||
| dep instanceof HtmlSourceDependency; | ||
| /** | ||
| * True for the CSS / HTML URL-asset deps whose templates don't run for JS | ||
| * output — the plugin lifts their hint flags into JS runtime requirements | ||
| * itself. JS `URLDependency` lifts its own, so it is excluded here. | ||
| * @param {import("../Dependency")} dep dependency | ||
| * @returns {dep is CssUrlDependency | HtmlSourceDependency} whether it is a CSS/HTML URL asset dep | ||
| */ | ||
| const isCssOrHtmlUrlAssetDep = (dep) => | ||
| dep instanceof CssUrlDependency || dep instanceof HtmlSourceDependency; | ||
| /** | ||
| * Entry chunk name for a hint's `hostChunks` (Vite's `hostId`); the id is a | ||
| * stable fallback for unnamed chunks. | ||
| * @param {import("../Chunk")} chunk chunk | ||
| * @returns {string} name or stringified id | ||
| */ | ||
| const chunkHostName = (chunk) => chunk.name || String(chunk.id); | ||
| /** | ||
| * Walk `chunks` × their modules × dependencies and yield each distinct | ||
| * URL-asset module carrying a prefetch/preload flag, deduping targets within a | ||
| * single call. One shared walk for the entrypoint-hint and HTML-hint passes, | ||
| * which would otherwise each re-implement the same 4-level nesting. | ||
| * @param {Compilation} compilation compilation | ||
| * @param {Iterable<import("../Chunk")>} chunks chunks to scan | ||
| * @returns {IterableIterator<{ dep: URLDependency | CssUrlDependency | HtmlSourceDependency, target: Module, chunk: import("../Chunk") }>} hinted assets | ||
| */ | ||
| function* iterateHintedUrlAssets(compilation, chunks) { | ||
| const { chunkGraph, moduleGraph } = compilation; | ||
| /** @type {WeakSet<Module>} */ | ||
| const seen = new WeakSet(); | ||
| for (const chunk of chunks) { | ||
| for (const module of chunkGraph.getChunkModulesIterable(chunk)) { | ||
| const deps = module.dependencies; | ||
| if (!deps) continue; | ||
| for (const dep of deps) { | ||
| if (!isUrlAssetDep(dep)) continue; | ||
| if (!dep.prefetch && !dep.preload) continue; | ||
| const target = moduleGraph.getModule(dep); | ||
| if (!target || seen.has(target)) continue; | ||
| seen.add(target); | ||
| yield { dep, target, chunk }; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Merge the matching `UrlHintRule`s for a request. Rules match by | ||
@@ -242,3 +303,3 @@ * `test`/`include`/`exclude` (omit all three → matches everything); later | ||
| for (const file of chunk.files) { | ||
| if (!/\.m?jsx?$/i.test(file)) continue; | ||
| if (!JS_CHUNK_FILE_REGEXP.test(file)) continue; | ||
| const href = publicPath + file; | ||
@@ -249,3 +310,3 @@ /** @type {EntrypointHint} */ | ||
| href, | ||
| hostChunks: [chunk.name || String(chunk.id)] | ||
| hostChunks: [chunkHostName(chunk)] | ||
| }; | ||
@@ -262,41 +323,26 @@ if (rel === "preload") h.as = "script"; | ||
| const chunkSet = new Set(entrypoint.chunks); | ||
| const rt = entrypoint.getRuntimeChunk(); | ||
| if (rt) chunkSet.add(rt); | ||
| /** @type {WeakSet<Module>} */ | ||
| const seenAssets = new WeakSet(); | ||
| for (const chunk of chunkSet) { | ||
| for (const m of compilation.chunkGraph.getChunkModulesIterable(chunk)) { | ||
| if (!m.dependencies) continue; | ||
| for (const d of m.dependencies) { | ||
| if ( | ||
| !(d instanceof URLDependency) && | ||
| !(d instanceof CssUrlDependency) && | ||
| !(d instanceof HtmlSourceDependency) | ||
| ) { | ||
| continue; | ||
| } | ||
| if (!d.prefetch && !d.preload) continue; | ||
| const t = compilation.moduleGraph.getModule(d); | ||
| if (!t || seenAssets.has(t)) continue; | ||
| seenAssets.add(t); | ||
| const buildInfo = | ||
| /** @type {{ filename?: string }} */ | ||
| (t.buildInfo); | ||
| if (!buildInfo || !buildInfo.filename) continue; | ||
| const asAttr = | ||
| d.asAttribute || | ||
| ResourceHintRuntimeModule.guessAsAttribute(d.request); | ||
| /** @type {EntrypointHint} */ | ||
| const h = { | ||
| rel: d.preload ? "preload" : "prefetch", | ||
| href: publicPath + buildInfo.filename, | ||
| hostChunks: [chunk.name || String(chunk.id)] | ||
| }; | ||
| if (asAttr) h.as = asAttr; | ||
| if (d.typeAttribute) h.type = d.typeAttribute; | ||
| if (d.mediaAttribute) h.media = d.mediaAttribute; | ||
| if (d.fetchPriority) h.fetchPriority = d.fetchPriority; | ||
| push(h); | ||
| } | ||
| } | ||
| const runtimeChunk = entrypoint.getRuntimeChunk(); | ||
| if (runtimeChunk) chunkSet.add(runtimeChunk); | ||
| for (const { dep, target, chunk } of iterateHintedUrlAssets( | ||
| compilation, | ||
| chunkSet | ||
| )) { | ||
| const buildInfo = | ||
| /** @type {{ filename?: string }} */ | ||
| (target.buildInfo); | ||
| if (!buildInfo || !buildInfo.filename) continue; | ||
| const asAttribute = | ||
| dep.asAttribute || | ||
| ResourceHintRuntimeModule.guessAsAttribute(dep.request); | ||
| /** @type {EntrypointHint} */ | ||
| const h = { | ||
| rel: dep.preload ? "preload" : "prefetch", | ||
| href: publicPath + buildInfo.filename, | ||
| hostChunks: [chunkHostName(chunk)] | ||
| }; | ||
| if (asAttribute) h.as = asAttribute; | ||
| if (dep.typeAttribute) h.type = dep.typeAttribute; | ||
| if (dep.mediaAttribute) h.media = dep.mediaAttribute; | ||
| if (dep.fetchPriority) h.fetchPriority = dep.fetchPriority; | ||
| push(h); | ||
| } | ||
@@ -500,29 +546,12 @@ // `hostType`: `"html"` iff the entrypoint has an extracted HTML page | ||
| if (entry) chunks.add(entry); | ||
| const rt = entrypoint.getRuntimeChunk(); | ||
| if (rt) chunks.add(rt); | ||
| const runtimeChunk = entrypoint.getRuntimeChunk(); | ||
| if (runtimeChunk) chunks.add(runtimeChunk); | ||
| /** @type {HtmlHintedAsset[]} */ | ||
| const hinted = []; | ||
| /** @type {WeakSet<Module>} */ | ||
| const seen = new WeakSet(); | ||
| for (const c of chunks) { | ||
| for (const m of compilation.chunkGraph.getChunkModulesIterable( | ||
| c | ||
| )) { | ||
| if (!m.dependencies) continue; | ||
| for (const d of m.dependencies) { | ||
| if ( | ||
| !(d instanceof URLDependency) && | ||
| !(d instanceof CssUrlDependency) && | ||
| !(d instanceof HtmlSourceDependency) | ||
| ) { | ||
| continue; | ||
| } | ||
| if (!d.prefetch && !d.preload) continue; | ||
| const t = compilation.moduleGraph.getModule(d); | ||
| if (!t || seen.has(t)) continue; | ||
| seen.add(t); | ||
| anyHtmlHinted.add(t); | ||
| hinted.push({ module: t, dep: d }); | ||
| } | ||
| } | ||
| for (const { dep: assetDep, target } of iterateHintedUrlAssets( | ||
| compilation, | ||
| chunks | ||
| )) { | ||
| anyHtmlHinted.add(target); | ||
| hinted.push({ module: target, dep: assetDep }); | ||
| } | ||
@@ -579,8 +608,3 @@ // Multiple HtmlEntryDeps may share an entryName (e.g. a | ||
| for (const dep of deps) { | ||
| if ( | ||
| !(dep instanceof CssUrlDependency) && | ||
| !(dep instanceof HtmlSourceDependency) | ||
| ) { | ||
| continue; | ||
| } | ||
| if (!isCssOrHtmlUrlAssetDep(dep)) continue; | ||
| if (dep.prefetch) hasPrefetch = true; | ||
@@ -587,0 +611,0 @@ if (dep.preload) hasPreload = true; |
+40
-113
@@ -122,2 +122,29 @@ /* | ||
| // Tree runtime requirements whose only effect is attaching a zero-arg runtime | ||
| // module to the chunk. Conditional / argument-taking ones stay as explicit taps. | ||
| /** @type {[string, (new () => import("./RuntimeModule"))][]} */ | ||
| const SIMPLE_TREE_RUNTIME_MODULES = [ | ||
| [RuntimeGlobals.definePropertyGetters, DefinePropertyGettersRuntimeModule], | ||
| [RuntimeGlobals.makeNamespaceObject, MakeNamespaceObjectRuntimeModule], | ||
| [ | ||
| RuntimeGlobals.createFakeNamespaceObject, | ||
| CreateFakeNamespaceObjectRuntimeModule | ||
| ], | ||
| [RuntimeGlobals.hasOwnProperty, HasOwnPropertyRuntimeModule], | ||
| [RuntimeGlobals.compatGetDefaultExport, CompatGetDefaultExportRuntimeModule], | ||
| [RuntimeGlobals.commonJsWrap, CommonJsWrapRuntimeModule], | ||
| [ | ||
| RuntimeGlobals.setAnonymousDefaultName, | ||
| SetAnonymousDefaultNameRuntimeModule | ||
| ], | ||
| [RuntimeGlobals.runtimeId, RuntimeIdRuntimeModule], | ||
| [RuntimeGlobals.global, GlobalRuntimeModule], | ||
| [RuntimeGlobals.shareScopeMap, ShareRuntimeModule], | ||
| [RuntimeGlobals.relativeUrl, RelativeUrlRuntimeModule], | ||
| [RuntimeGlobals.worker, WorkerRuntimeModule], | ||
| [RuntimeGlobals.onChunksLoaded, OnChunksLoadedRuntimeModule], | ||
| [RuntimeGlobals.scriptNonce, NonceRuntimeModule], | ||
| [RuntimeGlobals.toBinary, ToBinaryRuntimeModule] | ||
| ]; | ||
| /** | ||
@@ -202,32 +229,16 @@ * @param {string} template path template | ||
| } | ||
| for (const [ | ||
| runtimeGlobal, | ||
| RuntimeModule | ||
| ] of SIMPLE_TREE_RUNTIME_MODULES) { | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(runtimeGlobal) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule(chunk, new RuntimeModule()); | ||
| return true; | ||
| }); | ||
| } | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.definePropertyGetters) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule( | ||
| chunk, | ||
| new DefinePropertyGettersRuntimeModule() | ||
| ); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.makeNamespaceObject) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule( | ||
| chunk, | ||
| new MakeNamespaceObjectRuntimeModule() | ||
| ); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.createFakeNamespaceObject) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule( | ||
| chunk, | ||
| new CreateFakeNamespaceObjectRuntimeModule() | ||
| ); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.makeOptimizedDeferredNamespaceObject) | ||
| .tap("RuntimePlugin", (chunk, runtimeRequirement) => { | ||
| .tap(PLUGIN_NAME, (chunk, runtimeRequirement) => { | ||
| compilation.addRuntimeModule( | ||
@@ -243,3 +254,3 @@ chunk, | ||
| .for(RuntimeGlobals.makeDeferredNamespaceObject) | ||
| .tap("RuntimePlugin", (chunk, runtimeRequirement) => { | ||
| .tap(PLUGIN_NAME, (chunk, runtimeRequirement) => { | ||
| compilation.addRuntimeModule( | ||
@@ -254,41 +265,2 @@ chunk, | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.hasOwnProperty) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule( | ||
| chunk, | ||
| new HasOwnPropertyRuntimeModule() | ||
| ); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.compatGetDefaultExport) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule( | ||
| chunk, | ||
| new CompatGetDefaultExportRuntimeModule() | ||
| ); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.commonJsWrap) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule(chunk, new CommonJsWrapRuntimeModule()); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.setAnonymousDefaultName) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule( | ||
| chunk, | ||
| new SetAnonymousDefaultNameRuntimeModule() | ||
| ); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.runtimeId) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule(chunk, new RuntimeIdRuntimeModule()); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.publicPath) | ||
@@ -329,8 +301,2 @@ .tap(PLUGIN_NAME, (chunk, set) => { | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.global) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule(chunk, new GlobalRuntimeModule()); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.asyncModule) | ||
@@ -509,8 +475,2 @@ .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.shareScopeMap) | ||
| .tap(PLUGIN_NAME, (chunk, set) => { | ||
| compilation.addRuntimeModule(chunk, new ShareRuntimeModule()); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.loadScript) | ||
@@ -562,23 +522,2 @@ .tap(PLUGIN_NAME, (chunk, set) => { | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.relativeUrl) | ||
| .tap(PLUGIN_NAME, (chunk, _set) => { | ||
| compilation.addRuntimeModule(chunk, new RelativeUrlRuntimeModule()); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.worker) | ||
| .tap(PLUGIN_NAME, (chunk, _set) => { | ||
| compilation.addRuntimeModule(chunk, new WorkerRuntimeModule()); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.onChunksLoaded) | ||
| .tap(PLUGIN_NAME, (chunk, _set) => { | ||
| compilation.addRuntimeModule( | ||
| chunk, | ||
| new OnChunksLoadedRuntimeModule() | ||
| ); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.baseURI) | ||
@@ -591,14 +530,2 @@ .tap(PLUGIN_NAME, (chunk) => { | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.scriptNonce) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule(chunk, new NonceRuntimeModule()); | ||
| return true; | ||
| }); | ||
| compilation.hooks.runtimeRequirementInTree | ||
| .for(RuntimeGlobals.toBinary) | ||
| .tap(PLUGIN_NAME, (chunk) => { | ||
| compilation.addRuntimeModule(chunk, new ToBinaryRuntimeModule()); | ||
| return true; | ||
| }); | ||
| // TODO webpack 6: remove CompatRuntimeModule | ||
@@ -605,0 +532,0 @@ compilation.hooks.additionalTreeRuntimeRequirements.tap( |
@@ -7,2 +7,3 @@ /* | ||
| const { deserialize: v8Deserialize, serialize: v8Serialize } = require("v8"); | ||
| const memoize = require("../util/memoize"); | ||
@@ -19,96 +20,30 @@ const SerializerMiddleware = require("./SerializerMiddleware"); | ||
| Section -> NullsSection | | ||
| BooleansSection | | ||
| F64NumbersSection | | ||
| I32NumbersSection | | ||
| I8NumbersSection | | ||
| ShortStringSection | | ||
| BigIntSection | | ||
| I32BigIntSection | | ||
| I8BigIntSection | ||
| StringSection | | ||
| BufferSection | | ||
| NopSection | ||
| Section -> ValuesSection | LazySection | ||
| ValuesSection -> | ||
| ValuesHeaderByte u32:payloadSize u32:bufferCount (u32:valueIndex u32:bufferSize)* | ||
| payload buffer* | ||
| LazySection -> | ||
| LazyHeaderByte u32:count u32:size* | ||
| ValuesHeaderByte -> 0xf1 | ||
| LazyHeaderByte -> 0xf2 | ||
| */ | ||
| NullsSection -> | ||
| NullHeaderByte | Null2HeaderByte | Null3HeaderByte | | ||
| Nulls8HeaderByte 0xnn (n:count - 4) | | ||
| Nulls32HeaderByte n:ui32 (n:count - 260) | | ||
| BooleansSection -> TrueHeaderByte | FalseHeaderByte | BooleansSectionHeaderByte BooleansCountAndBitsByte | ||
| F64NumbersSection -> F64NumbersSectionHeaderByte f64* | ||
| I32NumbersSection -> I32NumbersSectionHeaderByte i32* | ||
| I8NumbersSection -> I8NumbersSectionHeaderByte i8* | ||
| ShortStringSection -> ShortStringSectionHeaderByte ascii-byte* | ||
| StringSection -> StringSectionHeaderByte i32:length utf8-byte* | ||
| BufferSection -> BufferSectionHeaderByte i32:length byte* | ||
| NopSection --> NopSectionHeaderByte | ||
| BigIntSection -> BigIntSectionHeaderByte i32:length ascii-byte* | ||
| I32BigIntSection -> I32BigIntSectionHeaderByte i32 | ||
| I8BigIntSection -> I8BigIntSectionHeaderByte i8 | ||
| const VALUES_HEADER = 0xf1; | ||
| const LAZY_HEADER = 0xf2; | ||
| ShortStringSectionHeaderByte -> 0b1nnn_nnnn (n:length) | ||
| const HEADER_SIZE = 1; | ||
| const I32_SIZE = 4; | ||
| F64NumbersSectionHeaderByte -> 0b001n_nnnn (n:count - 1) | ||
| I32NumbersSectionHeaderByte -> 0b010n_nnnn (n:count - 1) | ||
| I8NumbersSectionHeaderByte -> 0b011n_nnnn (n:count - 1) | ||
| /** A values section is closed once its payload is estimated to reach this size. */ | ||
| const MAX_SECTION_SIZE = 16 * 1024 * 1024; | ||
| /** Assumed encoded size of a value that isn't a string, for that estimate. */ | ||
| const ESTIMATED_VALUE_SIZE = 4; | ||
| NullsSectionHeaderByte -> 0b0001_nnnn (n:count - 1) | ||
| BooleansCountAndBitsByte -> | ||
| 0b0000_1xxx (count = 3) | | ||
| 0b0001_xxxx (count = 4) | | ||
| 0b001x_xxxx (count = 5) | | ||
| 0b01xx_xxxx (count = 6) | | ||
| 0b1nnn_nnnn (n:count - 7, 7 <= count <= 133) | ||
| 0xff n:ui32 (n:count, 134 <= count < 2^32) | ||
| const EMPTY_BUFFER = Buffer.alloc(0); | ||
| StringSectionHeaderByte -> 0b0000_1110 | ||
| BufferSectionHeaderByte -> 0b0000_1111 | ||
| NopSectionHeaderByte -> 0b0000_1011 | ||
| BigIntSectionHeaderByte -> 0b0001_1010 | ||
| I32BigIntSectionHeaderByte -> 0b0001_1100 | ||
| I8BigIntSectionHeaderByte -> 0b0001_1011 | ||
| FalseHeaderByte -> 0b0000_1100 | ||
| TrueHeaderByte -> 0b0000_1101 | ||
| /** Highest V8 value serialization format version this Node.js can read. */ | ||
| const V8_FORMAT_VERSION = v8Serialize(null)[1]; | ||
| RawNumber -> n (n <= 10) | ||
| */ | ||
| const LAZY_HEADER = 0x0b; | ||
| const TRUE_HEADER = 0x0c; | ||
| const FALSE_HEADER = 0x0d; | ||
| const BOOLEANS_HEADER = 0x0e; | ||
| const NULL_HEADER = 0x10; | ||
| const NULL2_HEADER = 0x11; | ||
| const NULL3_HEADER = 0x12; | ||
| const NULLS8_HEADER = 0x13; | ||
| const NULLS32_HEADER = 0x14; | ||
| const NULL_AND_I8_HEADER = 0x15; | ||
| const NULL_AND_I16_HEADER = 0x19; | ||
| const NULL_AND_I32_HEADER = 0x16; | ||
| const NULL_AND_TRUE_HEADER = 0x17; | ||
| const NULL_AND_FALSE_HEADER = 0x18; | ||
| const BIGINT_HEADER = 0x1a; | ||
| const BIGINT_I8_HEADER = 0x1b; | ||
| const BIGINT_I32_HEADER = 0x1c; | ||
| const STRING_HEADER = 0x1e; | ||
| const BUFFER_HEADER = 0x1f; | ||
| const I8_HEADER = 0x60; | ||
| const I32_HEADER = 0x40; | ||
| const F64_HEADER = 0x20; | ||
| const SHORT_STRING_HEADER = 0x80; | ||
| /** Uplift high-order bits */ | ||
| const NUMBERS_HEADER_MASK = 0xe0; // 0b1010_0000 | ||
| const NUMBERS_COUNT_MASK = 0x1f; // 0b0001_1111 | ||
| const SHORT_STRING_LENGTH_MASK = 0x7f; // 0b0111_1111 | ||
| const HEADER_SIZE = 1; | ||
| const I8_SIZE = 1; | ||
| const I16_SIZE = 2; | ||
| const I32_SIZE = 4; | ||
| const F64_SIZE = 8; | ||
| const MEASURE_START_OPERATION = Symbol("MEASURE_START_OPERATION"); | ||
@@ -120,26 +55,2 @@ const MEASURE_END_OPERATION = Symbol("MEASURE_END_OPERATION"); | ||
| /** | ||
| * Returns type of number for serialization. | ||
| * @param {number} n number | ||
| * @returns {0 | 1 | 2} type of number for serialization | ||
| */ | ||
| const identifyNumber = (n) => { | ||
| if (n === (n | 0)) { | ||
| if (n <= 127 && n >= -128) return 0; | ||
| if (n <= 2147483647 && n >= -2147483648) return 1; | ||
| } | ||
| return 2; | ||
| }; | ||
| /** | ||
| * Returns type of bigint for serialization. | ||
| * @param {bigint} n bigint | ||
| * @returns {0 | 1 | 2} type of bigint for serialization | ||
| */ | ||
| const identifyBigInt = (n) => { | ||
| if (n <= BigInt(127) && n >= BigInt(-128)) return 0; | ||
| if (n <= BigInt(2147483647) && n >= BigInt(-2147483648)) return 1; | ||
| return 2; | ||
| }; | ||
| /** @typedef {PrimitiveSerializableType[]} DeserializedType */ | ||
@@ -156,6 +67,3 @@ /** @typedef {BufferSerializableType[]} SerializedType} */ | ||
| /** | ||
| * Mutable read state of one `_deserialize` run; the module-level dispatch | ||
| * table operates on this instead of per-call closures. | ||
| */ | ||
| /** Mutable read state of one `_deserialize` run. */ | ||
| class ReadState { | ||
@@ -165,11 +73,6 @@ /** | ||
| * @param {Context} context context object | ||
| * @param {BinaryMiddleware} middleware binary middleware | ||
| */ | ||
| constructor(data, context, middleware) { | ||
| constructor(data, context) { | ||
| /** @type {SerializedType} */ | ||
| this.data = data; | ||
| /** @type {Context} */ | ||
| this.context = context; | ||
| /** @type {BinaryMiddleware} */ | ||
| this.middleware = middleware; | ||
| this.retainedBuffer = context.retainedBuffer || ((x) => x); | ||
@@ -179,3 +82,3 @@ /** @type {number} */ | ||
| /** @type {BufferSerializableType | null} */ | ||
| this.currentBuffer = data[0]; | ||
| this.currentBuffer = data.length > 0 ? data[0] : null; | ||
| /** @type {boolean} */ | ||
@@ -185,4 +88,2 @@ this.currentIsBuffer = Buffer.isBuffer(this.currentBuffer); | ||
| this.currentPosition = 0; | ||
| /** @type {DeserializedType} */ | ||
| this.result = []; | ||
| } | ||
@@ -238,2 +139,3 @@ | ||
| read(n) { | ||
| if (n === 0) return EMPTY_BUFFER; | ||
| this.ensureBuffer(); | ||
@@ -295,3 +197,3 @@ const rem = | ||
| (this.currentBuffer).readUInt8(this.currentPosition); | ||
| this.currentPosition += I8_SIZE; | ||
| this.currentPosition += HEADER_SIZE; | ||
| this.checkOverflow(); | ||
@@ -317,361 +219,60 @@ return byte; | ||
| } | ||
| } | ||
| /** | ||
| * Pushes the lowest n bits of data as booleans. | ||
| * @param {number} data data | ||
| * @param {number} n n | ||
| */ | ||
| readBits(data, n) { | ||
| let mask = 1; | ||
| while (n !== 0) { | ||
| this.result.push((data & mask) !== 0); | ||
| mask <<= 1; | ||
| n--; | ||
| } | ||
| /** | ||
| * Reads a section of values written by V8's value serializer. | ||
| * @param {ReadState} state read state | ||
| * @returns {DeserializedType} values of the section | ||
| */ | ||
| const readValuesSection = (state) => { | ||
| const payloadSize = state.readU32(); | ||
| const bufferCount = state.readU32(); | ||
| /** @type {number[]} */ | ||
| const bufferInfo = []; | ||
| for (let i = 0; i < bufferCount * 2; i++) bufferInfo.push(state.readU32()); | ||
| const payload = state.read(payloadSize); | ||
| // a V8 payload opens with 0xff and its format version, which only ever grows | ||
| if (payload[0] === 0xff && payload[1] > V8_FORMAT_VERSION) { | ||
| throw new Error( | ||
| `Data was written with V8 serialization format version ${payload[1]}, but this Node.js (${process.version}) only reads up to version ${V8_FORMAT_VERSION}` | ||
| ); | ||
| } | ||
| } | ||
| const values = /** @type {DeserializedType} */ (v8Deserialize(payload)); | ||
| for (let i = 0; i < bufferCount; i++) { | ||
| values[bufferInfo[i * 2]] = state.retainedBuffer( | ||
| state.read(bufferInfo[i * 2 + 1]) | ||
| ); | ||
| } | ||
| return values; | ||
| }; | ||
| /** | ||
| * Deserialize handlers indexed by header byte; built once so each | ||
| * `_deserialize` call doesn't rebuild 256 closures (hot on cache restore). | ||
| * @type {((s: ReadState) => void)[]} | ||
| * Reads the content items of a lazy section. | ||
| * @param {ReadState} state read state | ||
| * @returns {SerializedType} content of the lazy value | ||
| */ | ||
| const dispatchTable = Array.from({ length: 256 }, (_, header) => { | ||
| switch (header) { | ||
| case LAZY_HEADER: | ||
| return (s) => { | ||
| const count = s.readU32(); | ||
| /** @type {number[]} */ | ||
| const lengths = []; | ||
| for (let i = 0; i < count; i++) lengths.push(s.readU32()); | ||
| /** @type {(Buffer | LazyFunction<SerializedType, DeserializedType>)[]} */ | ||
| const content = []; | ||
| for (let l of lengths) { | ||
| if (l === 0) { | ||
| if (typeof s.currentBuffer !== "function") { | ||
| throw new Error("Unexpected non-lazy element in stream"); | ||
| } | ||
| content.push(s.currentBuffer); | ||
| s.nextDataItem(); | ||
| } else { | ||
| do { | ||
| const buf = s.readUpTo(l); | ||
| l -= buf.length; | ||
| content.push(s.retainedBuffer(buf)); | ||
| } while (l > 0); | ||
| } | ||
| } | ||
| s.result.push(s.middleware._createLazyDeserialized(content, s.context)); | ||
| }; | ||
| case BUFFER_HEADER: | ||
| return (s) => { | ||
| const len = s.readU32(); | ||
| s.result.push(s.retainedBuffer(s.read(len))); | ||
| }; | ||
| case TRUE_HEADER: | ||
| return (s) => s.result.push(true); | ||
| case FALSE_HEADER: | ||
| return (s) => s.result.push(false); | ||
| case NULL3_HEADER: | ||
| return (s) => s.result.push(null, null, null); | ||
| case NULL2_HEADER: | ||
| return (s) => s.result.push(null, null); | ||
| case NULL_HEADER: | ||
| return (s) => s.result.push(null); | ||
| case NULL_AND_TRUE_HEADER: | ||
| return (s) => s.result.push(null, true); | ||
| case NULL_AND_FALSE_HEADER: | ||
| return (s) => s.result.push(null, false); | ||
| case NULL_AND_I8_HEADER: | ||
| return (s) => { | ||
| if (s.currentIsBuffer) { | ||
| s.result.push( | ||
| null, | ||
| /** @type {Buffer} */ (s.currentBuffer).readInt8(s.currentPosition) | ||
| ); | ||
| s.currentPosition += I8_SIZE; | ||
| s.checkOverflow(); | ||
| } else { | ||
| s.result.push(null, s.read(I8_SIZE).readInt8(0)); | ||
| } | ||
| }; | ||
| case NULL_AND_I16_HEADER: | ||
| return (s) => { | ||
| s.result.push(null); | ||
| if (s.isInCurrentBuffer(I16_SIZE)) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ (s.currentBuffer).readInt16LE( | ||
| s.currentPosition | ||
| ) | ||
| ); | ||
| s.currentPosition += I16_SIZE; | ||
| s.checkOverflow(); | ||
| } else { | ||
| s.result.push(s.read(I16_SIZE).readInt16LE(0)); | ||
| } | ||
| }; | ||
| case NULL_AND_I32_HEADER: | ||
| return (s) => { | ||
| s.result.push(null); | ||
| if (s.isInCurrentBuffer(I32_SIZE)) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ (s.currentBuffer).readInt32LE( | ||
| s.currentPosition | ||
| ) | ||
| ); | ||
| s.currentPosition += I32_SIZE; | ||
| s.checkOverflow(); | ||
| } else { | ||
| s.result.push(s.read(I32_SIZE).readInt32LE(0)); | ||
| } | ||
| }; | ||
| case NULLS8_HEADER: | ||
| return (s) => { | ||
| const len = s.readU8() + 4; | ||
| for (let i = 0; i < len; i++) { | ||
| s.result.push(null); | ||
| } | ||
| }; | ||
| case NULLS32_HEADER: | ||
| return (s) => { | ||
| const len = s.readU32() + 260; | ||
| for (let i = 0; i < len; i++) { | ||
| s.result.push(null); | ||
| } | ||
| }; | ||
| case BOOLEANS_HEADER: | ||
| return (s) => { | ||
| const innerHeader = s.readU8(); | ||
| if ((innerHeader & 0xf0) === 0) { | ||
| s.readBits(innerHeader, 3); | ||
| } else if ((innerHeader & 0xe0) === 0) { | ||
| s.readBits(innerHeader, 4); | ||
| } else if ((innerHeader & 0xc0) === 0) { | ||
| s.readBits(innerHeader, 5); | ||
| } else if ((innerHeader & 0x80) === 0) { | ||
| s.readBits(innerHeader, 6); | ||
| } else if (innerHeader !== 0xff) { | ||
| let count = (innerHeader & 0x7f) + 7; | ||
| while (count > 8) { | ||
| s.readBits(s.readU8(), 8); | ||
| count -= 8; | ||
| } | ||
| s.readBits(s.readU8(), count); | ||
| } else { | ||
| let count = s.readU32(); | ||
| while (count > 8) { | ||
| s.readBits(s.readU8(), 8); | ||
| count -= 8; | ||
| } | ||
| s.readBits(s.readU8(), count); | ||
| } | ||
| }; | ||
| case STRING_HEADER: | ||
| return (s) => { | ||
| const len = s.readU32(); | ||
| if (s.isInCurrentBuffer(len) && s.currentPosition + len < 0x7fffffff) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ | ||
| (s.currentBuffer).toString( | ||
| undefined, | ||
| s.currentPosition, | ||
| s.currentPosition + len | ||
| ) | ||
| ); | ||
| s.currentPosition += len; | ||
| s.checkOverflow(); | ||
| } else { | ||
| s.result.push(s.read(len).toString()); | ||
| } | ||
| }; | ||
| case SHORT_STRING_HEADER: | ||
| return (s) => s.result.push(""); | ||
| case SHORT_STRING_HEADER | 1: | ||
| return (s) => { | ||
| if (s.currentIsBuffer && s.currentPosition < 0x7ffffffe) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ | ||
| (s.currentBuffer).toString( | ||
| "latin1", | ||
| s.currentPosition, | ||
| s.currentPosition + 1 | ||
| ) | ||
| ); | ||
| s.currentPosition++; | ||
| s.checkOverflow(); | ||
| } else { | ||
| s.result.push(s.read(1).toString("latin1")); | ||
| } | ||
| }; | ||
| case I8_HEADER: | ||
| return (s) => { | ||
| if (s.currentIsBuffer) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ (s.currentBuffer).readInt8(s.currentPosition) | ||
| ); | ||
| s.currentPosition++; | ||
| s.checkOverflow(); | ||
| } else { | ||
| s.result.push(s.read(1).readInt8(0)); | ||
| } | ||
| }; | ||
| case BIGINT_I8_HEADER: { | ||
| const len = 1; | ||
| return (s) => { | ||
| const need = I8_SIZE * len; | ||
| if (s.isInCurrentBuffer(need)) { | ||
| for (let i = 0; i < len; i++) { | ||
| const value = | ||
| /** @type {Buffer} */ | ||
| (s.currentBuffer).readInt8(s.currentPosition); | ||
| s.result.push(BigInt(value)); | ||
| s.currentPosition += I8_SIZE; | ||
| } | ||
| s.checkOverflow(); | ||
| } else { | ||
| const buf = s.read(need); | ||
| for (let i = 0; i < len; i++) { | ||
| const value = buf.readInt8(i * I8_SIZE); | ||
| s.result.push(BigInt(value)); | ||
| } | ||
| } | ||
| }; | ||
| const readLazySection = (state) => { | ||
| const count = state.readU32(); | ||
| /** @type {number[]} */ | ||
| const sizes = []; | ||
| for (let i = 0; i < count; i++) sizes.push(state.readU32()); | ||
| /** @type {SerializedType} */ | ||
| const content = []; | ||
| for (let size of sizes) { | ||
| if (size === 0) { | ||
| if (typeof state.currentBuffer !== "function") { | ||
| throw new Error("Unexpected non-lazy element in stream"); | ||
| } | ||
| content.push(state.currentBuffer); | ||
| state.nextDataItem(); | ||
| } else { | ||
| do { | ||
| const buf = state.readUpTo(size); | ||
| size -= buf.length; | ||
| content.push(state.retainedBuffer(buf)); | ||
| } while (size > 0); | ||
| } | ||
| case BIGINT_I32_HEADER: { | ||
| const len = 1; | ||
| return (s) => { | ||
| const need = I32_SIZE * len; | ||
| if (s.isInCurrentBuffer(need)) { | ||
| for (let i = 0; i < len; i++) { | ||
| const value = /** @type {Buffer} */ (s.currentBuffer).readInt32LE( | ||
| s.currentPosition | ||
| ); | ||
| s.result.push(BigInt(value)); | ||
| s.currentPosition += I32_SIZE; | ||
| } | ||
| s.checkOverflow(); | ||
| } else { | ||
| const buf = s.read(need); | ||
| for (let i = 0; i < len; i++) { | ||
| const value = buf.readInt32LE(i * I32_SIZE); | ||
| s.result.push(BigInt(value)); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| case BIGINT_HEADER: { | ||
| return (s) => { | ||
| const len = s.readU32(); | ||
| if (s.isInCurrentBuffer(len) && s.currentPosition + len < 0x7fffffff) { | ||
| const value = | ||
| /** @type {Buffer} */ | ||
| (s.currentBuffer).toString( | ||
| undefined, | ||
| s.currentPosition, | ||
| s.currentPosition + len | ||
| ); | ||
| s.result.push(BigInt(value)); | ||
| s.currentPosition += len; | ||
| s.checkOverflow(); | ||
| } else { | ||
| const value = s.read(len).toString(); | ||
| s.result.push(BigInt(value)); | ||
| } | ||
| }; | ||
| } | ||
| default: | ||
| if (header <= 10) { | ||
| return (s) => s.result.push(header); | ||
| } else if ((header & SHORT_STRING_HEADER) === SHORT_STRING_HEADER) { | ||
| const len = header & SHORT_STRING_LENGTH_MASK; | ||
| return (s) => { | ||
| if ( | ||
| s.isInCurrentBuffer(len) && | ||
| s.currentPosition + len < 0x7fffffff | ||
| ) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ | ||
| (s.currentBuffer).toString( | ||
| "latin1", | ||
| s.currentPosition, | ||
| s.currentPosition + len | ||
| ) | ||
| ); | ||
| s.currentPosition += len; | ||
| s.checkOverflow(); | ||
| } else { | ||
| s.result.push(s.read(len).toString("latin1")); | ||
| } | ||
| }; | ||
| } else if ((header & NUMBERS_HEADER_MASK) === F64_HEADER) { | ||
| const len = (header & NUMBERS_COUNT_MASK) + 1; | ||
| return (s) => { | ||
| const need = F64_SIZE * len; | ||
| if (s.isInCurrentBuffer(need)) { | ||
| for (let i = 0; i < len; i++) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ (s.currentBuffer).readDoubleLE( | ||
| s.currentPosition | ||
| ) | ||
| ); | ||
| s.currentPosition += F64_SIZE; | ||
| } | ||
| s.checkOverflow(); | ||
| } else { | ||
| const buf = s.read(need); | ||
| for (let i = 0; i < len; i++) { | ||
| s.result.push(buf.readDoubleLE(i * F64_SIZE)); | ||
| } | ||
| } | ||
| }; | ||
| } else if ((header & NUMBERS_HEADER_MASK) === I32_HEADER) { | ||
| const len = (header & NUMBERS_COUNT_MASK) + 1; | ||
| return (s) => { | ||
| const need = I32_SIZE * len; | ||
| if (s.isInCurrentBuffer(need)) { | ||
| for (let i = 0; i < len; i++) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ (s.currentBuffer).readInt32LE( | ||
| s.currentPosition | ||
| ) | ||
| ); | ||
| s.currentPosition += I32_SIZE; | ||
| } | ||
| s.checkOverflow(); | ||
| } else { | ||
| const buf = s.read(need); | ||
| for (let i = 0; i < len; i++) { | ||
| s.result.push(buf.readInt32LE(i * I32_SIZE)); | ||
| } | ||
| } | ||
| }; | ||
| } else if ((header & NUMBERS_HEADER_MASK) === I8_HEADER) { | ||
| const len = (header & NUMBERS_COUNT_MASK) + 1; | ||
| return (s) => { | ||
| const need = I8_SIZE * len; | ||
| if (s.isInCurrentBuffer(need)) { | ||
| for (let i = 0; i < len; i++) { | ||
| s.result.push( | ||
| /** @type {Buffer} */ (s.currentBuffer).readInt8( | ||
| s.currentPosition | ||
| ) | ||
| ); | ||
| s.currentPosition += I8_SIZE; | ||
| } | ||
| s.checkOverflow(); | ||
| } else { | ||
| const buf = s.read(need); | ||
| for (let i = 0; i < len; i++) { | ||
| s.result.push(buf.readInt8(i * I8_SIZE)); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| return (s) => { | ||
| throw new Error(`Unexpected header byte 0x${header.toString(16)}`); | ||
| }; | ||
| } | ||
| }); | ||
| return content; | ||
| }; | ||
@@ -709,493 +310,205 @@ /** | ||
| * @param {Context} context context object | ||
| * @param {{ leftOverBuffer: Buffer | null, allocationSize: number, increaseCounter: number }} allocationScope allocation scope | ||
| * @returns {SerializedType} serialized data | ||
| */ | ||
| _serialize( | ||
| data, | ||
| context, | ||
| allocationScope = { | ||
| allocationSize: 1024, | ||
| increaseCounter: 0, | ||
| leftOverBuffer: null | ||
| } | ||
| ) { | ||
| /** @type {Buffer | null} */ | ||
| let leftOverBuffer = null; | ||
| /** @type {BufferSerializableType[]} */ | ||
| let buffers = []; | ||
| /** @type {Buffer | null} */ | ||
| let currentBuffer = allocationScope ? allocationScope.leftOverBuffer : null; | ||
| allocationScope.leftOverBuffer = null; | ||
| let currentPosition = 0; | ||
| if (currentBuffer === null) { | ||
| currentBuffer = Buffer.allocUnsafe(allocationScope.allocationSize); | ||
| } | ||
| _serialize(data, context) { | ||
| /** @type {SerializedType} */ | ||
| const result = []; | ||
| /** @type {number[]} */ | ||
| const measureStack = []; | ||
| let writtenBytes = 0; | ||
| /** Index of the first value of the open section. */ | ||
| let sectionStart = 0; | ||
| /** Estimated payload size of the open section. */ | ||
| let sectionSize = 0; | ||
| /** | ||
| * Processes the provided bytes needed. | ||
| * @param {number} bytesNeeded bytes needed | ||
| * Positions of the buffers of the open section, relative to `data`. | ||
| * @type {number[]} | ||
| */ | ||
| const allocate = (bytesNeeded) => { | ||
| if (currentBuffer !== null) { | ||
| if (currentBuffer.length - currentPosition >= bytesNeeded) return; | ||
| flush(); | ||
| } | ||
| if (leftOverBuffer && leftOverBuffer.length >= bytesNeeded) { | ||
| currentBuffer = leftOverBuffer; | ||
| leftOverBuffer = null; | ||
| } else { | ||
| currentBuffer = Buffer.allocUnsafe( | ||
| Math.max(bytesNeeded, allocationScope.allocationSize) | ||
| ); | ||
| if ( | ||
| !(allocationScope.increaseCounter = | ||
| (allocationScope.increaseCounter + 1) % 4) && | ||
| allocationScope.allocationSize < 16777216 | ||
| ) { | ||
| allocationScope.allocationSize <<= 1; | ||
| } | ||
| } | ||
| }; | ||
| const flush = () => { | ||
| if (currentBuffer !== null) { | ||
| if (currentPosition > 0) { | ||
| buffers.push( | ||
| Buffer.from( | ||
| currentBuffer.buffer, | ||
| currentBuffer.byteOffset, | ||
| currentPosition | ||
| ) | ||
| ); | ||
| } | ||
| if ( | ||
| !leftOverBuffer || | ||
| leftOverBuffer.length < currentBuffer.length - currentPosition | ||
| ) { | ||
| leftOverBuffer = Buffer.from( | ||
| currentBuffer.buffer, | ||
| currentBuffer.byteOffset + currentPosition, | ||
| currentBuffer.byteLength - currentPosition | ||
| ); | ||
| } | ||
| currentBuffer = null; | ||
| currentPosition = 0; | ||
| } | ||
| }; | ||
| let bufferIndices = []; | ||
| /** @type {Buffer[]} */ | ||
| let buffers = []; | ||
| /** | ||
| * Processes the provided byte. | ||
| * @param {number} byte byte | ||
| * Appends a buffer to the output. | ||
| * @param {Buffer} buffer buffer | ||
| */ | ||
| const writeU8 = (byte) => { | ||
| /** @type {Buffer} */ | ||
| (currentBuffer).writeUInt8(byte, currentPosition++); | ||
| const write = (buffer) => { | ||
| writtenBytes += buffer.length; | ||
| result.push(buffer); | ||
| }; | ||
| /** | ||
| * Processes the provided ui32. | ||
| * @param {number} ui32 ui32 | ||
| * Writes a values section for the given values. | ||
| * @param {DeserializedType} values values without their buffers | ||
| * @param {number[]} indices position of each buffer within `values` | ||
| * @param {Buffer[]} bufferList buffers in the order of `indices` | ||
| */ | ||
| const writeU32 = (ui32) => { | ||
| /** @type {Buffer} */ | ||
| (currentBuffer).writeUInt32LE(ui32, currentPosition); | ||
| currentPosition += 4; | ||
| const writeValuesSection = (values, indices, bufferList) => { | ||
| const payload = v8Serialize(values); | ||
| const header = Buffer.allocUnsafe( | ||
| HEADER_SIZE + I32_SIZE * (2 + indices.length * 2) | ||
| ); | ||
| header[0] = VALUES_HEADER; | ||
| header.writeUInt32LE(payload.length, HEADER_SIZE); | ||
| header.writeUInt32LE(indices.length, HEADER_SIZE + I32_SIZE); | ||
| let offset = HEADER_SIZE + I32_SIZE * 2; | ||
| for (let i = 0; i < indices.length; i++) { | ||
| header.writeUInt32LE(indices[i], offset); | ||
| header.writeUInt32LE(bufferList[i].length, offset + I32_SIZE); | ||
| offset += I32_SIZE * 2; | ||
| } | ||
| write(header); | ||
| write(payload); | ||
| for (const buffer of bufferList) { | ||
| if (buffer.length > 0) write(buffer); | ||
| } | ||
| }; | ||
| /** @type {number[]} */ | ||
| const measureStack = []; | ||
| const measureStart = () => { | ||
| measureStack.push(buffers.length, currentPosition); | ||
| }; | ||
| /** | ||
| * Returns size. | ||
| * @returns {number} size | ||
| * Closes the open values section before the value at `end`. | ||
| * @param {number} end index of the first value not in the section | ||
| */ | ||
| const measureEnd = () => { | ||
| const oldPos = /** @type {number} */ (measureStack.pop()); | ||
| const buffersIndex = /** @type {number} */ (measureStack.pop()); | ||
| let size = currentPosition - oldPos; | ||
| for (let i = buffersIndex; i < buffers.length; i++) { | ||
| size += buffers[i].length; | ||
| } | ||
| return size; | ||
| }; | ||
| for (let i = 0; i < data.length; i++) { | ||
| const thing = data[i]; | ||
| switch (typeof thing) { | ||
| case "function": { | ||
| if (!SerializerMiddleware.isLazy(thing)) { | ||
| throw new Error(`Unexpected function ${thing}`); | ||
| } | ||
| /** @type {SerializedType | LazyFunction<SerializedType, DeserializedType> | undefined} */ | ||
| let serializedData = | ||
| SerializerMiddleware.getLazySerializedValue(thing); | ||
| if (serializedData === undefined) { | ||
| if (SerializerMiddleware.isLazy(thing, this)) { | ||
| flush(); | ||
| allocationScope.leftOverBuffer = leftOverBuffer; | ||
| const result = | ||
| /** @type {PrimitiveSerializableType[]} */ | ||
| (thing()); | ||
| const data = this._serialize(result, context, allocationScope); | ||
| leftOverBuffer = allocationScope.leftOverBuffer; | ||
| allocationScope.leftOverBuffer = null; | ||
| SerializerMiddleware.setLazySerializedValue(thing, data); | ||
| serializedData = data; | ||
| } else { | ||
| serializedData = this._serializeLazy(thing, context); | ||
| flush(); | ||
| buffers.push(serializedData); | ||
| break; | ||
| const flush = (end) => { | ||
| if (end > sectionStart) { | ||
| if ( | ||
| sectionStart === 0 && | ||
| end === data.length && | ||
| // a frozen input can't take the placeholders, so it needs the copy | ||
| (bufferIndices.length === 0 || !Object.isFrozen(data)) | ||
| ) { | ||
| // the section covers all values: swap the buffers out and back in | ||
| // instead of copying the whole array to place the placeholders | ||
| try { | ||
| for (let i = 0; i < bufferIndices.length; i++) { | ||
| data[bufferIndices[i]] = null; | ||
| } | ||
| } else if (typeof serializedData === "function") { | ||
| flush(); | ||
| buffers.push(serializedData); | ||
| break; | ||
| } | ||
| /** @type {number[]} */ | ||
| const lengths = []; | ||
| for (const item of serializedData) { | ||
| /** @type {undefined | number} */ | ||
| let last; | ||
| if (typeof item === "function") { | ||
| lengths.push(0); | ||
| } else if (item.length === 0) { | ||
| // ignore | ||
| } else if ( | ||
| lengths.length > 0 && | ||
| (last = lengths[lengths.length - 1]) !== 0 | ||
| ) { | ||
| const remaining = 0xffffffff - last; | ||
| if (remaining >= item.length) { | ||
| lengths[lengths.length - 1] += item.length; | ||
| } else { | ||
| lengths.push(item.length - remaining); | ||
| lengths[lengths.length - 2] = 0xffffffff; | ||
| } | ||
| } else { | ||
| lengths.push(item.length); | ||
| writeValuesSection(data, bufferIndices, buffers); | ||
| } finally { | ||
| for (let i = 0; i < bufferIndices.length; i++) { | ||
| data[bufferIndices[i]] = buffers[i]; | ||
| } | ||
| } | ||
| allocate(5 + lengths.length * 4); | ||
| writeU8(LAZY_HEADER); | ||
| writeU32(lengths.length); | ||
| for (const l of lengths) { | ||
| writeU32(l); | ||
| } else { | ||
| const values = data.slice(sectionStart, end); | ||
| for (let i = 0; i < bufferIndices.length; i++) { | ||
| const index = bufferIndices[i] - sectionStart; | ||
| bufferIndices[i] = index; | ||
| values[index] = null; | ||
| } | ||
| flush(); | ||
| for (const item of serializedData) { | ||
| buffers.push(item); | ||
| } | ||
| break; | ||
| writeValuesSection(values, bufferIndices, buffers); | ||
| } | ||
| case "string": { | ||
| const len = Buffer.byteLength(thing); | ||
| if (len >= 128 || len !== thing.length) { | ||
| allocate(len + HEADER_SIZE + I32_SIZE); | ||
| writeU8(STRING_HEADER); | ||
| writeU32(len); | ||
| currentBuffer.write(thing, currentPosition); | ||
| currentPosition += len; | ||
| } else if (len >= 70) { | ||
| allocate(len + HEADER_SIZE); | ||
| writeU8(SHORT_STRING_HEADER | len); | ||
| currentBuffer.write(thing, currentPosition, "latin1"); | ||
| currentPosition += len; | ||
| } else { | ||
| allocate(len + HEADER_SIZE); | ||
| writeU8(SHORT_STRING_HEADER | len); | ||
| for (let i = 0; i < len; i++) { | ||
| currentBuffer[currentPosition++] = thing.charCodeAt(i); | ||
| } | ||
| } | ||
| break; | ||
| if (bufferIndices.length > 0) { | ||
| bufferIndices = []; | ||
| buffers = []; | ||
| } | ||
| case "bigint": { | ||
| const type = identifyBigInt(thing); | ||
| if (type === 0 && thing >= 0 && thing <= BigInt(10)) { | ||
| // shortcut for very small bigints | ||
| allocate(HEADER_SIZE + I8_SIZE); | ||
| writeU8(BIGINT_I8_HEADER); | ||
| writeU8(Number(thing)); | ||
| break; | ||
| } | ||
| sectionStart = end; | ||
| sectionSize = 0; | ||
| }; | ||
| for (let i = 0; i < data.length; i++) { | ||
| const thing = data[i]; | ||
| const type = typeof thing; | ||
| if (type === "string") { | ||
| sectionSize += /** @type {string} */ (thing).length; | ||
| } else if (type === "object") { | ||
| // buffers are appended to the section instead of entering the payload | ||
| if (thing !== null) { | ||
| if (!Buffer.isBuffer(thing)) { | ||
| throw new Error(`Unexpected object ${thing} in binary middleware`); | ||
| } | ||
| switch (type) { | ||
| case 0: { | ||
| let n = 1; | ||
| allocate(HEADER_SIZE + I8_SIZE * n); | ||
| writeU8(BIGINT_I8_HEADER | (n - 1)); | ||
| while (n > 0) { | ||
| currentBuffer.writeInt8( | ||
| Number(/** @type {bigint} */ (data[i])), | ||
| currentPosition | ||
| ); | ||
| currentPosition += I8_SIZE; | ||
| n--; | ||
| i++; | ||
| } | ||
| i--; | ||
| break; | ||
| } | ||
| case 1: { | ||
| let n = 1; | ||
| allocate(HEADER_SIZE + I32_SIZE * n); | ||
| writeU8(BIGINT_I32_HEADER | (n - 1)); | ||
| while (n > 0) { | ||
| currentBuffer.writeInt32LE( | ||
| Number(/** @type {bigint} */ (data[i])), | ||
| currentPosition | ||
| ); | ||
| currentPosition += I32_SIZE; | ||
| n--; | ||
| i++; | ||
| } | ||
| i--; | ||
| break; | ||
| } | ||
| default: { | ||
| const value = thing.toString(); | ||
| const len = Buffer.byteLength(value); | ||
| allocate(len + HEADER_SIZE + I32_SIZE); | ||
| writeU8(BIGINT_HEADER); | ||
| writeU32(len); | ||
| currentBuffer.write(value, currentPosition); | ||
| currentPosition += len; | ||
| break; | ||
| } | ||
| } | ||
| break; | ||
| bufferIndices.push(i); | ||
| buffers.push(thing); | ||
| // count placeholder + bytes so buffer-heavy runs still split | ||
| sectionSize += ESTIMATED_VALUE_SIZE + thing.length; | ||
| } else { | ||
| sectionSize += ESTIMATED_VALUE_SIZE; | ||
| } | ||
| case "number": { | ||
| const type = identifyNumber(thing); | ||
| if (type === 0 && thing >= 0 && thing <= 10) { | ||
| // shortcut for very small numbers | ||
| allocate(I8_SIZE); | ||
| writeU8(thing); | ||
| break; | ||
| } | ||
| /** | ||
| * amount of numbers to write | ||
| * @type {number} | ||
| */ | ||
| let n = 1; | ||
| for (; n < 32 && i + n < data.length; n++) { | ||
| const item = data[i + n]; | ||
| if (typeof item !== "number") break; | ||
| if (identifyNumber(item) !== type) break; | ||
| } | ||
| switch (type) { | ||
| case 0: | ||
| allocate(HEADER_SIZE + I8_SIZE * n); | ||
| writeU8(I8_HEADER | (n - 1)); | ||
| while (n > 0) { | ||
| currentBuffer.writeInt8( | ||
| /** @type {number} */ (data[i]), | ||
| currentPosition | ||
| ); | ||
| currentPosition += I8_SIZE; | ||
| n--; | ||
| i++; | ||
| } | ||
| break; | ||
| case 1: | ||
| allocate(HEADER_SIZE + I32_SIZE * n); | ||
| writeU8(I32_HEADER | (n - 1)); | ||
| while (n > 0) { | ||
| currentBuffer.writeInt32LE( | ||
| /** @type {number} */ (data[i]), | ||
| currentPosition | ||
| ); | ||
| currentPosition += I32_SIZE; | ||
| n--; | ||
| i++; | ||
| } | ||
| break; | ||
| case 2: | ||
| allocate(HEADER_SIZE + F64_SIZE * n); | ||
| writeU8(F64_HEADER | (n - 1)); | ||
| while (n > 0) { | ||
| currentBuffer.writeDoubleLE( | ||
| /** @type {number} */ (data[i]), | ||
| currentPosition | ||
| ); | ||
| currentPosition += F64_SIZE; | ||
| n--; | ||
| i++; | ||
| } | ||
| break; | ||
| } | ||
| i--; | ||
| break; | ||
| } else if (type === "function") { | ||
| flush(i); | ||
| sectionStart = i + 1; | ||
| if (!SerializerMiddleware.isLazy(thing)) { | ||
| throw new Error(`Unexpected function ${thing}`); | ||
| } | ||
| case "boolean": { | ||
| let lastByte = thing === true ? 1 : 0; | ||
| /** @type {number[]} */ | ||
| const bytes = []; | ||
| let count = 1; | ||
| /** @type {undefined | number} */ | ||
| let n; | ||
| for (n = 1; n < 0xffffffff && i + n < data.length; n++) { | ||
| const item = data[i + n]; | ||
| if (typeof item !== "boolean") break; | ||
| const pos = count & 0x7; | ||
| if (pos === 0) { | ||
| bytes.push(lastByte); | ||
| lastByte = item === true ? 1 : 0; | ||
| } else if (item === true) { | ||
| lastByte |= 1 << pos; | ||
| } | ||
| count++; | ||
| } | ||
| i += count - 1; | ||
| if (count === 1) { | ||
| allocate(HEADER_SIZE); | ||
| writeU8(lastByte === 1 ? TRUE_HEADER : FALSE_HEADER); | ||
| } else if (count === 2) { | ||
| allocate(HEADER_SIZE * 2); | ||
| writeU8(lastByte & 1 ? TRUE_HEADER : FALSE_HEADER); | ||
| writeU8(lastByte & 2 ? TRUE_HEADER : FALSE_HEADER); | ||
| } else if (count <= 6) { | ||
| allocate(HEADER_SIZE + I8_SIZE); | ||
| writeU8(BOOLEANS_HEADER); | ||
| writeU8((1 << count) | lastByte); | ||
| } else if (count <= 133) { | ||
| allocate(HEADER_SIZE + I8_SIZE + I8_SIZE * bytes.length + I8_SIZE); | ||
| writeU8(BOOLEANS_HEADER); | ||
| writeU8(0x80 | (count - 7)); | ||
| for (const byte of bytes) writeU8(byte); | ||
| writeU8(lastByte); | ||
| /** @type {SerializedType | LazyFunction<SerializedType, DeserializedType> | undefined} */ | ||
| let serializedData = SerializerMiddleware.getLazySerializedValue(thing); | ||
| if (serializedData === undefined) { | ||
| if (SerializerMiddleware.isLazy(thing, this)) { | ||
| serializedData = this._serialize( | ||
| /** @type {DeserializedType} */ (thing()), | ||
| context | ||
| ); | ||
| SerializerMiddleware.setLazySerializedValue(thing, serializedData); | ||
| } else { | ||
| allocate( | ||
| HEADER_SIZE + | ||
| I8_SIZE + | ||
| I32_SIZE + | ||
| I8_SIZE * bytes.length + | ||
| I8_SIZE | ||
| result.push( | ||
| this._serializeLazy( | ||
| /** @type {LazyFunction<DeserializedType, SerializedType>} */ | ||
| (thing), | ||
| context | ||
| ) | ||
| ); | ||
| writeU8(BOOLEANS_HEADER); | ||
| writeU8(0xff); | ||
| writeU32(count); | ||
| for (const byte of bytes) writeU8(byte); | ||
| writeU8(lastByte); | ||
| continue; | ||
| } | ||
| break; | ||
| } else if (typeof serializedData === "function") { | ||
| result.push(serializedData); | ||
| continue; | ||
| } | ||
| case "object": { | ||
| if (thing === null) { | ||
| /** @type {number} */ | ||
| let n; | ||
| for (n = 1; n < 0x100000104 && i + n < data.length; n++) { | ||
| const item = data[i + n]; | ||
| if (item !== null) break; | ||
| } | ||
| i += n - 1; | ||
| if (n === 1) { | ||
| if (i + 1 < data.length) { | ||
| const next = data[i + 1]; | ||
| if (next === true) { | ||
| allocate(HEADER_SIZE); | ||
| writeU8(NULL_AND_TRUE_HEADER); | ||
| i++; | ||
| } else if (next === false) { | ||
| allocate(HEADER_SIZE); | ||
| writeU8(NULL_AND_FALSE_HEADER); | ||
| i++; | ||
| } else if (typeof next === "number") { | ||
| const type = identifyNumber(next); | ||
| if (type === 0) { | ||
| allocate(HEADER_SIZE + I8_SIZE); | ||
| writeU8(NULL_AND_I8_HEADER); | ||
| currentBuffer.writeInt8(next, currentPosition); | ||
| currentPosition += I8_SIZE; | ||
| i++; | ||
| } else if (type === 1) { | ||
| // `null` + i32 is 5 bytes; if the value also fits an | ||
| // i16, fuse it as 3 bytes instead (helps back-references) | ||
| if (next >= -32768 && next <= 32767) { | ||
| allocate(HEADER_SIZE + I16_SIZE); | ||
| writeU8(NULL_AND_I16_HEADER); | ||
| currentBuffer.writeInt16LE(next, currentPosition); | ||
| currentPosition += I16_SIZE; | ||
| } else { | ||
| allocate(HEADER_SIZE + I32_SIZE); | ||
| writeU8(NULL_AND_I32_HEADER); | ||
| currentBuffer.writeInt32LE(next, currentPosition); | ||
| currentPosition += I32_SIZE; | ||
| } | ||
| i++; | ||
| } else { | ||
| allocate(HEADER_SIZE); | ||
| writeU8(NULL_HEADER); | ||
| } | ||
| } else { | ||
| allocate(HEADER_SIZE); | ||
| writeU8(NULL_HEADER); | ||
| } | ||
| } else { | ||
| allocate(HEADER_SIZE); | ||
| writeU8(NULL_HEADER); | ||
| } | ||
| } else if (n === 2) { | ||
| allocate(HEADER_SIZE); | ||
| writeU8(NULL2_HEADER); | ||
| } else if (n === 3) { | ||
| allocate(HEADER_SIZE); | ||
| writeU8(NULL3_HEADER); | ||
| } else if (n < 260) { | ||
| allocate(HEADER_SIZE + I8_SIZE); | ||
| writeU8(NULLS8_HEADER); | ||
| writeU8(n - 4); | ||
| /** @type {number[]} */ | ||
| const sizes = []; | ||
| for (const item of serializedData) { | ||
| /** @type {undefined | number} */ | ||
| let last; | ||
| if (typeof item === "function") { | ||
| sizes.push(0); | ||
| } else if (item.length === 0) { | ||
| // ignore | ||
| } else if ( | ||
| sizes.length > 0 && | ||
| (last = sizes[sizes.length - 1]) !== 0 | ||
| ) { | ||
| const remaining = 0xffffffff - last; | ||
| if (remaining >= item.length) { | ||
| sizes[sizes.length - 1] += item.length; | ||
| } else { | ||
| allocate(HEADER_SIZE + I32_SIZE); | ||
| writeU8(NULLS32_HEADER); | ||
| writeU32(n - 260); | ||
| sizes.push(item.length - remaining); | ||
| sizes[sizes.length - 2] = 0xffffffff; | ||
| } | ||
| } else if (Buffer.isBuffer(thing)) { | ||
| if (thing.length < 8192) { | ||
| allocate(HEADER_SIZE + I32_SIZE + thing.length); | ||
| writeU8(BUFFER_HEADER); | ||
| writeU32(thing.length); | ||
| thing.copy(currentBuffer, currentPosition); | ||
| currentPosition += thing.length; | ||
| } else { | ||
| allocate(HEADER_SIZE + I32_SIZE); | ||
| writeU8(BUFFER_HEADER); | ||
| writeU32(thing.length); | ||
| flush(); | ||
| buffers.push(thing); | ||
| } | ||
| } else { | ||
| sizes.push(item.length); | ||
| } | ||
| break; | ||
| } | ||
| case "symbol": { | ||
| if (thing === MEASURE_START_OPERATION) { | ||
| measureStart(); | ||
| } else if (thing === MEASURE_END_OPERATION) { | ||
| const size = measureEnd(); | ||
| allocate(HEADER_SIZE + I32_SIZE); | ||
| writeU8(I32_HEADER); | ||
| currentBuffer.writeInt32LE(size, currentPosition); | ||
| currentPosition += I32_SIZE; | ||
| const header = Buffer.allocUnsafe( | ||
| HEADER_SIZE + I32_SIZE * (1 + sizes.length) | ||
| ); | ||
| header[0] = LAZY_HEADER; | ||
| header.writeUInt32LE(sizes.length, HEADER_SIZE); | ||
| for (let j = 0; j < sizes.length; j++) { | ||
| header.writeUInt32LE(sizes[j], HEADER_SIZE + I32_SIZE * (1 + j)); | ||
| } | ||
| write(header); | ||
| for (const item of serializedData) { | ||
| if (typeof item === "function") { | ||
| result.push(item); | ||
| } else { | ||
| write(item); | ||
| } | ||
| break; | ||
| } | ||
| default: { | ||
| throw new Error( | ||
| `Unknown typeof "${typeof thing}" in binary middleware` | ||
| ); | ||
| continue; | ||
| } else if (type === "symbol") { | ||
| flush(i); | ||
| sectionStart = i + 1; | ||
| const operation = | ||
| /** @type {MEASURE_START_OPERATION_TYPE | MEASURE_END_OPERATION_TYPE} */ | ||
| (/** @type {unknown} */ (thing)); | ||
| if (operation === MEASURE_START_OPERATION) { | ||
| measureStack.push(writtenBytes); | ||
| } else if (operation === MEASURE_END_OPERATION) { | ||
| const size = | ||
| writtenBytes - /** @type {number} */ (measureStack.pop()); | ||
| writeValuesSection([size], [], []); | ||
| } | ||
| continue; | ||
| } else { | ||
| sectionSize += ESTIMATED_VALUE_SIZE; | ||
| } | ||
| if (sectionSize >= MAX_SECTION_SIZE) flush(i + 1); | ||
| } | ||
| flush(); | ||
| allocationScope.leftOverBuffer = leftOverBuffer; | ||
| // avoid leaking memory | ||
| currentBuffer = null; | ||
| leftOverBuffer = null; | ||
| allocationScope = /** @type {EXPECTED_ANY} */ (undefined); | ||
| const _buffers = buffers; | ||
| buffers = /** @type {EXPECTED_ANY} */ (undefined); | ||
| return _buffers; | ||
| flush(data.length); | ||
| return result; | ||
| } | ||
@@ -1214,3 +527,3 @@ | ||
| /** | ||
| * Create lazy deserialized. Internal, but called from the dispatch table. | ||
| * Create lazy deserialized. | ||
| * @param {SerializedType} content content | ||
@@ -1249,12 +562,31 @@ * @param {Context} context context object | ||
| _deserialize(data, context) { | ||
| const state = new ReadState(data, context, this); | ||
| const state = new ReadState(data, context); | ||
| /** @type {DeserializedType | undefined} */ | ||
| let result; | ||
| while (state.currentBuffer !== null) { | ||
| /** @type {DeserializedType} */ | ||
| let part; | ||
| if (typeof state.currentBuffer === "function") { | ||
| state.result.push(this._deserializeLazy(state.currentBuffer, context)); | ||
| part = [this._deserializeLazy(state.currentBuffer, context)]; | ||
| state.nextDataItem(); | ||
| } else { | ||
| dispatchTable[state.readU8()](state); | ||
| const header = state.readU8(); | ||
| if (header === VALUES_HEADER) { | ||
| part = readValuesSection(state); | ||
| } else if (header === LAZY_HEADER) { | ||
| part = [ | ||
| this._createLazyDeserialized(readLazySection(state), context) | ||
| ]; | ||
| } else { | ||
| throw new Error(`Unexpected header byte 0x${header.toString(16)}`); | ||
| } | ||
| } | ||
| // a single-section stream needs no copying: reuse its array as the result | ||
| if (result === undefined) { | ||
| result = part; | ||
| } else { | ||
| for (let j = 0; j < part.length; j++) result.push(part[j]); | ||
| } | ||
| } | ||
| return state.result; | ||
| return result === undefined ? [] : result; | ||
| } | ||
@@ -1261,0 +593,0 @@ } |
@@ -1698,170 +1698,28 @@ /* | ||
| for (const key of /** @type {(keyof CompilationSimplePrinters)[]} */ ( | ||
| Object.keys(COMPILATION_SIMPLE_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {NonNullable<CompilationSimplePrinters[keyof CompilationSimplePrinters]>} */ | ||
| (COMPILATION_SIMPLE_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"compilation">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| // Every `*_SIMPLE_PRINTERS` map registers identically: tap | ||
| // `print.for(key)` with a formatter called as `(obj, ctx, stats)`. | ||
| // One loop over all maps replaces a dozen near-identical blocks. | ||
| for (const simplePrinters of /** @type {Record<string, EXPECTED_ANY>[]} */ ([ | ||
| COMPILATION_SIMPLE_PRINTERS, | ||
| ASSET_SIMPLE_PRINTERS, | ||
| MODULE_SIMPLE_PRINTERS, | ||
| MODULE_ISSUER_PRINTERS, | ||
| MODULE_REASON_PRINTERS, | ||
| MODULE_PROFILE_PRINTERS, | ||
| CHUNK_GROUP_PRINTERS, | ||
| CHUNK_PRINTERS, | ||
| ERROR_PRINTERS, | ||
| LOG_ENTRY_PRINTERS, | ||
| MODULE_TRACE_DEPENDENCY_PRINTERS, | ||
| MODULE_TRACE_ITEM_PRINTERS | ||
| ])) { | ||
| for (const key of Object.keys(simplePrinters)) { | ||
| stats.hooks.print | ||
| .for(key) | ||
| .tap(PLUGIN_NAME, (obj, ctx) => | ||
| simplePrinters[key](obj, ctx, stats) | ||
| ); | ||
| } | ||
| } | ||
| for (const key of /** @type {(keyof AssetSimplePrinters)[]} */ ( | ||
| Object.keys(ASSET_SIMPLE_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {NonNullable<AssetSimplePrinters[keyof AssetSimplePrinters]>} */ | ||
| (ASSET_SIMPLE_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"asset" | "asset.info">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ModuleSimplePrinters)[]} */ ( | ||
| Object.keys(MODULE_SIMPLE_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {SimplePrinter<EXPECTED_ANY, "module">} */ | ||
| (MODULE_SIMPLE_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"module">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ModuleIssuerPrinters)[]} */ ( | ||
| Object.keys(MODULE_ISSUER_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {NonNullable<ModuleIssuerPrinters[keyof ModuleIssuerPrinters]>} */ | ||
| (MODULE_ISSUER_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"moduleIssuer">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ModuleReasonsPrinters)[]} */ ( | ||
| Object.keys(MODULE_REASON_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {SimplePrinter<EXPECTED_ANY, "moduleReason">} */ | ||
| (MODULE_REASON_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"moduleReason">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ModuleProfilePrinters)[]} */ ( | ||
| Object.keys(MODULE_PROFILE_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {NonNullable<ModuleProfilePrinters[keyof ModuleProfilePrinters]>} */ | ||
| (MODULE_PROFILE_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"profile">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ChunkGroupPrinters)[]} */ ( | ||
| Object.keys(CHUNK_GROUP_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {SimplePrinter<EXPECTED_ANY, "chunkGroupKind" | "chunkGroup">} */ | ||
| (CHUNK_GROUP_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"chunkGroupKind" | "chunkGroup">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ChunkPrinters)[]} */ ( | ||
| Object.keys(CHUNK_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {SimplePrinter<EXPECTED_ANY, "chunk">} */ | ||
| (CHUNK_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"chunk">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ErrorPrinters)[]} */ ( | ||
| Object.keys(ERROR_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {SimplePrinter<EXPECTED_ANY, "error">} */ | ||
| (ERROR_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"error">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof LogEntryPrinters)[]} */ ( | ||
| Object.keys(LOG_ENTRY_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {SimplePrinter<EXPECTED_ANY, "logging">} */ | ||
| (LOG_ENTRY_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"logging">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ModuleTraceDependencyPrinters)[]} */ ( | ||
| Object.keys(MODULE_TRACE_DEPENDENCY_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {NonNullable<ModuleTraceDependencyPrinters[keyof ModuleTraceDependencyPrinters]>} */ | ||
| (MODULE_TRACE_DEPENDENCY_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"moduleTraceDependency">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of /** @type {(keyof ModuleTraceItemPrinters)[]} */ ( | ||
| Object.keys(MODULE_TRACE_ITEM_PRINTERS) | ||
| )) { | ||
| stats.hooks.print.for(key).tap(PLUGIN_NAME, (obj, ctx) => | ||
| /** @type {NonNullable<ModuleTraceItemPrinters[keyof ModuleTraceItemPrinters]>} */ | ||
| (MODULE_TRACE_ITEM_PRINTERS[key])( | ||
| obj, | ||
| /** @type {DefineStatsPrinterContext<"moduleTraceItem">} */ | ||
| (ctx), | ||
| stats | ||
| ) | ||
| ); | ||
| } | ||
| for (const key of Object.keys(PREFERRED_ORDERS)) { | ||
@@ -1868,0 +1726,0 @@ const preferredOrder = PREFERRED_ORDERS[key]; |
@@ -9,3 +9,2 @@ /* | ||
| const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable"); | ||
| const { concatComparators, keepOriginalOrder } = require("../util/comparators"); | ||
| const smartGrouping = require("../util/smartGrouping"); | ||
@@ -298,6 +297,3 @@ | ||
| if (comparators.length > 0) { | ||
| items.sort( | ||
| // @ts-expect-error number of arguments is correct | ||
| concatComparators(...comparators, keepOriginalOrder(items)) | ||
| ); | ||
| sortWithOriginalOrder(items, comparators); | ||
| } | ||
@@ -363,6 +359,3 @@ | ||
| if (comparators2.length > 0) { | ||
| resultItems.sort( | ||
| // @ts-expect-error number of arguments is correct | ||
| concatComparators(...comparators2, keepOriginalOrder(resultItems)) | ||
| ); | ||
| sortWithOriginalOrder(resultItems, comparators2); | ||
| } | ||
@@ -430,2 +423,31 @@ | ||
| /** | ||
| * Stable in-place sort applying comparators in order, keeping the original order | ||
| * for equal items. Inlined instead of `concatComparators(...c, keepOriginalOrder())` | ||
| * because that combination is single-use per sort and only thrashes the comparator | ||
| * caches (a fresh tiebreaker closure every call allocates a new cache entry). | ||
| * @param {EXPECTED_ANY[]} items items to sort in place | ||
| * @param {Comparator[]} comparators comparators applied in order | ||
| * @returns {void} | ||
| */ | ||
| const sortWithOriginalOrder = (items, comparators) => { | ||
| // original-index tiebreaker keeps the sort stable on engines without a stable Array.sort | ||
| /** @type {Map<EXPECTED_ANY, number>} */ | ||
| const originalOrder = new Map(); | ||
| for (let i = 0; i < items.length; i++) { | ||
| originalOrder.set(items[i], i); | ||
| } | ||
| const count = comparators.length; | ||
| items.sort((a, b) => { | ||
| for (let i = 0; i < count; i++) { | ||
| const res = comparators[i](a, b); | ||
| if (res !== 0) return res; | ||
| } | ||
| return ( | ||
| /** @type {number} */ (originalOrder.get(a)) - | ||
| /** @type {number} */ (originalOrder.get(b)) | ||
| ); | ||
| }); | ||
| }; | ||
| module.exports = StatsFactory; |
+23
-3
@@ -50,2 +50,18 @@ /* | ||
| /** | ||
| * Decimal digit count of a non-negative integer, without allocating a string. | ||
| * @param {number} n non-negative integer | ||
| * @returns {number} number of decimal digits | ||
| */ | ||
| const numberLength = (n) => { | ||
| if (n < 10) return 1; | ||
| if (n < 100) return 2; | ||
| if (n < 1000) return 3; | ||
| if (n < 10000) return 4; | ||
| if (n < 100000) return 5; | ||
| if (n < 1000000) return 6; | ||
| if (n < 10000000) return 7; | ||
| return String(n).length; | ||
| }; | ||
| /** | ||
| * Defines the render manifest options type used by this module. | ||
@@ -300,7 +316,11 @@ * @typedef {object} RenderManifestOptions | ||
| for (const module of modules) { | ||
| // module id + colon + comma | ||
| objectOverhead += `${module.id}`.length + 2; | ||
| // module id digits + colon + comma; ids are non-negative integers here | ||
| // (non-number ids already returned false above), so count digits | ||
| // arithmetically instead of allocating a string per module. | ||
| const id = /** @type {number} */ (module.id); | ||
| objectOverhead += numberLength(id) + 2; | ||
| } | ||
| // number of commas, or when starting non-zero the length of Array(minId).concat() | ||
| const arrayOverhead = minId === 0 ? maxId : 16 + `${minId}`.length + maxId; | ||
| const arrayOverhead = | ||
| minId === 0 ? maxId : 16 + numberLength(minId) + maxId; | ||
| return arrayOverhead < objectOverhead ? [minId, maxId] : false; | ||
@@ -307,0 +327,0 @@ } |
+2
-2
| { | ||
| "name": "webpack", | ||
| "version": "5.109.0", | ||
| "version": "5.109.1", | ||
| "description": "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.", | ||
@@ -68,3 +68,3 @@ "homepage": "https://github.com/webpack/webpack", | ||
| "test:css-parsing": "yarn test:base --testMatch \"<rootDir>/test/cssParsing-webpack.spectest.js\"", | ||
| "test:deno": "yarn test:base:deno --reporters default --reporters \"<rootDir>/test/runtimeCrashReporter.js\" --testMatch \"<rootDir>/test/*.{basictest,longtest,test,unittest}.js\" --testPathIgnorePatterns \"/node_modules/|test/HotTestCases\"", | ||
| "test:deno": "yarn test:base:deno --reporters default --reporters \"<rootDir>/test/runtimeCrashReporter.js\" --testMatch \"<rootDir>/test/*.{basictest,longtest,test,unittest}.js\" --testPathIgnorePatterns \"/node_modules/\"", | ||
| "test:base:deno": "deno --allow-read --allow-env --allow-sys --allow-ffi --allow-write --allow-run --allow-net --import-map=test/deno-import-map.json --v8-flags='--max-old-space-size=4096' ./node_modules/jest-cli/bin/jest.js --workerIdleMemoryLimit=512MB --logHeapUsage", | ||
@@ -71,0 +71,0 @@ "test:bun": "yarn test:base:bun --reporters default --reporters \"<rootDir>/test/runtimeCrashReporter.js\" --testMatch \"<rootDir>/test/*.{basictest,longtest,test,unittest}.js\" --testPathIgnorePatterns \"/node_modules/\"", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
8086219
0.38%223927
0.25%36
5.88%