Skip to content

Commit 5e9021b

Browse files
docs: typechecking.
1 parent 47c0676 commit 5e9021b

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

docs/type-checking.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Type Checking In The Browser
2+
3+
This document explains how `@knighted/develop` performs TypeScript diagnostics directly in the browser, including the general flow for all render modes and the React-mode-specific type graph loading path.
4+
5+
## Goals
6+
7+
- Provide on-demand TypeScript diagnostics without a local build step.
8+
- Keep render/preview UX responsive while type checks run.
9+
- Support a generic baseline in DOM mode.
10+
- Support a realistic React typing environment in React mode.
11+
- Preserve CDN fallback behavior so diagnostics can still run when one provider fails.
12+
13+
## High-Level Architecture
14+
15+
Browser type checking is implemented by combining three pieces:
16+
17+
1. TypeScript compiler runtime loaded from CDN.
18+
2. Virtual filesystem assembled in memory from source + declaration files.
19+
3. Custom TypeScript host and module resolution bridge that reads from the virtual filesystem.
20+
21+
At runtime this is managed by `createTypeDiagnosticsController` in `src/modules/type-diagnostics.js` and wired from `src/app.js`.
22+
23+
## Generic Typecheck Flow (All Modes)
24+
25+
When the user clicks Typecheck:
26+
27+
1. Ensure TypeScript compiler runtime is loaded from CDN.
28+
2. Build (or reuse) TypeScript standard library declarations (`lib.*.d.ts`).
29+
3. Read current editor source (`component.tsx`).
30+
4. Build a virtual file map for the current run.
31+
5. Create a TypeScript `Program` with a custom host.
32+
6. Collect diagnostics and display formatted output in the diagnostics UI.
33+
34+
### Compiler Loading
35+
36+
- TypeScript runtime is loaded using `importFromCdnWithFallback`.
37+
- The selected provider is remembered for downstream declaration URL generation.
38+
39+
### Standard Library Hydration
40+
41+
- `getTypeScriptLibUrls(...)` provides provider-prioritized URLs for TS lib declarations.
42+
- Triple-slash `reference lib` and `reference path` directives are followed recursively.
43+
- Loaded files are cached in memory so repeated checks do not re-fetch.
44+
45+
### Program Options (Generic)
46+
47+
- `jsx: Preserve`
48+
- `target: ES2022`
49+
- `module: ESNext`
50+
- `moduleResolution: Bundler` (fallback NodeNext/NodeJs)
51+
- `strict: true`
52+
- `noEmit: true`
53+
- `skipLibCheck: true`
54+
- `types: []` to disable implicit ambient type package scanning in this virtual environment
55+
56+
The explicit `types: []` avoids TypeScript attempting implicit `@types/*` discovery that does not map to a real disk `node_modules` in browser.
57+
58+
## DOM Mode Behavior
59+
60+
DOM mode uses a lightweight ambient JSX definition that is injected into the virtual filesystem as a synthetic declaration file.
61+
62+
This keeps the baseline path minimal and avoids loading React type packages when they are not needed.
63+
64+
## React Mode Behavior: Lazy CDN Type Hydration
65+
66+
React mode enables an additional lazy type graph loader:
67+
68+
- Trigger condition: render mode is `react` and Typecheck is run.
69+
- Root packages: `@types/react` and `@types/react-dom`.
70+
- Transitive dependencies are discovered and loaded on demand.
71+
- Everything is cached after first load.
72+
73+
### CDN Type Package URL Strategy
74+
75+
`getTypePackageFileUrls(...)` generates candidate URLs for type package files with a fallback order that favors raw package CDNs before esm-hosted variants.
76+
77+
Current priority for type package files:
78+
79+
1. jsDelivr
80+
2. unpkg
81+
3. active TypeScript provider (if present)
82+
4. esm.sh
83+
84+
This ordering reduces issues from transformed declaration content.
85+
86+
### Declaration Graph Discovery
87+
88+
For each loaded declaration file:
89+
90+
1. Parse references with `ts.preProcessFile` when available.
91+
2. Fallback to a minimal regex parser only if preprocessor is unavailable.
92+
3. Follow imports/references/type directives recursively.
93+
94+
Guardrails:
95+
96+
- Relative declaration references are treated as paths.
97+
- Extensionless references try `.d.ts` candidates first.
98+
- Absolute URL specifiers are ignored.
99+
- Commented example imports are not treated as real dependencies.
100+
101+
### Candidate File Resolution
102+
103+
When a declaration path is ambiguous, candidates are tried in ordered fallback:
104+
105+
1. `<path>.d.ts`
106+
2. script-extension-normalized `.d.ts`
107+
3. `<path>/index.d.ts`
108+
4. raw `<path>`
109+
110+
This reduces noisy failed requests and improves compatibility with DefinitelyTyped layouts.
111+
112+
## Virtual Filesystem Design
113+
114+
The virtual filesystem is a `Map<string, string>` where keys are normalized virtual paths.
115+
116+
Typical entries include:
117+
118+
- `component.tsx`
119+
- `lib.esnext.full.d.ts` and referenced TS lib files
120+
- `knighted-jsx-runtime.d.ts` (DOM mode only)
121+
- `node_modules/@types/react/...`
122+
- `node_modules/@types/react-dom/...`
123+
- transitive type deps like `node_modules/csstype/...`
124+
125+
The loader maintains:
126+
127+
- loaded file content cache
128+
- package manifest cache
129+
- package entrypoint cache
130+
- in-flight promise dedupe for concurrent requests
131+
132+
## TypeScript Host + Resolver Bridge
133+
134+
A custom host is supplied to TypeScript `createProgram(...)` and reads from the virtual map:
135+
136+
- `fileExists`
137+
- `readFile`
138+
- `directoryExists`
139+
- `getDirectories`
140+
- `getSourceFile`
141+
- `resolveModuleNames`
142+
143+
Resolver strategy:
144+
145+
1. Ask TypeScript `resolveModuleName(...)` first.
146+
2. If unresolved and React type graph is active, resolve via virtual `node_modules` candidates.
147+
148+
This allows TypeScript diagnostics to behave like a project-backed environment while operating purely in browser memory.
149+
150+
## Diagnostics UI Integration
151+
152+
- Typecheck state is surfaced via loading/neutral/ok/error states.
153+
- Results are formatted with line/column when available.
154+
- Existing render status is preserved and adjusted when type errors are present.
155+
- Re-check scheduling is supported when unresolved type errors already exist.
156+
157+
## Known Constraints
158+
159+
- This is intentionally diagnostics-only (`noEmit`).
160+
- Type package compatibility still depends on CDN availability.
161+
- Browser security and CDN headers may surface noisy network failures on provider fallback paths.
162+
- Complex package resolution edge cases may still require targeted guardrails.
163+
164+
## Why This Approach
165+
166+
Compared to a server-side typecheck service, this approach keeps feedback local to the browser session and aligns with the CDN-first architecture of `@knighted/develop`.
167+
168+
Compared to a purely regex-driven declaration walker, TypeScript preprocessor parsing gives a more robust dependency graph with fewer false positives.
169+
170+
## Validation And Regression Coverage
171+
172+
Recent changes are protected with Playwright coverage that checks:
173+
174+
- React-mode Typecheck succeeds.
175+
- Expected `@types/react` loading occurs.
176+
- Malformed type fetch URL patterns do not occur.
177+
178+
Recommended local validation when changing this system:
179+
180+
```bash
181+
npm run lint
182+
npm run build:esm
183+
npm run test:e2e -- --grep "react mode typecheck"
184+
```
185+
186+
## Future Improvements
187+
188+
- Add explicit lazy-loading assertions (no `@types/*` requests before first React-mode Typecheck).
189+
- Expand diagnostics UI with jump-to-line navigation and richer context.
190+
- Consider optional user-configurable extra type roots after baseline stability is proven.

0 commit comments

Comments
 (0)