Skip to content

Commit 55fd773

Browse files
Copilotmrjf
andauthored
Fix playground: bundle TypeScript locally, add CDN fallback, create serve.ts
- Bundle TypeScript compiler into playground/dist/ during CI build so the playground doesn't depend on external CDN availability - Update playground-runtime.js to load TypeScript from local bundle first, with CDN fallback and timeout handling - Add playground/serve.ts for local development (referenced in package.json but was missing) - Update docs/playground.md with new architecture details - Add playground/serve.ts to biome ignore (dev tool, not library) Agent-Logs-Url: https://github.com/githubnext/tsessebe/sessions/526f7361-16a1-4b0c-8ef4-4c405e36044b Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
1 parent 77a253e commit 55fd773

5 files changed

Lines changed: 138 additions & 20 deletions

File tree

.github/workflows/pages.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ jobs:
3333
- name: Build library for browser
3434
run: bun build ./src/index.ts --outdir ./playground/dist --target browser --minify
3535

36+
- name: Bundle TypeScript compiler for offline playground
37+
run: cp node_modules/typescript/lib/typescript.js ./playground/dist/typescript.js
38+
3639
- name: Setup Pages
3740
uses: actions/configure-pages@v5
3841
with:

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
"files": {
99
"ignoreUnknown": false,
10-
"ignore": ["dist/**", "node_modules/**", "*.d.ts", "playground/**/*.js"]
10+
"ignore": ["dist/**", "node_modules/**", "*.d.ts", "playground/**/*.js", "playground/serve.ts"]
1111
},
1212
"formatter": {
1313
"enabled": true,

docs/playground.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ compiler from CDN and transpiles user code to JavaScript before execution.
2424
Users can write type annotations, interfaces, and generics — the compiler
2525
strips them automatically.
2626

27-
- TypeScript compiler: loaded from `https://cdn.jsdelivr.net/npm/typescript@5/lib/typescript.js`
27+
- TypeScript compiler: bundled locally (`playground/dist/typescript.js`) with
28+
CDN fallback (`https://cdn.jsdelivr.net/npm/typescript@5/lib/typescript.js`).
2829
- No WASM required — the compiler runs natively in JavaScript.
2930

3031
### 3. Live tsb Library Access
@@ -112,6 +113,7 @@ playground/
112113

113114
```bash
114115
bun build ./src/index.ts --outdir ./playground/dist --target browser --minify
116+
cp node_modules/typescript/lib/typescript.js ./playground/dist/typescript.js
115117
```
116118

117119
The CI pipeline (`pages.yml`) runs this automatically during deployment.

playground/playground-runtime.js

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,55 @@
55
*
66
* Architecture:
77
* 1. Loads the tsb browser bundle (built by CI into playground/dist/)
8-
* 2. Loads the TypeScript compiler from CDN for in-browser transpilation
8+
* 2. Loads the TypeScript compiler (local bundle first, CDN fallback)
99
* 3. Converts playground blocks into editable editors with Run/Reset buttons
1010
* 4. Transforms imports, transpiles TS → JS, and executes with output capture
1111
*
1212
* No WASM needed — the TypeScript compiler runs natively in JavaScript.
1313
*/
1414

15-
// ── Load TypeScript compiler from CDN ──────────────────────────────
15+
// ── Load TypeScript compiler (local bundle → CDN fallback) ─────────
1616

17-
function loadTypeScript() {
17+
function loadScriptWithTimeout(src, timeoutMs) {
1818
return new Promise(function (resolve, reject) {
19-
if (window.ts) {
20-
resolve(window.ts);
21-
return;
22-
}
2319
var script = document.createElement("script");
24-
script.src =
25-
"https://cdn.jsdelivr.net/npm/typescript@5/lib/typescript.js";
20+
var timer = setTimeout(function () {
21+
reject(new Error("Timeout loading " + src));
22+
}, timeoutMs);
23+
script.src = src;
2624
script.onload = function () {
27-
resolve(window.ts);
25+
clearTimeout(timer);
26+
resolve();
2827
};
2928
script.onerror = function () {
30-
reject(new Error("Failed to load TypeScript compiler from CDN"));
29+
clearTimeout(timer);
30+
reject(new Error("Failed to load " + src));
3131
};
3232
document.head.appendChild(script);
3333
});
3434
}
3535

36+
function loadTypeScript() {
37+
if (window.ts) return Promise.resolve(window.ts);
38+
39+
// Try local bundle first (built by CI), then fall back to CDN
40+
return loadScriptWithTimeout("./dist/typescript.js", 15000)
41+
.catch(function () {
42+
return loadScriptWithTimeout(
43+
"https://cdn.jsdelivr.net/npm/typescript@5/lib/typescript.js",
44+
30000,
45+
);
46+
})
47+
.then(function () {
48+
if (!window.ts) {
49+
throw new Error(
50+
"TypeScript compiler loaded but window.ts is not available",
51+
);
52+
}
53+
return window.ts;
54+
});
55+
}
56+
3657
// ── Load tsb browser bundle ────────────────────────────────────────
3758

3859
function loadTsb() {
@@ -199,16 +220,20 @@ async function initPlayground() {
199220
var loadingEl = document.getElementById("playground-loading");
200221
var statusEl = document.getElementById("playground-status");
201222

202-
try {
203-
if (statusEl)
204-
statusEl.textContent = "Loading TypeScript compiler & tsb library\u2026";
223+
function setStatus(msg) {
224+
if (statusEl) statusEl.textContent = msg;
225+
}
205226

206-
// Load both dependencies in parallel
207-
var results = await Promise.all([loadTypeScript(), loadTsb()]);
208-
var ts = results[0];
209-
var tsb = results[1];
227+
try {
228+
setStatus("Loading tsb library\u2026");
229+
var tsb = await loadTsb();
210230
window.__tsb = tsb;
211231

232+
setStatus("Loading TypeScript compiler\u2026");
233+
var ts = await loadTypeScript();
234+
235+
setStatus("Initializing editors\u2026");
236+
212237
// Initialize all playground blocks on the page
213238
var blocks = document.querySelectorAll(".playground-block");
214239
blocks.forEach(function (block) {

playground/serve.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Local development server for the tsb playground.
3+
*
4+
* Usage: bun run playground (or: bun run playground/serve.ts)
5+
*
6+
* Builds the tsb browser bundle and TypeScript compiler into playground/dist/,
7+
* then serves the playground on http://localhost:3000.
8+
*/
9+
10+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
11+
import { extname, join } from "node:path";
12+
13+
const PORT = 3000;
14+
const PLAYGROUND_DIR = import.meta.dir;
15+
const PROJECT_ROOT = join(PLAYGROUND_DIR, "..");
16+
const DIST_DIR = join(PLAYGROUND_DIR, "dist");
17+
18+
const MIME_TYPES: Record<string, string> = {
19+
".html": "text/html",
20+
".js": "text/javascript",
21+
".css": "text/css",
22+
".json": "application/json",
23+
".png": "image/png",
24+
".svg": "image/svg+xml",
25+
};
26+
27+
async function buildBundle() {
28+
console.log("Building tsb browser bundle…");
29+
const result = await Bun.build({
30+
entrypoints: [join(PROJECT_ROOT, "src", "index.ts")],
31+
outdir: DIST_DIR,
32+
target: "browser",
33+
minify: true,
34+
});
35+
if (!result.success) {
36+
console.error("Build failed:");
37+
for (const log of result.logs) {
38+
console.error(log);
39+
}
40+
process.exit(1);
41+
}
42+
console.log(" → playground/dist/index.js");
43+
}
44+
45+
function copyTypeScript() {
46+
const src = join(PROJECT_ROOT, "node_modules", "typescript", "lib", "typescript.js");
47+
const dest = join(DIST_DIR, "typescript.js");
48+
if (!existsSync(src)) {
49+
console.warn("⚠ TypeScript compiler not found at", src);
50+
console.warn(" Run `npm install` first. CDN fallback will be used.");
51+
return;
52+
}
53+
copyFileSync(src, dest);
54+
console.log(" → playground/dist/typescript.js");
55+
}
56+
57+
async function main() {
58+
if (!existsSync(DIST_DIR)) {
59+
mkdirSync(DIST_DIR, { recursive: true });
60+
}
61+
62+
await buildBundle();
63+
copyTypeScript();
64+
65+
console.log(`\nPlayground ready at http://localhost:${PORT}\n`);
66+
67+
Bun.serve({
68+
port: PORT,
69+
fetch(req) {
70+
const url = new URL(req.url);
71+
const pathname = url.pathname === "/" ? "/index.html" : url.pathname;
72+
const filePath = join(PLAYGROUND_DIR, pathname);
73+
74+
const file = Bun.file(filePath);
75+
const ext = extname(filePath);
76+
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
77+
78+
return new Response(file, {
79+
headers: { "Content-Type": contentType },
80+
});
81+
},
82+
error() {
83+
return new Response("Not found", { status: 404 });
84+
},
85+
});
86+
}
87+
88+
main();

0 commit comments

Comments
 (0)