mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
refactor(browser): separate bundle state from AST inspection
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
type WebBundleAnalysis,
|
||||
} from "../domain/webBundleAnalysis.js";
|
||||
import { buildCapturedWebBundleAnalysis } from "../domain/webBundleAnalyzerResult.js";
|
||||
import { isIncludedScript } from "../domain/webBundleAnalyzerInspection.js";
|
||||
import { isIncludedScript } from "../domain/webBundleAnalyzerState.js";
|
||||
import type { WebBundleScriptAnalysis } from "../domain/webBundleScriptAnalysis.js";
|
||||
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { access } from "node:fs/promises";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerInspection.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerState.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisOutputError,
|
||||
|
||||
@@ -31,7 +31,7 @@ import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
||||
import { jsonParts, bufferedJsonParts } from "../domain/jsonSerialization.js";
|
||||
import { javaScriptRuntime } from "../domain/runtimeVersion.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerInspection.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerState.js";
|
||||
import {
|
||||
webBundleScriptAnalysisSchema,
|
||||
type WebBundleScriptAnalysis,
|
||||
|
||||
@@ -18,7 +18,7 @@ vi.mock("node:v8", async (importOriginal) => {
|
||||
|
||||
import { AnalysisResourceConstraintError } from "../domain/analysisErrorCore.js";
|
||||
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerInspection.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerState.js";
|
||||
import { WebBundleAnalysisWorker } from "./WebBundleAnalysisWorker.js";
|
||||
|
||||
it("refuses before spawning or reading source when no host result capacity remains", async () => {
|
||||
|
||||
@@ -47,7 +47,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
|
||||
});
|
||||
|
||||
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerInspection.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerState.js";
|
||||
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
||||
import { cleanupOwnedProcessGroup } from "../process/ProcessOwnership.js";
|
||||
import {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { createInterface } from "node:readline";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerInspection.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerState.js";
|
||||
import {
|
||||
webBundleWorkerMessageSchema,
|
||||
type WebBundleWorkerMessage,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { withArtifactFileHandle } from "../artifacts/ArtifactFileOperation.js";
|
||||
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
||||
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
||||
import { jsonParts } from "../domain/jsonSerialization.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerInspection.js";
|
||||
import type { IncludedScript } from "../domain/webBundleAnalyzerState.js";
|
||||
import {
|
||||
webBundleScriptAnalysisSchema,
|
||||
type WebBundleScriptAnalysis,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "./webBundleAnalyzer.js";
|
||||
import { buildCapturedWebBundleAnalysis } from "./webBundleAnalyzerResult.js";
|
||||
import { webBundleAnalysisSchema } from "./webBundleAnalysis.js";
|
||||
import { isIncludedScript } from "./webBundleAnalyzerInspection.js";
|
||||
import { isIncludedScript } from "./webBundleAnalyzerState.js";
|
||||
import { webBundleScriptAnalysisSchema } from "./webBundleScriptAnalysis.js";
|
||||
import { createWebTextArtifact } from "./webContentArtifact.js";
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { WebPageInspection } from "./browserObservationSchemas.js";
|
||||
import { analyzeScript } from "./webBundleAnalyzerInspection.js";
|
||||
import {
|
||||
analyzeScript,
|
||||
emptyAccumulator,
|
||||
createWebBundleAnalysisAccumulator,
|
||||
isIncludedScript,
|
||||
type IncludedScript,
|
||||
} from "./webBundleAnalyzerInspection.js";
|
||||
} from "./webBundleAnalyzerState.js";
|
||||
import { buildCapturedWebBundleAnalysis } from "./webBundleAnalyzerResult.js";
|
||||
import type { WebBundleAnalysis } from "./webBundleAnalysis.js";
|
||||
import {
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
export const analyzeCapturedWebScript = (
|
||||
script: IncludedScript,
|
||||
): WebBundleScriptAnalysis => {
|
||||
const accumulator = emptyAccumulator();
|
||||
const accumulator = createWebBundleAnalysisAccumulator();
|
||||
analyzeScript(script, accumulator);
|
||||
const failed = accumulator.failedScripts.length > 0;
|
||||
const recovered = accumulator.recoveredScripts.length > 0;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import type {
|
||||
AnalysisAccumulator,
|
||||
IncludedScript,
|
||||
} from "./webBundleAnalyzerState.js";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import { sanitizeEndpointCandidate } from "./browserObservation.js";
|
||||
import { compositeKey } from "./unicodeCodePointOrder.js";
|
||||
import type { WebPageInspection } from "./browserObservationSchemas.js";
|
||||
import type { WebBundleAnalysis } from "./webBundleAnalysis.js";
|
||||
import { traverseJavaScriptAst } from "./javascript/javascriptSemanticTraversal.js";
|
||||
import { indexJavaScriptBindingsWithReceiversSteps } from "./javascript/javascriptSemanticAnalysis.js";
|
||||
@@ -37,26 +40,6 @@ type ChunkEdge = BundleObservations["chunks"]["edges"][number];
|
||||
type Finding = BundleObservations["routes"][number];
|
||||
type WebMcpDeclaration = BundleObservations["webmcp_declarations"][number];
|
||||
type Inference = WebBundleAnalysis["inferences"][number];
|
||||
type BrowserScript = WebPageInspection["scripts"]["items"][number];
|
||||
type IncludedSource = Extract<BrowserScript["source"], { included: true }>;
|
||||
export type IncludedScript = Omit<BrowserScript, "source"> & {
|
||||
readonly source: IncludedSource;
|
||||
};
|
||||
|
||||
export interface AnalysisAccumulator {
|
||||
readonly edges: ChunkEdge[];
|
||||
readonly routes: Finding[];
|
||||
readonly endpoints: Finding[];
|
||||
readonly webMcp: WebMcpDeclaration[];
|
||||
readonly inferences: Inference[];
|
||||
readonly seen: Set<string>;
|
||||
visitedNodes: number;
|
||||
parsedScripts: number;
|
||||
readonly failedScripts: string[];
|
||||
readonly recoveredScripts: string[];
|
||||
readonly widenedScripts: string[];
|
||||
}
|
||||
|
||||
export const analyzeScript = (
|
||||
script: IncludedScript,
|
||||
accumulator: AnalysisAccumulator,
|
||||
@@ -501,24 +484,6 @@ const basis = (script: IncludedScript, detector: string) => ({
|
||||
detector,
|
||||
});
|
||||
|
||||
export const emptyAccumulator = (): AnalysisAccumulator => ({
|
||||
edges: [],
|
||||
routes: [],
|
||||
endpoints: [],
|
||||
webMcp: [],
|
||||
inferences: [],
|
||||
seen: new Set(),
|
||||
visitedNodes: 0,
|
||||
parsedScripts: 0,
|
||||
failedScripts: [],
|
||||
recoveredScripts: [],
|
||||
widenedScripts: [],
|
||||
});
|
||||
|
||||
export const isIncludedScript = (
|
||||
script: BrowserScript,
|
||||
): script is IncludedScript => script.source.included;
|
||||
|
||||
const routeCallNames = [
|
||||
"route",
|
||||
"addRoute",
|
||||
|
||||
@@ -4,10 +4,12 @@ import {
|
||||
webBundleAnalysisSchema,
|
||||
type WebBundleAnalysis,
|
||||
} from "./webBundleAnalysis.js";
|
||||
import type {
|
||||
AnalysisAccumulator,
|
||||
IncludedScript,
|
||||
} from "./webBundleAnalyzerInspection.js";
|
||||
import {
|
||||
createWebBundleAnalysisAccumulator,
|
||||
isIncludedScript,
|
||||
type AnalysisAccumulator,
|
||||
type IncludedScript,
|
||||
} from "./webBundleAnalyzerState.js";
|
||||
import {
|
||||
webBundleScriptAnalysisSchema,
|
||||
type WebBundleScriptAnalysis,
|
||||
@@ -29,9 +31,7 @@ export const buildCapturedWebBundleAnalysis = (
|
||||
);
|
||||
byKey.set(result.script_key, result);
|
||||
}
|
||||
const sourceScripts = inspection.scripts.items.filter(
|
||||
(script): script is IncludedScript => script.source.included,
|
||||
);
|
||||
const sourceScripts = inspection.scripts.items.filter(isIncludedScript);
|
||||
const expectedKeys = new Set(
|
||||
sourceScripts.map(({ script_key }) => script_key),
|
||||
);
|
||||
@@ -145,19 +145,7 @@ const mergeScriptResults = (
|
||||
{ readonly status: "analyzed" }
|
||||
>[],
|
||||
): AnalysisAccumulator => {
|
||||
const accumulator: AnalysisAccumulator = {
|
||||
edges: [],
|
||||
routes: [],
|
||||
endpoints: [],
|
||||
webMcp: [],
|
||||
inferences: [],
|
||||
seen: new Set(),
|
||||
visitedNodes: 0,
|
||||
parsedScripts: 0,
|
||||
failedScripts: [],
|
||||
recoveredScripts: [],
|
||||
widenedScripts: [],
|
||||
};
|
||||
const accumulator = createWebBundleAnalysisAccumulator();
|
||||
for (const result of results) {
|
||||
accumulator.visitedNodes += result.visited_ast_nodes;
|
||||
if (result.parse_status === "failed") {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { WebPageInspection } from "./browserObservationSchemas.js";
|
||||
import type { WebBundleAnalysis } from "./webBundleAnalysis.js";
|
||||
|
||||
type BundleObservations = WebBundleAnalysis["observations"];
|
||||
type BrowserScript = WebPageInspection["scripts"]["items"][number];
|
||||
type IncludedSource = Extract<BrowserScript["source"], { included: true }>;
|
||||
|
||||
/** Captured script whose source bytes are available for inert analysis. */
|
||||
export type IncludedScript = Omit<BrowserScript, "source"> & {
|
||||
readonly source: IncludedSource;
|
||||
};
|
||||
|
||||
/** Mutable facts shared by per-script inspection and bundle result assembly. */
|
||||
export interface AnalysisAccumulator {
|
||||
readonly edges: BundleObservations["chunks"]["edges"];
|
||||
readonly routes: BundleObservations["routes"];
|
||||
readonly endpoints: BundleObservations["endpoints"];
|
||||
readonly webMcp: BundleObservations["webmcp_declarations"];
|
||||
readonly inferences: WebBundleAnalysis["inferences"];
|
||||
readonly seen: Set<string>;
|
||||
visitedNodes: number;
|
||||
parsedScripts: number;
|
||||
readonly failedScripts: string[];
|
||||
readonly recoveredScripts: string[];
|
||||
readonly widenedScripts: string[];
|
||||
}
|
||||
|
||||
export const createWebBundleAnalysisAccumulator = (): AnalysisAccumulator => ({
|
||||
edges: [],
|
||||
routes: [],
|
||||
endpoints: [],
|
||||
webMcp: [],
|
||||
inferences: [],
|
||||
seen: new Set(),
|
||||
visitedNodes: 0,
|
||||
parsedScripts: 0,
|
||||
failedScripts: [],
|
||||
recoveredScripts: [],
|
||||
widenedScripts: [],
|
||||
});
|
||||
|
||||
export const isIncludedScript = (
|
||||
script: BrowserScript,
|
||||
): script is IncludedScript => script.source.included;
|
||||
@@ -0,0 +1,40 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
it("loads the isolated coordinator without loading its worker's JavaScript parser", async () => {
|
||||
const { stdout } = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
`
|
||||
import { registerHooks } from "node:module";
|
||||
const parser = import.meta.resolve("@babel/parser");
|
||||
let parserLoaded = false;
|
||||
const hook = registerHooks({
|
||||
load(url, context, nextLoad) {
|
||||
if (url === parser) parserLoaded = true;
|
||||
return nextLoad(url, context);
|
||||
},
|
||||
});
|
||||
const { IsolatedWebBundleAnalyzer } = await import(process.argv[1]);
|
||||
hook.deregister();
|
||||
process.stdout.write(JSON.stringify({
|
||||
coordinatorLoaded: typeof IsolatedWebBundleAnalyzer === "function",
|
||||
parserLoaded,
|
||||
}));
|
||||
`,
|
||||
new URL(
|
||||
"../../../dist/browser/IsolatedWebBundleAnalyzer.js",
|
||||
import.meta.url,
|
||||
).href,
|
||||
],
|
||||
{ timeout: 10_000 },
|
||||
);
|
||||
expect(JSON.parse(stdout)).toEqual({
|
||||
coordinatorLoaded: true,
|
||||
parserLoaded: false,
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user