123 lines
3.5 KiB
JavaScript
123 lines
3.5 KiB
JavaScript
// src/dependencies.ts
|
|
const EMPTY = [];
|
|
function collectComponent(manifest, root, component) {
|
|
const deps = {
|
|
scripts: {},
|
|
styles: {},
|
|
preload: {},
|
|
prefetch: {}
|
|
};
|
|
const visited = /* @__PURE__ */ new Set();
|
|
const walk = (moduleId) => {
|
|
visited.add(moduleId);
|
|
const partial = component.get(moduleId);
|
|
if (moduleId === root) Object.assign(deps.scripts, partial.scripts);
|
|
Object.assign(deps.styles, partial.styles);
|
|
Object.assign(deps.preload, partial.preload);
|
|
Object.assign(deps.prefetch, partial.prefetch);
|
|
for (const depId of manifest[moduleId].imports || EMPTY) if (component.has(depId) && !visited.has(depId)) walk(depId);
|
|
};
|
|
walk(root);
|
|
return deps;
|
|
}
|
|
/**
|
|
* Tarjan's strongly connected components algorithm. Returns the lowlink of
|
|
* `moduleId`, or `Infinity` once `moduleId` is resolved without ever joining
|
|
* the stack. A module only joins the stack when it has an unresolved import,
|
|
* so acyclic graphs never allocate Tarjan state.
|
|
*/
|
|
function visit(manifest, resolved, moduleId, tarjan) {
|
|
const deps = {
|
|
scripts: {},
|
|
styles: {},
|
|
preload: {},
|
|
prefetch: {}
|
|
};
|
|
const meta = manifest[moduleId];
|
|
if (!meta) {
|
|
resolved[moduleId] = deps;
|
|
return Infinity;
|
|
}
|
|
if (meta.file) {
|
|
if (meta.preload) deps.preload[moduleId] = meta;
|
|
if (meta.isEntry || meta.sideEffects) deps.scripts[moduleId] = meta;
|
|
}
|
|
for (const css of meta.css || EMPTY) {
|
|
const cssResource = manifest[css];
|
|
if (cssResource) {
|
|
deps.styles[css] = deps.prefetch[css] = cssResource;
|
|
if (cssResource.preload) deps.preload[css] = cssResource;
|
|
}
|
|
}
|
|
for (const asset of meta.assets || EMPTY) {
|
|
const assetResource = manifest[asset];
|
|
if (assetResource) {
|
|
deps.prefetch[asset] = assetResource;
|
|
if (assetResource.preload) deps.preload[asset] = assetResource;
|
|
}
|
|
}
|
|
let frame;
|
|
let lowlink = Infinity;
|
|
for (const depId of meta.imports || EMPTY) {
|
|
let depDeps = resolved[depId];
|
|
if (!depDeps) {
|
|
if (!frame) {
|
|
tarjan ||= {
|
|
index: /* @__PURE__ */ new Map(),
|
|
stack: [],
|
|
count: 0
|
|
};
|
|
frame = {
|
|
id: moduleId,
|
|
order: tarjan.count++,
|
|
deps
|
|
};
|
|
lowlink = frame.order;
|
|
tarjan.index.set(moduleId, frame);
|
|
tarjan.stack.push(frame);
|
|
}
|
|
const depFrame = tarjan.index.get(depId);
|
|
const depLowlink = depFrame ? depFrame.order : visit(manifest, resolved, depId, tarjan);
|
|
if (depLowlink < lowlink) lowlink = depLowlink;
|
|
depDeps = resolved[depId];
|
|
if (!depDeps) continue;
|
|
}
|
|
Object.assign(deps.styles, depDeps.styles);
|
|
Object.assign(deps.preload, depDeps.preload);
|
|
Object.assign(deps.prefetch, depDeps.prefetch);
|
|
}
|
|
if (!frame) {
|
|
resolved[moduleId] = deps;
|
|
return Infinity;
|
|
}
|
|
if (lowlink !== frame.order) return lowlink;
|
|
const stack = tarjan.stack;
|
|
if (stack[stack.length - 1] === frame) {
|
|
stack.pop();
|
|
resolved[moduleId] = deps;
|
|
return lowlink;
|
|
}
|
|
const component = /* @__PURE__ */ new Map();
|
|
let member;
|
|
do {
|
|
member = stack.pop();
|
|
component.set(member.id, member.deps);
|
|
} while (member !== frame);
|
|
for (const root of component.keys()) resolved[root] = collectComponent(manifest, root, component);
|
|
return lowlink;
|
|
}
|
|
/**
|
|
* Resolves `id` and every module it statically imports into `resolved`.
|
|
* Styles, preload and prefetch cover every reachable module, so modules in an
|
|
* import cycle all include the whole cycle.
|
|
*/
|
|
function resolveModuleDependencies(manifest, id, resolved) {
|
|
if (!resolved[id]) visit(manifest, resolved, id, void 0);
|
|
return resolved[id];
|
|
}
|
|
Object.defineProperty(exports, "resolveModuleDependencies", {
|
|
enumerable: true,
|
|
get: function() {
|
|
return resolveModuleDependencies;
|
|
}
|
|
});
|