|
| 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