Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
6ac0e97
Add standalone TypeScript 7.1 playground
jakebailey Sep 16, 2026
21f6e30
Integrate TS 7.1 playground into website
jakebailey Sep 16, 2026
5b0bda0
Vendor TypeScript 7 compiler for playground
jakebailey Sep 16, 2026
1360e9a
Add vendored TypeScript compiler artifacts
jakebailey Sep 16, 2026
e263e75
Remove internal playground README
jakebailey Sep 16, 2026
b105882
Add TypeScript 7 Monaco LSP support
jakebailey Sep 16, 2026
0bca0ef
Fix TypeScript 7 playground asset URLs
jakebailey Sep 16, 2026
fed0140
Fix playground COI redirect loop
jakebailey Sep 16, 2026
53eca8e
Turn TS7 playground into a project workspace
jakebailey Sep 16, 2026
d67f86b
Improve TypeScript 7 project UX
jakebailey Sep 16, 2026
cdda448
Fix TSConfig validation and saved emit
jakebailey Sep 16, 2026
eb6943b
Add truthful playground loading progress
jakebailey Sep 16, 2026
8639a6a
Cache TypeScript playground assets
jakebailey Sep 16, 2026
65665fd
Enable TS7 semantic editor features
jakebailey Sep 16, 2026
02ee465
Show playground while TypeScript loads
jakebailey Sep 16, 2026
6cfd09a
Clarify playground file tree nesting
jakebailey Sep 16, 2026
0be5f54
Support type queries in JavaScript files
jakebailey Sep 22, 2026
a38d170
Refresh vendored TypeScript WASI compiler
jakebailey Sep 24, 2026
3b18493
Add unified classic TypeScript versions
jakebailey Sep 24, 2026
cdd91f6
Make TS7 playground mobile responsive
jakebailey Sep 24, 2026
dc974e5
Add playground editor navigation history
jakebailey Sep 24, 2026
2c0f368
Add project file deletion
jakebailey Sep 24, 2026
38d49bf
Restore classic Monaco editor features
jakebailey Sep 24, 2026
2155152
Add project-wide Problems panel
jakebailey Sep 24, 2026
151543a
Add automatic package type acquisition
jakebailey Sep 24, 2026
cf26cf6
Integrate playground examples and help
jakebailey Sep 24, 2026
0eb90e5
Vendor the package type acquisition runtime
jakebailey Sep 24, 2026
6bb80a4
Wrap playground controls on tablets
jakebailey Sep 24, 2026
659b863
Add playground workspace layout controls
jakebailey Sep 24, 2026
a7334c0
Simplify playground compiler versions
jakebailey Sep 24, 2026
0ab90f8
Wrap playground code on mobile
jakebailey Sep 24, 2026
6d026df
Contain Monaco on mobile viewports
jakebailey Sep 24, 2026
b98a7e8
Keep Problems above emitted output
jakebailey Sep 24, 2026
85d9fc0
Expand the editor when panels are hidden
jakebailey Sep 24, 2026
653e0f0
Use disclosure controls for output sections
jakebailey Sep 24, 2026
53699e5
Confirm the initial compiler download
jakebailey Sep 24, 2026
16e24e7
Replace the new-file browser prompt
jakebailey Sep 24, 2026
0299dd3
Show the exact compiler version
jakebailey Sep 24, 2026
0a6be8c
Center the compiler download consent
jakebailey Sep 24, 2026
bd61e5f
Version multi-file playground URLs
jakebailey Sep 24, 2026
f5b0cc0
Add inline compiler option overrides
jakebailey Sep 24, 2026
49b8dae
Route legacy examples and handbook links
jakebailey Sep 24, 2026
fb54626
Load acquired package types into the live LSP
jakebailey Sep 24, 2026
5e0e760
Add persisted playground settings
jakebailey Sep 24, 2026
fcfa20f
Replace native playground dialogs
jakebailey Sep 24, 2026
a9e3f53
Replace the native compiler selector
jakebailey Sep 24, 2026
b3daccd
Add a compact playground site shell
jakebailey Sep 24, 2026
a92ba11
Persist editor selections in project links
jakebailey Sep 24, 2026
7de6b82
Restore reflect-metadata run compatibility
jakebailey Sep 24, 2026
c8a5bab
Rename the next-generation playground
jakebailey Sep 25, 2026
e263476
Move Playground v2 to its permanent route
jakebailey Sep 25, 2026
32ca7e0
Improve Playground v2 accessibility
jakebailey Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ packages/documentation/output/attribution.json
packages/sandbox/src/releases.json
packages/sandbox/src/release_data.ts
packages/typescriptlang-org/src/lib/documentationNavigation.ts
packages/typescriptlang-org/static/play/7/
packages/typescriptlang-org/static/play/v2/

