Implement JQHTML function cache ID system and fix bundle compilation Implement underscore prefix for system tables Fix JS syntax linter to support decorators and grant exception to Task system SPA: Update planning docs and wishlists with remaining features SPA: Document Navigation API abandonment and future enhancements Implement SPA browser integration with History API (Phase 1) Convert contacts view page to SPA action Convert clients pages to SPA actions and document conversion procedure SPA: Merge GET parameters and update documentation Implement SPA route URL generation in JavaScript and PHP Implement SPA bootstrap controller architecture Add SPA routing manual page (rsx:man spa) Add SPA routing documentation to CLAUDE.md Phase 4 Complete: Client-side SPA routing implementation Update get_routes() consumers for unified route structure Complete SPA Phase 3: PHP-side route type detection and is_spa flag Restore unified routes structure and Manifest_Query class Refactor route indexing and add SPA infrastructure Phase 3 Complete: SPA route registration in manifest Implement SPA Phase 2: Extract router code and test decorators Rename Jqhtml_Component to Component and complete SPA foundation setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
314 lines
8.8 KiB
JavaScript
314 lines
8.8 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const { CachedSource, ConcatSource, RawSource } = require("webpack-sources");
|
|
const { UsageState } = require("./ExportsInfo");
|
|
const Template = require("./Template");
|
|
const CssModulesPlugin = require("./css/CssModulesPlugin");
|
|
const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
|
|
|
|
/** @typedef {import("webpack-sources").Source} Source */
|
|
/** @typedef {import("./Compiler")} Compiler */
|
|
/** @typedef {import("./ExportsInfo")} ExportsInfo */
|
|
/** @typedef {import("./ExportsInfo").ExportInfo} ExportInfo */
|
|
/** @typedef {import("./Module")} Module */
|
|
/** @typedef {import("./Module").BuildMeta} BuildMeta */
|
|
/** @typedef {import("./ModuleGraph")} ModuleGraph */
|
|
/** @typedef {import("./RequestShortener")} RequestShortener */
|
|
|
|
/**
|
|
* @template T
|
|
* @param {Iterable<T>} iterable iterable
|
|
* @returns {string} joined with comma
|
|
*/
|
|
const joinIterableWithComma = (iterable) => {
|
|
// This is more performant than Array.from().join(", ")
|
|
// as it doesn't create an array
|
|
let str = "";
|
|
let first = true;
|
|
for (const item of iterable) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
str += ", ";
|
|
}
|
|
str += item;
|
|
}
|
|
return str;
|
|
};
|
|
|
|
/**
|
|
* @param {ConcatSource} source output
|
|
* @param {string} indent spacing
|
|
* @param {ExportsInfo} exportsInfo data
|
|
* @param {ModuleGraph} moduleGraph moduleGraph
|
|
* @param {RequestShortener} requestShortener requestShortener
|
|
* @param {Set<ExportInfo>} alreadyPrinted deduplication set
|
|
* @returns {void}
|
|
*/
|
|
const printExportsInfoToSource = (
|
|
source,
|
|
indent,
|
|
exportsInfo,
|
|
moduleGraph,
|
|
requestShortener,
|
|
alreadyPrinted = new Set()
|
|
) => {
|
|
const otherExportsInfo = exportsInfo.otherExportsInfo;
|
|
|
|
let alreadyPrintedExports = 0;
|
|
|
|
// determine exports to print
|
|
const printedExports = [];
|
|
for (const exportInfo of exportsInfo.orderedExports) {
|
|
if (!alreadyPrinted.has(exportInfo)) {
|
|
alreadyPrinted.add(exportInfo);
|
|
printedExports.push(exportInfo);
|
|
} else {
|
|
alreadyPrintedExports++;
|
|
}
|
|
}
|
|
let showOtherExports = false;
|
|
if (!alreadyPrinted.has(otherExportsInfo)) {
|
|
alreadyPrinted.add(otherExportsInfo);
|
|
showOtherExports = true;
|
|
} else {
|
|
alreadyPrintedExports++;
|
|
}
|
|
|
|
// print the exports
|
|
for (const exportInfo of printedExports) {
|
|
const target = exportInfo.getTarget(moduleGraph);
|
|
source.add(
|
|
`${Template.toComment(
|
|
`${indent}export ${JSON.stringify(exportInfo.name).slice(
|
|
1,
|
|
-1
|
|
)} [${exportInfo.getProvidedInfo()}] [${exportInfo.getUsedInfo()}] [${exportInfo.getRenameInfo()}]${
|
|
target
|
|
? ` -> ${target.module.readableIdentifier(requestShortener)}${
|
|
target.export
|
|
? ` .${target.export
|
|
.map((e) => JSON.stringify(e).slice(1, -1))
|
|
.join(".")}`
|
|
: ""
|
|
}`
|
|
: ""
|
|
}`
|
|
)}\n`
|
|
);
|
|
if (exportInfo.exportsInfo) {
|
|
printExportsInfoToSource(
|
|
source,
|
|
`${indent} `,
|
|
exportInfo.exportsInfo,
|
|
moduleGraph,
|
|
requestShortener,
|
|
alreadyPrinted
|
|
);
|
|
}
|
|
}
|
|
|
|
if (alreadyPrintedExports) {
|
|
source.add(
|
|
`${Template.toComment(
|
|
`${indent}... (${alreadyPrintedExports} already listed exports)`
|
|
)}\n`
|
|
);
|
|
}
|
|
|
|
if (showOtherExports) {
|
|
const target = otherExportsInfo.getTarget(moduleGraph);
|
|
if (
|
|
target ||
|
|
otherExportsInfo.provided !== false ||
|
|
otherExportsInfo.getUsed(undefined) !== UsageState.Unused
|
|
) {
|
|
const title =
|
|
printedExports.length > 0 || alreadyPrintedExports > 0
|
|
? "other exports"
|
|
: "exports";
|
|
source.add(
|
|
`${Template.toComment(
|
|
`${indent}${title} [${otherExportsInfo.getProvidedInfo()}] [${otherExportsInfo.getUsedInfo()}]${
|
|
target
|
|
? ` -> ${target.module.readableIdentifier(requestShortener)}`
|
|
: ""
|
|
}`
|
|
)}\n`
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** @type {WeakMap<RequestShortener, WeakMap<Module, { header: RawSource | undefined, full: WeakMap<Source, CachedSource> }>>} */
|
|
const caches = new WeakMap();
|
|
|
|
const PLUGIN_NAME = "ModuleInfoHeaderPlugin";
|
|
|
|
class ModuleInfoHeaderPlugin {
|
|
/**
|
|
* @param {boolean=} verbose add more information like exports, runtime requirements and bailouts
|
|
*/
|
|
constructor(verbose = true) {
|
|
this._verbose = verbose;
|
|
}
|
|
|
|
/**
|
|
* @param {Compiler} compiler the compiler
|
|
* @returns {void}
|
|
*/
|
|
apply(compiler) {
|
|
const { _verbose: verbose } = this;
|
|
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
|
const javascriptHooks =
|
|
JavascriptModulesPlugin.getCompilationHooks(compilation);
|
|
javascriptHooks.renderModulePackage.tap(
|
|
PLUGIN_NAME,
|
|
(
|
|
moduleSource,
|
|
module,
|
|
{ chunk, chunkGraph, moduleGraph, runtimeTemplate }
|
|
) => {
|
|
const { requestShortener } = runtimeTemplate;
|
|
let cacheEntry;
|
|
let cache = caches.get(requestShortener);
|
|
if (cache === undefined) {
|
|
caches.set(requestShortener, (cache = new WeakMap()));
|
|
cache.set(
|
|
module,
|
|
(cacheEntry = { header: undefined, full: new WeakMap() })
|
|
);
|
|
} else {
|
|
cacheEntry = cache.get(module);
|
|
if (cacheEntry === undefined) {
|
|
cache.set(
|
|
module,
|
|
(cacheEntry = { header: undefined, full: new WeakMap() })
|
|
);
|
|
} else if (!verbose) {
|
|
const cachedSource = cacheEntry.full.get(moduleSource);
|
|
if (cachedSource !== undefined) return cachedSource;
|
|
}
|
|
}
|
|
const source = new ConcatSource();
|
|
let header = cacheEntry.header;
|
|
if (header === undefined) {
|
|
header = this.generateHeader(module, requestShortener);
|
|
cacheEntry.header = header;
|
|
}
|
|
source.add(header);
|
|
if (verbose) {
|
|
const exportsType = /** @type {BuildMeta} */ (module.buildMeta)
|
|
.exportsType;
|
|
source.add(
|
|
`${Template.toComment(
|
|
exportsType
|
|
? `${exportsType} exports`
|
|
: "unknown exports (runtime-defined)"
|
|
)}\n`
|
|
);
|
|
if (exportsType) {
|
|
const exportsInfo = moduleGraph.getExportsInfo(module);
|
|
printExportsInfoToSource(
|
|
source,
|
|
"",
|
|
exportsInfo,
|
|
moduleGraph,
|
|
requestShortener
|
|
);
|
|
}
|
|
source.add(
|
|
`${Template.toComment(
|
|
`runtime requirements: ${joinIterableWithComma(
|
|
chunkGraph.getModuleRuntimeRequirements(module, chunk.runtime)
|
|
)}`
|
|
)}\n`
|
|
);
|
|
const optimizationBailout =
|
|
moduleGraph.getOptimizationBailout(module);
|
|
if (optimizationBailout) {
|
|
for (const text of optimizationBailout) {
|
|
const code =
|
|
typeof text === "function" ? text(requestShortener) : text;
|
|
source.add(`${Template.toComment(`${code}`)}\n`);
|
|
}
|
|
}
|
|
source.add(moduleSource);
|
|
return source;
|
|
}
|
|
source.add(moduleSource);
|
|
const cachedSource = new CachedSource(source);
|
|
cacheEntry.full.set(moduleSource, cachedSource);
|
|
return cachedSource;
|
|
}
|
|
);
|
|
javascriptHooks.chunkHash.tap(PLUGIN_NAME, (_chunk, hash) => {
|
|
hash.update(PLUGIN_NAME);
|
|
hash.update("1");
|
|
});
|
|
const cssHooks = CssModulesPlugin.getCompilationHooks(compilation);
|
|
cssHooks.renderModulePackage.tap(
|
|
PLUGIN_NAME,
|
|
(moduleSource, module, { runtimeTemplate }) => {
|
|
const { requestShortener } = runtimeTemplate;
|
|
let cacheEntry;
|
|
let cache = caches.get(requestShortener);
|
|
if (cache === undefined) {
|
|
caches.set(requestShortener, (cache = new WeakMap()));
|
|
cache.set(
|
|
module,
|
|
(cacheEntry = { header: undefined, full: new WeakMap() })
|
|
);
|
|
} else {
|
|
cacheEntry = cache.get(module);
|
|
if (cacheEntry === undefined) {
|
|
cache.set(
|
|
module,
|
|
(cacheEntry = { header: undefined, full: new WeakMap() })
|
|
);
|
|
} else if (!verbose) {
|
|
const cachedSource = cacheEntry.full.get(moduleSource);
|
|
if (cachedSource !== undefined) return cachedSource;
|
|
}
|
|
}
|
|
const source = new ConcatSource();
|
|
let header = cacheEntry.header;
|
|
if (header === undefined) {
|
|
header = this.generateHeader(module, requestShortener);
|
|
cacheEntry.header = header;
|
|
}
|
|
source.add(header);
|
|
source.add(moduleSource);
|
|
const cachedSource = new CachedSource(source);
|
|
cacheEntry.full.set(moduleSource, cachedSource);
|
|
return cachedSource;
|
|
}
|
|
);
|
|
cssHooks.chunkHash.tap(PLUGIN_NAME, (_chunk, hash) => {
|
|
hash.update(PLUGIN_NAME);
|
|
hash.update("1");
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {Module} module the module
|
|
* @param {RequestShortener} requestShortener request shortener
|
|
* @returns {RawSource} the header
|
|
*/
|
|
generateHeader(module, requestShortener) {
|
|
const req = module.readableIdentifier(requestShortener);
|
|
const reqStr = req.replace(/\*\//g, "*_/");
|
|
const reqStrStar = "*".repeat(reqStr.length);
|
|
const headerStr = `/*!****${reqStrStar}****!*\\\n !*** ${reqStr} ***!\n \\****${reqStrStar}****/\n`;
|
|
return new RawSource(headerStr);
|
|
}
|
|
}
|
|
|
|
module.exports = ModuleInfoHeaderPlugin;
|