fix(playground): defer loading of large emitters until selected - #11508
Merged
timotheeguerin merged 4 commits intoJul 31, 2026
Merged
Conversation
The playground eagerly imported every library in its import map on startup. On typespec.io that includes `@typespec/http-client-python` and `@typespec/http-client-csharp`, which are large enough to push iOS Safari past its per-tab memory budget, so the page never finished loading. Emitters listed in the new `deferredEmitters` option are now registered as placeholders and only imported when they are actually used for a compilation.
commit: |
Contributor
|
All changed packages have been documented.
Show changes
|
|
You can try these changes here
|
…ocking
The suite runs with `isolate: false`, so `vi.mock("../src/core.js")` was not
reliably applied when the whole workspace runs in one vitest instance and the
real `importLibrary` was used instead.
Test the injectable seams directly instead: `createBrowserHostInternal` takes
the loaders, and the eager/deferred split is now a pure `splitDeferredLibraries`
helper. No module mocking is involved.
Also fix two portability bugs in the compile test: `join()` was used to build
virtual FS paths, which produces backslashes on Windows, and
`import.meta.resolve` is replaced with `createRequire`.
timotheeguerin
marked this pull request as ready for review
July 31, 2026 19:08
timotheeguerin
requested review from
catalinaperalta,
iscai-msft,
markcowl and
xirzec
as code owners
July 31, 2026 19:08
xirzec
approved these changes
Jul 31, 2026
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.
Fixes #11506
Problem
typespec.io/playgroundhangs forever on iOS. The standalone playground (cadlplayground.z22.web.core.windows.net) works fine.The difference:
website/src/components/playground-component/import-map.tsadds@typespec/http-client-pythonand@typespec/http-client-csharpto the website's import map, andbrowser-host.tseagerlyimport()s every library in the map at startup — before the user has picked an emitter.@typespec/http-client-pythonboots 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 (
psRSS of theWebKit.WebContentprocess):Fix
Add a
deferredEmittersoption. 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 underemitin 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
importstatement in TypeSpec source. That's fine for these two — they're pure emitters with no decorators or.tspsurface.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 virtualpackage.jsondependencies, 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 afterloadLibrary().Full playground suite: 55/55 green.
tsc --noEmitclean forpackages/playgroundandwebsite/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.getWorkerisn'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"
isEmittercomes from$lib.emitter, which only exists once the bundle has been evaluated. Neither the blob index (latest.jsonis just{version, imports}) norpackage.jsoncarries 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 animport "@typespec/json-schema";in the editor would fail to resolve before the emitter is ever selected.http-client-pythonandhttp-client-csharpare 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 thathttp-client-python's import map drags along — is only ~11 MB combined.Possible follow-up, not done here: the host's
readFile/statare async, so a miss undernode_modules/<deferred-lib>/could trigger the load mid-resolution. That would remove the "cannot be referenced by animportstatement" restriction and make the list safe to expand without auditing each library.