perf(build): lex compiler bundles once instead of re-parsing every module with acorn - #521
Conversation
🦋 Changeset detectedLatest commit: 72e8dda The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f9e91b3f6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
commit: |
e44e455 to
ef99aa7
Compare
Artifact validation re-parsed every emitted bundle in full with acorn for its side effect alone: the AST was discarded and only the AB6005 invalid syntax branch depended on it. On examples/host-test that parse was ~22 s of a ~40 s build. Modules the framework compiled (manifest kind bundle) now get the ESM lexer as their only syntax pass; copied and generated modules keep the full parse. Imports are read once per process by content digest, so the post-compile self-containment check in rslib.ts and the two validation passes of one build share one lex.
…gins once virtualModulesPluginConstructor was duplicated verbatim in rslib.ts and mcp-apps.ts, differing only in the package it named; one helper in meta.ts takes the engine object, the package name, and the purpose, and each build path still checks its own Rspack copy. enforceInvariants appended the RSC manifest DefinePlugin and the VirtualModulesPlugin in two separate spreads; they are now one append at the same position with the same order. Emitted artifacts are unchanged.
… bytes read A compiler bundle is trusted to the ESM lexer only while its bytes are the bundler's own: a tools hatch runs after Rspack parsed the source and can rewrite the emitted asset, so a build with a hatch selects the full parse for its bundles (bundleSyntaxCheck). The import cache is keyed by the digest of the bytes the validator actually reads, not the earlier inspection's, so a module rewritten between the two is never answered from the cache. Docs name the Node built-in exception of the import walk.
ef99aa7 to
72e8dda
Compare
Summary
Artifact validation dominated
agent-bundle build: onexamples/host-test~28 s of a ~40 s build. The cause wasvalidateJavaScriptModules(src/build/validate-artifact-modules.ts) re-parsing every emitted bundle in full withacornfor its side effect alone — the AST was discarded and only theAB6005"has invalid syntax" branch depended on it — and doing so on every pass: the pre-manifest content pass, the post-manifest strict pass, afterrslib.tshad already lexed the same bytes for its residual-reserved-import check.This PR keeps every check and changes what proves the syntax:
bundle) is the bundler's own output; the ESM lexer that drives the import-graph walk is its only syntax pass (ModuleSyntaxChecklexed). It rejects unterminated strings, templates, comments, and regexps and unbalanced braces. Copied and generated modules the framework did not compile keep the fullacornparse (parsed). Prebuilt payloads stay opaque as before.acornstays inpackage.jsonfor that branch.src/build/module-imports.tsreads imports through one function and remembers the result by<check>:<sha256>(bounded map). The digest is the oneinspectArtifactalready computed, so the post-compile self-containment check inrslib.tsand both validation passes of one build share one lex of a multi-megabyte bundle.toolshatch could have rewritten the emitted assets keeps the full parse for its bundles too.AB6005codes and messages are unchanged.Also folded in (routed from the orchestration lane; behavior-preserving, artifact bytes unchanged):
virtualModulesPluginConstructorwas duplicated verbatim inrslib.tsandmcp-apps.ts— now one helper inmeta.tstaking(rspack, packageName, purpose), each path still checking its own engine object with its own message text; andenforceInvariantsinrslib.tsappended the RSC-manifestDefinePluginand theVirtualModulesPluginin two separate spreads — now one append, same position, same order.Profile
node --cpu-profon a fullagent-bundle buildofexamples/host-test, self time by module:main)acorn/dist/acorn.mjses-module-lexerwasm@rspack/coreSo ~28 s of the 40 s build was module validation, matching the report; after the change the bundler itself is the largest item.
Timings
Wall time, warm cache, one run each (
AGENT_BUNDLE_WORKBENCH_PREBUILT=1):main)host-testbuildhost-testvalidate --artifactaudiobook-curatorbuildThe parse-once/concurrency follow-ups were not needed after this.
Proof of identical results
artifact-validator.test.ts(280 assertions) and the build/mcp/hooks/package-build/api/cli integration pools pass unchanged in outcome. Two tests whose fixture corrupted a compiler bundle withexport const broken = ;— a bare statement error a bundler never emits, which the lexer accepts — now corrupt it with an unterminated template literal, which the lexer rejects; a new test pins the kind-based gating (copied and generated modules parsed in full, bundles lexed).module-imports.test.tspins the import records, the digest cache, and what each check level rejects.Artifact diff for the folded simplification:
examples/host-testandexamples/audiobook-curatorbuilt before and after that commit (on the pre-#518 base) differed only in the MCP entries' then-unfixed.artifact.stage-XXXXXXtoken and those files'sha256in the manifest; every other byte was identical. Rebased over #518,build-reproducibility.test.tspasses on this branch: two builds emit byte-identical artifacts.Locally: unit 3205/3205; integration
build,hooks,mcp,package-build,cli-routes-build,api,plugin-bundle,cli,artifact-validator,prebuilt-payload,target-stages(277/277);pnpm lint,pnpm typecheck,pnpm docs:site:buildgreen.Docs
website/docs/{en,zh}/guide/distribution/validation.mdx("Content-addressed, not path-present"): how emitted modules are walked and how the syntax check follows who produced the module. Changeset:.changeset/validate-artifact-lex-once.md(patch).Review status
Per the maintainer's instruction this PR carries no comments; review threads are answered here.
main61ff1f1dd(over chore(build): official Rsbuild plugin adoption — drop the standalone publint gate, register the framework-owned plugin set #509, refactor(workbench): fail the Agent Document and host discovery atoms with their typed client errors #515) before opening; rebased again onto5b9d3b72cafter fix(build): byte-reproducible artifacts — root the generated-module namespace at the project root #518 and feat(routes): publish routes.d.ts through Effect FileSystem with an ensuringRemoved staging file (FileSystem phase 1, module 3) #520 merged (onerslib.tsconflict, resolved by keeping fix(build): byte-reproducible artifacts — root the generated-module namespace at the project root #518's project-rootedvirtualRegistryModules(cwd, …)with this PR'srslibVirtualModulesPlugin()). Rebased once more onto5a86149d9after test(build): pass the project root to the composed configs in the plugin-registry test #523 (themaintypecheck fix, fix(build): byte-reproducible artifacts — root the generated-module namespace at the project root #518 × chore(build): official Rsbuild plugin adoption — drop the standalone publint gate, register the framework-owned plugin set #509) and feat(cli): route first-party CLI terminal I/O through Effect Terminal/Stdio; spell routed-CLI input errors in CLI terms (#465) #505 merged; the duplicate test-fix commit dropped out.3f9e91b. Later heads (e44e45567review fixes,72e8ddaedrebase) carry no review request under the no-comments rule; merging on green CI per the reviewer-quota fallback.3f9e91bP2 ×3, all fixed ine44e45567:validate-artifact-modules.ts:126): agreed for the hatch case.ValidateArtifactOptions.bundleSyntaxCheck(lexed|parsed) now chooses how akind: 'bundle'module is checked;buildselectsparsedwhenevertools.rspackortools.rsbuildis configured, since a hatch runs after Rspack parsed the source and can rewrite the asset, andlexedotherwise (the bundler's own bytes). Both validation passes of a build receive the same choice. Pinned bybuild.test.ts"parses emitted bundles in full when a tools hatch could have rewritten them" (a rawBannerPluginbannerexport const broken = ;fails the build withAB6005) and theparsedbranch of the kind-gating validator test; documented in both locales.:144): the validator now always reads the module and keys the cache bysha256Hexof those bytes rather than the inspection's digest, so a rewrite between inspection and read is never answered from the cache; hashing costs ~0.3 s onhost-test, whose build still takes 12.4 s.validation.mdx:31): both locales now state that an import may name a Node built-in (node:fs,fs) or resolve as a relative /file:specifier to a regular manifest file inside the artifact.