.idea/

Expand Down
34 changes: 34 additions & 0 deletions packages/playground-v2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@typescript/playground-v2",
"private": true,
"type": "module",
"scripts": {
"build": "node scripts/build.mjs",
"dev": "node scripts/build.mjs --serve",
"test": "node scripts/smoke-test.mjs",
"typecheck": "tsc --noEmit",
"vendor-resources": "node scripts/vendor-resources.mjs",
"vendor-typescript": "node scripts/vendor-typescript.mjs"
},
"dependencies": {
"@bjorn3/browser_wasi_shim": "^0.4.2",
"@fontsource/nunito-sans": "^5.2.6",
"@typescript/ata": "file:vendor/ata",
"@typescript/typescript": "file:vendor/typescript",
"@typescript/typescript-wasip1-wasm": "file:vendor/typescript-wasip1-wasm",
"@vscode/monaco-lsp-client": "file:vendor/vscode-monaco-lsp-client-0.1.0.tgz",
"coi-serviceworker": "^0.1.7",
"hack-font": "^3.3.0",
"jsonc-parser": "^3.3.1",
"lz-string": "^1.5.0",
"monaco-editor": "0.56.0",
"monaco-editor-core": "0.56.0",
"reflect-metadata": "^0.2.2",
"vscode-json-languageservice": "^5.3.11",
"vscode-languageserver-textdocument": "^1.0.11"
},
"devDependencies": {
"esbuild": "^0.27.3",
"typescript": "*"
}
}
122 changes: 122 additions & 0 deletions packages/playground-v2/scripts/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { context } from "esbuild"
import { createHash } from "node:crypto"
import { cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"
import { basename, dirname, resolve } from "node:path"
import { fileURLToPath } from "node:url"

const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), "..")
const websiteDirectory = resolve(packageDirectory, "../..")
const vendorDirectory = resolve(packageDirectory, "vendor")
const outputDirectory = resolve(packageDirectory, "dist")
const websiteStaticDirectory = resolve(websiteDirectory, "packages/typescriptlang-org/static/play/v2")
const legacyWebsiteStaticDirectory = resolve(websiteDirectory, "packages/typescriptlang-org/static/play/7")
const serve = process.argv.includes("--serve")
const playgroundBase = serve ? "/" : process.env.PLAYGROUND_BASE ?? "/play/v2/"

const wasmFile = resolve(vendorDirectory, "typescript-wasip1-wasm/dist/tsc.wasm")
const libDirectory = resolve(vendorDirectory, "lib")
const configSchema = resolve(websiteDirectory, "packages/tsconfig-reference/scripts/schema/result/schema.json")
const releaseIndex = resolve(packageDirectory, "vendor/versions.json")
const examplesIndex = resolve(packageDirectory, "vendor/examples.json")
const helpIndex = resolve(packageDirectory, "vendor/help.json")
const editorWorker = fileURLToPath(import.meta.resolve("monaco-editor/editor/editor.worker"))
const coiServiceWorker = fileURLToPath(import.meta.resolve("coi-serviceworker/coi-serviceworker.min.js"))

