You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(playground): defer loading of large emitters until selected (#11508)
Fixes#11506
## Problem
`typespec.io/playground` hangs forever on iOS. The standalone playground
(`cadlplayground.z22.web.core.windows.net`) works fine.
The difference:
`website/src/components/playground-component/import-map.ts` adds
`@typespec/http-client-python` and `@typespec/http-client-csharp` to the
website's import map, and `browser-host.ts` eagerly `import()`s
**every** library in the map at startup — before the user has picked an
emitter.
`@typespec/http-client-python` boots a full Pyodide (CPython/WASM)
runtime as a module-level side effect, which alone pulls ~23 MB of extra
payload and ~270 MB of resident memory.
Measured with Playwright WebKit under iPhone 15 emulation (`ps` RSS of
the `WebKit.WebContent` process):
| Page | RSS |
| --- | --- |
| typespec.io home | 195 MB |
| standalone playground (works on real iPhone) | 547 MB |
| typespec.io/playground?version=1.13.x (no extra emitters — **works on
real iPhone**) | 463 MB |
| typespec.io/playground (as shipped — **hangs on real iPhone**) | 741
MB |
## Fix
Add a `deferredEmitters` option. Libraries named there are registered as
placeholder entries at startup (so they still show up in the emitter
dropdown) and are only imported when a compilation actually needs them —
i.e. when they're the selected emitter or listed under `emit` in the
tspconfig.
The website passes the two heavy client emitters. Nothing is downloaded
or evaluated for them until you select one.
Caveat, documented on the option: a deferred library can't be referenced
by an `import` statement in TypeSpec source. That's fine for these two —
they're pure emitters with no decorators or `.tsp` surface.
## Relation to #11507#11507 makes the Pyodide boot
lazy inside the Python emitter. That's the right fix at the source, but
the website resolves emitter bundles at **runtime** from blob storage
(`typespec.blob.core.windows.net/pkgs/<name>/latest.json`), uploaded by
a separate ADO stage. So it can't take effect — or be validated in a PR
preview — until the emitter is republished. This PR fixes the site
independently of that, and also cuts startup cost for the C# emitter.
## Tests
- `packages/playground/test/browser-host-deferred.test.ts` — deferral,
exclusion from the virtual `package.json` dependencies, load-once
memoization, retry after a failed load, no-op for non-deferred libs.
- `packages/playground/test/deferred-emitter-compile.test.ts` —
end-to-end against the **real** compiler: compiling with a deferred
emitter fails to resolve it, then succeeds and writes its output after
`loadLibrary()`.
Full playground suite: 55/55 green. `tsc --noEmit` clean for
`packages/playground` and `website/src`.
## Not fixed here
Monaco web workers fail to start on typespec.io on desktop *and* mobile
(`Could not create web worker(s). Falling back to loading web worker
code in main thread`) — `MonacoEnvironment.getWorker` isn't defined.
Pre-existing and unrelated to the hang, but worth a follow-up:
everything currently runs on the main thread.
## Why this is an opt-in list rather than "defer every emitter"
`isEmitter` comes from `$lib.emitter`, which only exists once the bundle
has been evaluated. Neither the blob index (`latest.json` is just
`{version, imports}`) nor `package.json` carries an emitter marker, so
there is no way to populate the emitter dropdown without importing every
candidate. Deferring everything would leave the dropdown empty.
Deferring *all* emitters would also break the ones that ship TypeSpec
surface — `@typespec/openapi3` (`lib/decorators.tsp`),
`@typespec/json-schema`, `@typespec/protobuf` — since an `import
"@typespec/json-schema";` in the editor would fail to resolve before the
emitter is ever selected. `http-client-python` and `http-client-csharp`
are the only two in the map that are pure emitters with no `.tsp`.
The payoff is also concentrated in those two: with pyodide blocked the
page sits at 474 MB vs 463 MB for `?version=1.13.x` (which loads no
additional packages at all), so everything else the website pulls in
eagerly — including the ~7 Azure libraries that `http-client-python`'s
import map drags along — is only ~11 MB combined.
Possible follow-up, not done here: the host's `readFile`/`stat` are
async, so a miss under `node_modules/<deferred-lib>/` could trigger the
load mid-resolution. That would remove the "cannot be referenced by an
`import` statement" restriction and make the list safe to expand without
auditing each library.
Add support for deferring the loading of emitter libraries until they are selected. Configure with the new `deferredEmitters` option to avoid downloading and evaluating large emitters on startup.
0 commit comments