perf(lint): load the TypeScript compiler lazily in validateReactPageProps#2544
Merged
Conversation
…rops @objectstack/lint sits on the kernel boot path, but the react-props gate (ADR-0081 Phase 2, #2482) only runs when a kind:'react' page is actually validated. The top-level `import ts from 'typescript'` made every boot parse the ~9 MB compiler (~70 ms+ warm, worse on container cold starts) and hard-crashed boot when a deployment pruned the package from the image (cloud's Docker pruner did; worked around in cloud#728). The compiler now loads on the first validated react-source page via a deferred createRequire (the bundling-safe pattern from driver-sqlite-wasm's knex-wasm-dialect); the public API stays synchronous and unchanged, and `typescript` stays a regular dependency. If the package is missing at call time, validation fails with an actionable error instead of killing boot. Guarded by lazy-typescript.test.ts at three levels (structural no-eager- import scan over src, child-process probes of both dist formats, in-process lazy-load behavior) — verified to go red when the eager import is reintroduced. An in-worker require.cache probe alone cannot catch it: vitest inlines static imports through its transform. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 1 changed package(s). ✅ |
os-zhuang
added a commit
that referenced
this pull request
Jul 3, 2026
@objectstack/lint sits on the kernel boot path, but the react syntax
gate (ADR-0081) only runs when a kind:'react' page is actually
validated. The top-level `import { transform } from 'sucrase'` made
every boot parse ~1.5 MB of transpiler (~16 ms cold require) — the same
boot-path problem as the TypeScript compiler in validateReactPageProps,
fixed in #2544.
Sucrase now loads on the first validated react-source page via the same
deferred createRequire (anchor chain import.meta.url → __filename →
cwd); the public API stays synchronous and unchanged, `sucrase` stays a
regular dependency, and a missing package at call time fails validation
with an actionable error instead of killing boot.
The guard test is generalized from lazy-typescript.test.ts to
lazy-deps.test.ts, covering both deps at all three levels (structural
no-eager-import scan over src, child-process probes of both dist
formats, in-process lazy-load behavior) — verified to go red for each
dep separately when its eager import is reintroduced. An in-worker
require.cache probe alone cannot catch it: vitest inlines static
imports through its transform.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
68 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
@objectstack/lintis loaded on the kernel boot path, but the react-source prop gate (validate-react-page-props.ts, ADR-0081 Phase 2, #2482) only runs when akind:'react'page is actually validated — a rare, trusted-tier case. Its top-levelimport ts from 'typescript'meant:typescriptand container boot died (worked around cloud-side in cloud#728 by keeping the package).What
createRequire— the same bundling-safe pattern as driver-sqlite-wasm'sknex-wasm-dialect(staticnode:moduleimport survives esbuild/tsup; thecreateRequire(...)call is deferred becauseimport.meta.urlis an empty stub in the CJS build, falling back to__filename).(stack) => ReactPropFinding[]— the package-wide contract); no caller changes, no api-surface impact.typescriptstays a regular dependency inpackages/lint/package.json.tryso a missing compiler can't be swallowed as "unparseable source".packages/lint/tsconfig.jsongains"types": ["node"](needed fornode:module/__filenamein the dts build; same as driver-sqlite-wasm).Verification
packages/lint: 116/116 tests green ×3 runs; eslint clean;turbo build --filter=...@objectstack/lint(lint + all dependents incl. CLI) green.lazy-typescript.test.tsguards the contract at three levels, because vitest inlines static imports through its transform so an in-workerrequire.cacheprobe alone cannot catch a reintroduced eager import (found out the hard way — the first cut of the test stayed green under a red-check):import ... from 'typescript'(onlyimport type);nodeprocesses import both dist formats and assert the compiler is absent until a react page is validated (auto-skips if dist isn't built);Red-check: reintroducing the eager import turns exactly these three tests red.
typescriptonly): boot import OK → non-react stacks validate fine → react-page validation fails with the clear message. Measured on ESM dist: importing@objectstack/lint85 ms withtypescriptnot loaded; first react-page validation pays a one-time +66 ms.typescriptimport.🤖 Generated with Claude Code