const version = (await readFile(resolve(vendorDirectory, "version.txt"), "utf8")).trim()
const indexHtml = (await readFile(resolve(packageDirectory, "src/index.html"), "utf8"))
.replaceAll("__PLAYGROUND_BASE__", playgroundBase)
.replaceAll("__COMPILER_VERSION__", version)
.replace("<title>", `<title data-typescript-version="${version}">`)

await rm(outputDirectory, { force: true, recursive: true })
await mkdir(outputDirectory, { recursive: true })
const libFileNames = (await readdir(libDirectory)).filter(fileName => /^lib(?:\..+)?\.d\.ts$/.test(fileName)).sort()
const libFiles = Object.fromEntries(
await Promise.all(
libFileNames.map(async fileName => [
`/${basename(fileName)}`,
await readFile(resolve(libDirectory, fileName), "utf8"),
])
)
)
const libFilesJSON = JSON.stringify(libFiles)
const [wasmBytes, schemaBytes] = await Promise.all([readFile(wasmFile), readFile(configSchema)])
const assetCacheVersion = createHash("sha256")
.update(wasmBytes)
.update(libFilesJSON)
.update(schemaBytes)
.digest("hex")
.slice(0, 16)
await Promise.all([
writeFile(resolve(outputDirectory, "index.html"), indexHtml),
cp(coiServiceWorker, resolve(outputDirectory, "coi-serviceworker.min.js")),
cp(configSchema, resolve(outputDirectory, "tsconfig.schema.json")),
cp(releaseIndex, resolve(outputDirectory, "versions.json")),
cp(examplesIndex, resolve(outputDirectory, "examples.json")),
cp(helpIndex, resolve(outputDirectory, "help.json")),
cp(wasmFile, resolve(outputDirectory, "tsc.wasm")),
writeFile(resolve(outputDirectory, "lib-files.json"), libFilesJSON),
])

const buildContext = await context({
absWorkingDir: packageDirectory,
bundle: true,
conditions: ["browser", "default"],
define: {
__ASSET_CACHE_VERSION__: JSON.stringify(assetCacheVersion),
__LOAD_ASSET_SIZES__: JSON.stringify({
libraries: Buffer.byteLength(libFilesJSON),
schema: schemaBytes.byteLength,
wasm: wasmBytes.byteLength,
}),
__TS_VERSION__: JSON.stringify(version),
},
entryNames: "[name]",
entryPoints: {
main: resolve(packageDirectory, "src/main.ts"),
"editor.worker": editorWorker,
"strada.worker": resolve(packageDirectory, "src/strada.worker.ts"),
"tsgo-lsp.worker": resolve(packageDirectory, "src/tsgo-lsp.worker.ts"),
},
format: "esm",
loader: {
".ttf": "file",
".woff": "file",
".woff2": "file",
},
outdir: outputDirectory,
platform: "browser",
sourcemap: true,
target: ["es2022"],
})

if (serve) {
await buildContext.watch()
const server = await buildContext.serve({
host: "127.0.0.1",
port: 4173,
servedir: outputDirectory,
})
console.log(`TypeScript Playground v2: http://${server.host}:${server.port}`)
} else {
await buildContext.rebuild()
await buildContext.dispose()
await Promise.all([
rm(websiteStaticDirectory, { force: true, recursive: true }),
rm(legacyWebsiteStaticDirectory, { force: true, recursive: true }),
])
await cp(outputDirectory, websiteStaticDirectory, { recursive: true })
await mkdir(legacyWebsiteStaticDirectory, { recursive: true })
await writeFile(
resolve(legacyWebsiteStaticDirectory, "index.html"),
`<!doctype html>
<meta charset="utf-8">
<title>TypeScript Playground v2</title>
<script>
location.replace("/play/v2/" + location.search + location.hash)
</script>
<noscript><a href="/play/v2/">Continue to TypeScript Playground v2</a></noscript>
`
)
console.log(`Built TypeScript ${version} playground in ${outputDirectory}`)
}
65 changes: 65 additions & 0 deletions packages/playground-v2/scripts/smoke-test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import assert from "node:assert/strict"
import { readFile, readdir } from "node:fs/promises"
import { resolve } from "node:path"
import { API } from "@typescript/typescript/unstable/sync"
import { instantiateWasm, WasmTransport, wasmURL } from "@typescript/typescript-wasip1-wasm"

