Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ 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/

.idea/

Expand Down
30 changes: 30 additions & 0 deletions packages/ts7-playground/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@typescript/ts7-playground",
"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-typescript": "node scripts/vendor-typescript.mjs"
},
"dependencies": {
"@bjorn3/browser_wasi_shim": "^0.4.2",
"@fontsource/nunito-sans": "^5.2.6",
"@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",
"lz-string": "^1.5.0",
"monaco-editor": "0.56.0",
"monaco-editor-core": "0.56.0",
"vscode-json-languageservice": "^5.3.11",
"vscode-languageserver-textdocument": "^1.0.11"
},
"devDependencies": {
"esbuild": "^0.27.3",
"typescript": "*"
}
}
98 changes: 98 additions & 0 deletions packages/ts7-playground/scripts/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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/7")
const serve = process.argv.includes("--serve")
const playgroundBase = serve ? "/" : process.env.PLAYGROUND_BASE ?? "/play/7/"

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 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)
.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(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,
"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 7 playground: http://${server.host}:${server.port}`)
} else {
await buildContext.rebuild()
await buildContext.dispose()
await rm(websiteStaticDirectory, { force: true, recursive: true })
await cp(outputDirectory, websiteStaticDirectory, { recursive: true })
console.log(`Built TypeScript ${version} playground in ${outputDirectory}`)
}
66 changes: 66 additions & 0 deletions packages/ts7-playground/scripts/smoke-test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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, {
compilerOptions: 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")
97 changes: 97 additions & 0 deletions packages/ts7-playground/scripts/vendor-typescript.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"
import { dirname, resolve } from "node:path"
import { fileURLToPath } from "node:url"
import { spawnSync } from "node:child_process"

const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), "..")
const websiteDirectory = resolve(packageDirectory, "../..")
const typescriptDirectory = resolve(
process.env.TYPESCRIPT_REPO || resolve(websiteDirectory, "../TypeScript"),
)
const vendorDirectory = resolve(packageDirectory, "vendor")
const typescriptPackage = resolve(typescriptDirectory, "packages/typescript")
const wasmPackage = resolve(typescriptDirectory, "packages/typescript-wasip1-wasm")
const libDirectory = resolve(typescriptDirectory, "built/local")
const packageNames = ["typescript", "typescript-wasip1-wasm"]
const legalFiles = ["LICENSE.txt", "NOTICE.txt"]

await Promise.all([
rm(resolve(vendorDirectory, "lib"), { force: true, recursive: true }),
rm(resolve(vendorDirectory, "typescript"), { force: true, recursive: true }),
rm(resolve(vendorDirectory, "typescript-wasip1-wasm"), { force: true, recursive: true }),
rm(resolve(vendorDirectory, "version.txt"), { force: true }),
])
await Promise.all(
packageNames.map(packageName => mkdir(resolve(vendorDirectory, packageName), { recursive: true })),
)
await Promise.all([
cp(resolve(typescriptPackage, "dist"), resolve(vendorDirectory, "typescript/dist"), {
recursive: true,
}),
cp(resolve(wasmPackage, "dist"), resolve(vendorDirectory, "typescript-wasip1-wasm/dist"), {
recursive: true,
}),
writeVendorManifest(typescriptPackage, "typescript"),
writeVendorManifest(wasmPackage, "typescript-wasip1-wasm"),
...packageNames.flatMap(packageName =>
legalFiles.map(fileName =>
cp(
resolve(typescriptDirectory, fileName),
resolve(vendorDirectory, packageName, fileName),
)
)
),
])

const vendorLibDirectory = resolve(vendorDirectory, "lib")
await mkdir(vendorLibDirectory, { recursive: true })
const libFileNames = (await readdir(libDirectory))
.filter(fileName => /^lib(?:\..+)?\.d\.ts$/.test(fileName))
await Promise.all(
libFileNames.map(fileName =>
cp(resolve(libDirectory, fileName), resolve(vendorLibDirectory, fileName))
),
)

const versionResult = spawnSync(
resolve(typescriptDirectory, "built/local/tsc"),
["--version"],
{ encoding: "utf8" },
)
if (versionResult.status !== 0) {
throw new Error(versionResult.stderr || "Unable to read the TypeScript version")
}
const version = versionResult.stdout.trim().replace(/^Version\s+/, "")
await writeFile(resolve(vendorDirectory, "version.txt"), `${version}\n`)

console.log(`Vendored TypeScript ${version} from ${typescriptDirectory}`)

async function writeVendorManifest(sourceDirectory, vendorName) {
const manifest = JSON.parse(await readFile(resolve(sourceDirectory, "package.json"), "utf8"))
const exports = { ...manifest.exports }
const imports = { ...manifest.imports }
if (vendorName === "typescript") {
delete exports["."]
delete imports["#getExePath"]
delete imports["#vscode-jsonrpc/node"]
imports["#asyncClient"] = "./dist/api/async/browserClient.js"
imports["#syncClient"] = "./dist/api/sync/browserClient.js"
imports["#enums/*"] = {
types: "./dist/enums/*.enum.d.ts",
default: "./dist/enums/*.js",
}
}
const vendoredManifest = {
name: manifest.name,
version: manifest.version,
license: manifest.license,
type: manifest.type,
files: ["dist", ...legalFiles],
exports,
imports,
}
await writeFile(
resolve(vendorDirectory, vendorName, "package.json"),
`${JSON.stringify(vendoredManifest, undefined, 2)}\n`,
)
}
Loading
Loading