const packageDirectory = resolve(import.meta.dirname, "..")
const wasm = await readFile(wasmURL)
const module = await WebAssembly.compile(wasm)
const instance = await instantiateWasm(module)
const transport = new WasmTransport({ instance, cwd: "/workspace" })
const api = new API({ transport })

try {
const libDirectory = resolve(packageDirectory, "vendor/lib")
const libFileNames = (await readdir(libDirectory)).filter(fileName => /^lib(?:\..+)?\.d\.ts$/.test(fileName))
for (const fileName of libFileNames) {
transport.setFile(`/${fileName}`, await readFile(resolve(libDirectory, fileName), "utf8"))
}

const files = {
"/workspace/tsconfig.json": JSON.stringify({
compilerOptions: {
declaration: true,
module: "CommonJS",
strict: true,
target: "ES2022",
},
include: ["./src/**/*"],
}),
"/workspace/src/greet.ts": "export const greet = (name: string) => `Hello, ${name}!`;",
"/workspace/src/index.ts": 'import { greet } from "./greet"; console.log(greet("TS7"));',
}
for (const [fileName, source] of Object.entries(files)) {
transport.setFile(fileName, source)
}

const config = api.readConfigFile("/workspace/tsconfig.json")
assert.equal(config.error, undefined)
const parsed = api.parseJsonConfigFileContent(config.config, {
configFileName: "/workspace/tsconfig.json",
})
assert.deepEqual(parsed.fileNames, ["/workspace/src/greet.ts", "/workspace/src/index.ts"])
const program = api.createProgram(parsed.fileNames, parsed.options, {
projectReferences: parsed.projectReferences,
configFileParsingDiagnostics: parsed.errors,
})
try {
assert.equal(program.getSyntacticDiagnostics().length, 0)
assert.equal(program.getSemanticDiagnostics().length, 0)
const emit = program.emitToString()
assert.equal(emit.emitSkipped, false)
assert.deepEqual(
[...emit.outputFiles.keys()],
["/workspace/src/greet.d.ts", "/workspace/src/greet.js", "/workspace/src/index.d.ts", "/workspace/src/index.js"]
)
assert.match(emit.outputFiles.get("/workspace/src/index.js").text, /require\("\.\/greet"\)/)
} finally {
program.dispose()
}
} finally {
api.close()
}

console.log("TypeScript WASM API smoke test passed")
101 changes: 101 additions & 0 deletions packages/playground-v2/scripts/vendor-resources.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { cp, mkdir, readFile, writeFile } from "node:fs/promises"
import { dirname, resolve } from "node:path"
import { fileURLToPath } from "node:url"

const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), "..")
const websiteDirectory = resolve(packageDirectory, "../..")
const examplesDirectory = resolve(websiteDirectory, "packages/typescriptlang-org/static/js/examples")
const examplesIndex = JSON.parse(await readFile(resolve(examplesDirectory, "en.json"), "utf8"))
const helpIndex = {
docs: [
{
title: "Projects and files",
legacyIndexes: [6, 7],
html: "<p>The playground is a virtual project rooted at <code>/workspace</code>. Use <strong>+ File</strong> to add source or configuration files, select files in the project tree, and use <strong>Delete</strong> to remove the active project file.</p><p>The project is saved in the page URL and browser storage as you edit.</p>",
},
{
title: "Compiler versions",
legacyIndexes: [8],
html: "<p>The selector offers TypeScript 7.1, the latest patch release for each previous major.minor line, and custom playground-CDN build IDs.</p><p>Changing compiler versions reloads the page while preserving the project.</p>",
},
{
title: "Compiler settings",
legacyIndexes: [0],
html: "<p>Edit <code>tsconfig.json</code> directly. The editor provides schema completion, hover documentation, and validation for compiler options.</p><p>Emit and diagnostics use the files and options selected by that configuration.</p>",
},
{
title: "Package types",
legacyIndexes: [4],
html: "<p>Imports from npm packages automatically acquire bundled declarations or matching <code>@types</code> packages. Downloaded declarations are cached and available to diagnostics, completion, hover, and go-to-definition.</p><p>Package JavaScript is not downloaded, so <strong>Run</strong> still supports only emitted project files and relative CommonJS imports.</p>",
},
{
title: "Editor navigation",
legacyIndexes: [11],
html: "<p>Use hover, completion, references, rename, formatting, quick fixes, and <kbd>F12</kbd> go-to-definition as in an editor. Back and Forward return between project and declaration files.</p><p>Place <code>// ^?</code> beneath an expression to display its inferred type.</p>",
},
{
title: "Emit and Run",
legacyIndexes: [2, 3],
html: "<p>The Emit panel shows every generated JavaScript and declaration file. <strong>Run</strong> executes the emitted project with an in-browser CommonJS loader and captures console output.</p><p>Set <code>compilerOptions.module</code> to <code>CommonJS</code> when running a project.</p>",
},
{
title: "Examples",
legacyIndexes: [1],
html: "<p>Use the <strong>Examples</strong> button to browse the bundled TypeScript and JavaScript examples. Choosing an example replaces the current project, applies its compiler settings, and selects its requested compiler version.</p><p>Links written as <code>// example:example-id</code> are clickable in the editor.</p>",
},
{
title: "URLs and compatibility",
legacyIndexes: [9, 10],
html: "<p>New projects use the versioned <code>#code/v2/</code> format. The playground also accepts old <code>#code/</code> source links, <code>#src=</code>, compiler-option query parameters, selections, and filename directives.</p><p>Legacy example and handbook hashes are migrated when opened.</p>",
},
{
title: "Settings",
legacyIndexes: [5],
html: "<p>Use <strong>Settings</strong> to control automatic package type acquisition, URL updates while typing, font size, tab size, theme, desktop word wrapping, the minimap, and font ligatures.</p><p>Changing automatic type acquisition or the theme reloads the page so the language-service filesystem and editor colors are initialized consistently.</p>",
},
],
}

const examples = await Promise.all(
examplesIndex.examples.map(async example => {
const fileName = resolve(examplesDirectory, example.lang, ...example.path, example.name)
let code = await readFile(fileName, "utf8")
if (code.startsWith("//// {")) code = code.split(/\r?\n/).slice(1).join("\n").trim()
return { ...example, code }
})
)

const vendorDirectory = resolve(packageDirectory, "vendor")
const ataVendorDirectory = resolve(vendorDirectory, "ata")
await mkdir(vendorDirectory, { recursive: true })
await mkdir(ataVendorDirectory, { recursive: true })
await Promise.all([
writeFile(
resolve(vendorDirectory, "examples.json"),
`${JSON.stringify({
examples,
sections: examplesIndex.sections,
sortedSubSections: examplesIndex.sortedSubSections,
})}\n`
),
writeFile(resolve(vendorDirectory, "help.json"), `${JSON.stringify(helpIndex)}\n`),
cp(resolve(websiteDirectory, "packages/ata/dist/index.js"), resolve(ataVendorDirectory, "index.js")),
cp(resolve(websiteDirectory, "packages/ata/dist/src/index.d.ts"), resolve(ataVendorDirectory, "index.d.ts")),
cp(resolve(websiteDirectory, "LICENSE-CODE"), resolve(ataVendorDirectory, "LICENSE.txt")),
writeFile(
resolve(ataVendorDirectory, "package.json"),
`${JSON.stringify(
{
name: "@typescript/ata",
version: "0.9.8",
type: "module",
main: "./index.js",
types: "./index.d.ts",
},
undefined,
2
)}\n`
),
])

console.log(`Vendored ${examples.length} playground examples and ${helpIndex.docs.length} help topics`)
Loading
Loading