From 70e2c86656b85a73746d2994ede2328e9dde562d Mon Sep 17 00:00:00 2001
From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com>
Date: Tue, 6 Oct 2026 09:09:57 +0000
Subject: [PATCH 1/5] feat: add D language support (Wasm)
---
.../language-support/references/languages.md | 1 +
README.md | 2 +-
docs/docs/languages/d-wasm.mdx | 142 ++++++++
docs/src/components/LanguageSliders.tsx | 1 +
docs/src/components/TemplateList.tsx | 1 +
functions/vendors/templates.js | 1 +
scripts/build.js | 1 +
server/php/inc/starter-templates.json | 1 +
src/livecodes/UI/command-menu-actions.ts | 1 +
src/livecodes/assets/templates/d.svg | 8 +
src/livecodes/html/language-info.html | 36 +++
.../locales/en/language-info.lokalise.json | 12 +
.../i18n/locales/en/language-info.ts | 5 +
.../i18n/locales/en/translation.lokalise.json | 4 +
src/livecodes/i18n/locales/en/translation.ts | 1 +
.../d-wasm/__tests__/lang-d-wasm.spec.ts | 28 ++
src/livecodes/languages/d-wasm/index.ts | 1 +
.../languages/d-wasm/lang-d-wasm-script.ts | 306 ++++++++++++++++++
src/livecodes/languages/d-wasm/lang-d-wasm.ts | 27 ++
src/livecodes/languages/languages.ts | 2 +
src/livecodes/models.ts | 1 +
.../templates/starter/d-wasm-starter.ts | 92 ++++++
src/livecodes/templates/starter/index.ts | 2 +
src/livecodes/vendors.ts | 2 +
src/sdk/models.ts | 6 +
storybook/_stories/EmbedOptions/template.ts | 1 +
.../stories/EmbedOptions/template.stories.ts | 1 +
.../stories/EmbedOptions/template.stories.ts | 1 +
.../stories/EmbedOptions/template.stories.ts | 1 +
.../stories/EmbedOptions/template.stories.ts | 1 +
.../stories/EmbedOptions/template.stories.ts | 1 +
.../stories/EmbedOptions/template.stories.ts | 1 +
vendor-licenses.md | 4 +
33 files changed, 694 insertions(+), 1 deletion(-)
create mode 100644 docs/docs/languages/d-wasm.mdx
create mode 100644 src/livecodes/assets/templates/d.svg
create mode 100644 src/livecodes/languages/d-wasm/__tests__/lang-d-wasm.spec.ts
create mode 100644 src/livecodes/languages/d-wasm/index.ts
create mode 100644 src/livecodes/languages/d-wasm/lang-d-wasm-script.ts
create mode 100644 src/livecodes/languages/d-wasm/lang-d-wasm.ts
create mode 100644 src/livecodes/templates/starter/d-wasm-starter.ts
diff --git a/.agents/skills/livecodes/language-support/references/languages.md b/.agents/skills/livecodes/language-support/references/languages.md
index 66a48dcb2f..72ecac26d6 100644
--- a/.agents/skills/livecodes/language-support/references/languages.md
+++ b/.agents/skills/livecodes/language-support/references/languages.md
@@ -141,6 +141,7 @@ Enabled via `processors` config:
| Perl | `perl`, `pl`, `pm` | Perl runtime |
| Pascal | `pascal`, `pas`, `pp`, `pas2js` | pas2js (Pascal -> JS, WASM) |
| Ada (Wasm) | `ada`, `ada-wasm`, `adb`, `ads`, `hac`, `wasm.ada` | HAC (Ada -> WASM) |
+| D (Wasm) | `d-wasm`, `d`, `di`, `dlang`, `dmd` | DMD (D -> WASM) |
| Gleam | `gleam` | Gleam language |
| Elm | `elm` | Elm compiler (WASM) |
| Haskell | `haskell`, `hs`, `lhs` | MicroHs (not GHC) |
diff --git a/README.md b/README.md
index 8cc8a1d294..08af338fb2 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ A [feature-rich](https://livecodes.io/docs/features/), open-source, **client-sid
[](https://www.npmjs.com/package/livecodes)
[](https://www.npmjs.com/package/livecodes)
[](https://www.jsdelivr.com/package/npm/livecodes)
-[](https://livecodes.io/docs/languages/)
+[](https://livecodes.io/docs/languages/)
[](https://livecodes.io/docs/)
[](https://livecodes.io/docs/llms.txt)
[](https://livecodes.io/docs/llms-full.txt)
diff --git a/docs/docs/languages/d-wasm.mdx b/docs/docs/languages/d-wasm.mdx
new file mode 100644
index 0000000000..d84256689b
--- /dev/null
+++ b/docs/docs/languages/d-wasm.mdx
@@ -0,0 +1,142 @@
+# D (Wasm)
+
+D is a general-purpose, statically typed, compiled programming language with C-like syntax, garbage collection, and a rich standard library.
+
+D in LiveCodes is compiled to WebAssembly by [DMD](https://dlang.org/) and run in the browser, with
+no server. It uses the WebAssembly backend of [dkorpel's dmd-explorer](https://dkorpel.github.io/dmd-explorer/),
+packaged as [`@live-codes/dlang-wasm`](https://www.npmjs.com/package/@live-codes/dlang-wasm).
+
+## Usage
+
+Demo:
+
+import LiveCodes from '../../src/components/LiveCodes.tsx';
+
+export const dConfig = {
+ activeEditor: 'script',
+ script: {
+ language: 'd-wasm',
+ content: `import std.stdio;
+
+void main()
+{
+ writeln("Hello, World!");
+}`,
+ },
+ mode: 'simple',
+ editor: 'auto',
+ tools: {
+ status: 'full',
+ },
+};
+
+
+
+### Communication with JavaScript
+
+The program runs in the context of the result page. A few helpers are available in the browser global
+`livecodes.d` object:
+
+- `livecodes.d.input`: the standard input passed to the program.
+- `livecodes.d.loaded`: a promise that resolves when the compiler has downloaded and the program
+ has run. Other helpers should be used after it resolves.
+- `livecodes.d.output`: the program's output (standard output and standard error, in the order they
+ were written).
+- `livecodes.d.error`: the compiler diagnostics, if the code failed to compile.
+- `livecodes.d.exitCode`: the exit code of the program.
+- `livecodes.d.run`: compiles and runs the code again with new input, returning a promise that
+ resolves with `output`, `error` and `exitCode`.
+
+The same object is also available as `livecodes.dlang`.
+
+Example:
+
+
+
+```d
+import std.stdio;
+
+void main()
+{
+ writeln("Hello, World!");
+}
+```
+
+Templates, ranges and the algorithms in Phobos behave as expected:
+
+```d
+import std.stdio;
+import std.algorithm : map, sum;
+import std.range : iota;
+
+void main()
+{
+ auto squares = iota(1, 6).map!(x => x * x);
+ writeln(squares); // [1, 4, 9, 16, 25]
+ writeln(squares.sum); // 55
+}
+```
+
+`readln` reads from the **stdin** pane, in the same way as the other console languages.
+
+## Language Info
+
+### Name
+
+`d-wasm`
+
+### Aliases / Extensions
+
+`d`, `di`, `dlang`, `dmd`
+
+### Editor
+
+`script`
+
+## Compiler
+
+[DMD](https://dlang.org/) (the D reference compiler) compiled to WebAssembly, with druntime and
+Phobos.
+
+### Version
+
+DMD, built with the WebAssembly backend from [dkorpel's dmd-explorer](https://dkorpel.github.io/dmd-explorer/).
+
+## Code Formatting
+
+Not currently supported.
+
+## Live Reload
+
+By default, new code changes are sent to the result page for re-evaluation without a full page reload. This behavior can be disabled by adding the code comment `// __livecodes_reload__` to the D code, which forces a full page reload.
+
+This comment can be added in the [`hiddenContent` property of the editor](../configuration/configuration-object.mdx#markup) for embedded playgrounds.
+
+## Example Usage
+
+```d
+import std.stdio;
+
+void main()
+{
+ int sum = 0;
+ foreach (i; 1 .. 6)
+ {
+ sum += i;
+ }
+ writeln("Sum = ", sum); // Sum = 15
+ writeln("Mean = ", sum / 5); // Mean = 3
+}
+```
+
+## Starter Template
+
+https://livecodes.io/?template=d-wasm
+
+## Links
+
+- [D official website](https://dlang.org/)
+- [D language specification](https://dlang.org/spec/spec.html)
+- [D language tour](https://tour.dlang.org/)
+- [Learn D in Y minutes](https://learnxinyminutes.com/docs/d/)
+- [@live-codes/dlang-wasm](https://www.npmjs.com/package/@live-codes/dlang-wasm)
diff --git a/docs/src/components/LanguageSliders.tsx b/docs/src/components/LanguageSliders.tsx
index e87b4a218b..6e3a31c7fd 100644
--- a/docs/src/components/LanguageSliders.tsx
+++ b/docs/src/components/LanguageSliders.tsx
@@ -93,6 +93,7 @@ export default function Sliders() {
{ name: 'objc-wasm', title: 'Objective-C (Wasm)' },
{ name: 'objcpp-wasm', title: 'Objective-C++ (Wasm)' },
{ name: 'fortran', title: 'Fortran' },
+ { name: 'd-wasm', title: 'D (Wasm)' },
{ name: 'rust-wasm', title: 'Rust (Wasm)' },
{ name: 'swift-wasm', title: 'Swift (Wasm)' },
{ name: 'haskell', title: 'Haskell' },
diff --git a/docs/src/components/TemplateList.tsx b/docs/src/components/TemplateList.tsx
index 16e56b01d9..30e100120f 100644
--- a/docs/src/components/TemplateList.tsx
+++ b/docs/src/components/TemplateList.tsx
@@ -49,6 +49,7 @@ const templates = [
{ name: 'cpp', title: 'C++ Starter', thumbnail: 'cpp.svg' },
{ name: 'c-wasm', title: 'C (Wasm) Starter', thumbnail: 'c.svg' },
{ name: 'fortran-wasm', title: 'Fortran Starter', thumbnail: 'fortran.svg' },
+ { name: 'd-wasm', title: 'D Starter', thumbnail: 'd.svg' },
{ name: 'cpp-wasm', title: 'C++ (Wasm) Starter', thumbnail: 'cpp.svg' },
{ name: 'objc-wasm', title: 'Objective-C (Wasm) Starter', thumbnail: 'objective-c.svg' },
{ name: 'rust-wasm', title: 'Rust (Wasm) Starter', thumbnail: 'rust.svg' },
diff --git a/functions/vendors/templates.js b/functions/vendors/templates.js
index 347939811e..f3f85873a5 100644
--- a/functions/vendors/templates.js
+++ b/functions/vendors/templates.js
@@ -51,6 +51,7 @@ export const starterTemplates = {
"cpp-wasm": "C++ (Wasm) Starter",
"objc-wasm": "Objective-C (Wasm) Starter",
"fortran-wasm": "Fortran Starter",
+ "d-wasm": "D Starter",
"rust-wasm": "Rust (Wasm) Starter",
"swift-wasm": "Swift (Wasm) Starter",
"zig-wasm": "Zig (Wasm) Starter",
diff --git a/scripts/build.js b/scripts/build.js
index 81a0f32183..7c797a53d9 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -229,6 +229,7 @@ const iifeBuild = () =>
'languages/cpp/lang-cpp-script.ts',
'languages/clang-wasm/lang-clang-wasm-script.ts',
'languages/fortran-wasm/lang-fortran-wasm-script.ts',
+ 'languages/d-wasm/lang-d-wasm-script.ts',
'languages/go-wasm/lang-go-wasm-script.ts',
'languages/csharp-wasm/lang-csharp-wasm-script.ts',
'languages/fanak/lang-fanak-script.ts',
diff --git a/server/php/inc/starter-templates.json b/server/php/inc/starter-templates.json
index cfa96c36b4..c1c985ba7e 100644
--- a/server/php/inc/starter-templates.json
+++ b/server/php/inc/starter-templates.json
@@ -51,6 +51,7 @@
"cpp-wasm": "C++ (Wasm) Starter",
"objc-wasm": "Objective-C (Wasm) Starter",
"fortran-wasm": "Fortran Starter",
+ "d-wasm": "D Starter",
"rust-wasm": "Rust (Wasm) Starter",
"swift-wasm": "Swift (Wasm) Starter",
"zig-wasm": "Zig (Wasm) Starter",
diff --git a/src/livecodes/UI/command-menu-actions.ts b/src/livecodes/UI/command-menu-actions.ts
index d03bd476f2..8bee6f09e8 100644
--- a/src/livecodes/UI/command-menu-actions.ts
+++ b/src/livecodes/UI/command-menu-actions.ts
@@ -307,6 +307,7 @@ export const getCommandMenuActions = ({
'cpp-wasm',
'objc-wasm',
'fortran-wasm',
+ 'd-wasm',
'rust-wasm',
'swift-wasm',
'zig-wasm',
diff --git a/src/livecodes/assets/templates/d.svg b/src/livecodes/assets/templates/d.svg
new file mode 100644
index 0000000000..45aa32b149
--- /dev/null
+++ b/src/livecodes/assets/templates/d.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/livecodes/html/language-info.html b/src/livecodes/html/language-info.html
index f9f9713781..4045bb28bd 100644
--- a/src/livecodes/html/language-info.html
+++ b/src/livecodes/html/language-info.html
@@ -572,6 +572,42 @@
C# (Wasm)
+
+ D (Wasm)
+
+ D compiled to WebAssembly by
+
DMD, running entirely in the
+ browser.
+
+
+
Diagrams
(Experimental)
diff --git a/src/livecodes/i18n/locales/en/language-info.lokalise.json b/src/livecodes/i18n/locales/en/language-info.lokalise.json
index 987aaaa837..f42b1c02f7 100644
--- a/src/livecodes/i18n/locales/en/language-info.lokalise.json
+++ b/src/livecodes/i18n/locales/en/language-info.lokalise.json
@@ -244,6 +244,18 @@
"notes": "",
"translation": "C# (Wasm)"
},
+ "d-wasm.desc": {
+ "notes": "### ###\n\n\n",
+ "translation": "D compiled to WebAssembly by DMD, running entirely in the browser."
+ },
+ "d-wasm.link": {
+ "notes": "### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n### ###\n\n\n",
+ "translation": " D official website D language specification D language tour Learn X in Y minutes, where X=D LiveCodes Documentation Load starter template "
+ },
+ "d-wasm.name": {
+ "notes": "",
+ "translation": "D (Wasm)"
+ },
"diagrams.desc1": {
"notes": "",
"translation": "(Experimental)"
diff --git a/src/livecodes/i18n/locales/en/language-info.ts b/src/livecodes/i18n/locales/en/language-info.ts
index 0dc6d293bf..d74143ca5c 100644
--- a/src/livecodes/i18n/locales/en/language-info.ts
+++ b/src/livecodes/i18n/locales/en/language-info.ts
@@ -114,6 +114,11 @@ const languageInfo = {
link: '<1> <2>C# language documentation2> 1> <3> <4>Learn X in Y minutes, where X=C#4> 3> <5> <6>LiveCodes Documentation6> 5> <7> <8>Load starter template8> 7>',
name: 'C# (Wasm)',
},
+ 'd-wasm': {
+ desc: 'D compiled to WebAssembly by <1>DMD1>, running entirely in the browser.',
+ link: '<1> <2>D official website2> 1> <3> <4>D language specification4> 3> <5> <6>D language tour6> 5> <7> <8>Learn X in Y minutes, where X=D8> 7> <9> <10>LiveCodes Documentation10> 9> <11> <12>Load starter template12> 11>',
+ name: 'D (Wasm)',
+ },
diagrams: {
desc1: '(Experimental)',
desc2: 'Diagrams-as-code. Supports:',
diff --git a/src/livecodes/i18n/locales/en/translation.lokalise.json b/src/livecodes/i18n/locales/en/translation.lokalise.json
index e6f2d89824..185299623c 100644
--- a/src/livecodes/i18n/locales/en/translation.lokalise.json
+++ b/src/livecodes/i18n/locales/en/translation.lokalise.json
@@ -2624,6 +2624,10 @@
"notes": "",
"translation": "C# (Wasm) Starter"
},
+ "templates.starter.d-wasm": {
+ "notes": "",
+ "translation": "D Starter"
+ },
"templates.starter.d3": {
"notes": "",
"translation": "D3 Starter"
diff --git a/src/livecodes/i18n/locales/en/translation.ts b/src/livecodes/i18n/locales/en/translation.ts
index 89958f0bde..0d5e60446c 100644
--- a/src/livecodes/i18n/locales/en/translation.ts
+++ b/src/livecodes/i18n/locales/en/translation.ts
@@ -1002,6 +1002,7 @@ const translation = {
cpp: 'C++ Starter',
'cpp-wasm': 'C++ (Wasm) Starter',
'csharp-wasm': 'C# (Wasm) Starter',
+ 'd-wasm': 'D Starter',
d3: 'D3 Starter',
daisyui: 'daisyUI Starter',
diagrams: 'Diagrams Starter',
diff --git a/src/livecodes/languages/d-wasm/__tests__/lang-d-wasm.spec.ts b/src/livecodes/languages/d-wasm/__tests__/lang-d-wasm.spec.ts
new file mode 100644
index 0000000000..fa7a4050f2
--- /dev/null
+++ b/src/livecodes/languages/d-wasm/__tests__/lang-d-wasm.spec.ts
@@ -0,0 +1,28 @@
+import { dWasm } from '../lang-d-wasm';
+
+describe('D (Wasm) language spec', () => {
+ test('is registered as d-wasm, with the wasm compiler script', () => {
+ expect(dWasm.name).toBe('d-wasm');
+ expect(dWasm.title).toBe('D (Wasm)');
+ expect(dWasm.compiler).toMatchObject({
+ scriptType: 'text/d-wasm',
+ compiledCodeLanguage: 'd-wasm',
+ liveReload: true,
+ });
+ });
+
+ test('accepts the common D extensions', () => {
+ expect(dWasm.extensions).toEqual(expect.arrayContaining(['d', 'di', 'dlang', 'dmd']));
+ });
+
+ test('warns that the first run is a large download', () => {
+ // The DMD runtime is ~5.5 MB compressed (25 MB inflated); the app shows a loading indicator.
+ expect(dWasm.largeDownload).toBe(true);
+ });
+
+ test('uses the C++ editor, since Monaco has no D language', () => {
+ expect(dWasm.editor).toBe('script');
+ expect(dWasm.editorSupport?.monaco?.language).toBe('cpp');
+ expect(dWasm.editorSupport?.codejar?.language).toBe('d');
+ });
+});
diff --git a/src/livecodes/languages/d-wasm/index.ts b/src/livecodes/languages/d-wasm/index.ts
new file mode 100644
index 0000000000..7c6cf784a8
--- /dev/null
+++ b/src/livecodes/languages/d-wasm/index.ts
@@ -0,0 +1 @@
+export * from './lang-d-wasm';
diff --git a/src/livecodes/languages/d-wasm/lang-d-wasm-script.ts b/src/livecodes/languages/d-wasm/lang-d-wasm-script.ts
new file mode 100644
index 0000000000..5e0a241ec7
--- /dev/null
+++ b/src/livecodes/languages/d-wasm/lang-d-wasm-script.ts
@@ -0,0 +1,306 @@
+/* eslint-disable no-console */
+import { createWorkerFromContent, getErrorMessage } from '../../utils/utils';
+import { dlangWasmBaseUrl } from '../../vendors';
+
+// One runner for one language. The compiler itself is @live-codes/dlang-wasm (DMD compiled to
+// WebAssembly), loaded from its CDN build: the IIFE defines `self.dlangWasm`, which the worker
+// reaches with importScripts. It compiles and runs the program in place, because a browser has no
+// linker subprocess to hand a binary to.
+const SCRIPT_TYPE = 'text/d-wasm';
+
+interface WorkerRunResult {
+ output: string;
+ errors: string[];
+ exitCode: number | null;
+}
+
+interface RunResult {
+ input: string;
+ output: string | null;
+ error: string | null;
+ exitCode: number | null;
+}
+
+interface Runner {
+ ensureReady: () => Promise;
+ run: (code: string, input: string) => Promise;
+}
+
+interface DWasmApi {
+ ready: boolean;
+ input: string;
+ output: string | null;
+ error: string | null;
+ exitCode: number | null;
+ loaded: Promise;
+ init: Promise | null;
+ runner: Runner | null;
+ run: (input?: string) => Promise;
+}
+
+declare const window: Window & {
+ livecodes: Record;
+};
+
+const getWorkerSrc = () => `
+importScripts(${JSON.stringify(dlangWasmBaseUrl + 'dist/dlang-wasm.global.js')});
+
+let compilerPromise = null;
+
+const getCompiler = () => {
+ compilerPromise =
+ compilerPromise ||
+ self.dlangWasm
+ .createCompiler('d', {
+ baseUrl: ${JSON.stringify(dlangWasmBaseUrl + 'assets/')},
+ })
+ .then(async (compiler) => {
+ // Compile druntime up front so the first Run only pays for the program.
+ await compiler.warm();
+ return compiler;
+ });
+ return compilerPromise;
+};
+
+addEventListener('message', async (event) => {
+ const { id, code, input } = event.data;
+ try {
+ const compiler = await getCompiler();
+ const result = await compiler.run(code, input);
+ postMessage({
+ id,
+ result: {
+ output: result.output,
+ // The package reports diagnostics as an array; empty on a clean compile.
+ errors: result.errors || [],
+ exitCode: result.exitCode,
+ },
+ });
+ } catch (error) {
+ postMessage({ id, error: String((error && error.message) || error) });
+ }
+});
+
+getCompiler().then(
+ () => postMessage({ type: 'ready' }),
+ (error) => postMessage({ type: 'error', message: String((error && error.message) || error) }),
+);
+`;
+
+// A single compile + run is normally quick; a long timeout guards against hangs (e.g. an infinite
+// loop in the user program) by killing and respawning the worker. The wasm call cannot be
+// interrupted any other way.
+const RUN_TIMEOUT_MS = 120000;
+
+interface Pending {
+ resolve: (result: WorkerRunResult) => void;
+ reject: (error: Error) => void;
+ timer: ReturnType;
+}
+
+const createRunner = (): Runner => {
+ let worker: Worker | null = null;
+ let ready: Promise | null = null;
+ let settleReady: ((error?: Error) => void) | null = null;
+ let pending: Record = {};
+ let nextId = 1;
+
+ const failAll = (error: Error) => {
+ for (const id of Object.keys(pending)) {
+ const request = pending[Number(id)];
+ clearTimeout(request.timer);
+ request.reject(error);
+ }
+ pending = {};
+ };
+
+ const teardown = (error: Error) => {
+ worker?.terminate();
+ worker = null;
+ ready = null;
+ settleReady?.(error);
+ settleReady = null;
+ failAll(error);
+ // A fresh worker has to re-load and warm the runtime, so drop the cached init promise and
+ // let the next run show the loading indicator while it does.
+ dWasm.init = null;
+ };
+
+ const onMessage = (event: MessageEvent) => {
+ const message = event.data ?? {};
+ if (message.type === 'ready') {
+ settleReady?.();
+ settleReady = null;
+ return;
+ }
+ if (message.type === 'error') {
+ // The runtime failed to load (network, or the browser is unsupported).
+ teardown(new Error(message.message));
+ return;
+ }
+ const request = pending[message.id];
+ if (!request) return;
+ delete pending[message.id];
+ clearTimeout(request.timer);
+ if (message.error != null) {
+ request.reject(new Error(message.error));
+ } else {
+ request.resolve(message.result);
+ }
+ };
+
+ const spawn = () => {
+ ready = new Promise((resolve, reject) => {
+ settleReady = (error?: Error) => (error ? reject(error) : resolve());
+ });
+ worker = createWorkerFromContent(getWorkerSrc());
+ worker.onmessage = onMessage;
+ worker.onerror = (event) => teardown(new Error(`The D worker crashed: ${event.message}`));
+ };
+
+ /** Spawn the worker if needed and resolve once the compiler has loaded. */
+ const ensureReady = async () => {
+ if (!ready) spawn();
+ await ready;
+ };
+
+ const run = (code: string, input: string) =>
+ ensureReady().then(
+ () =>
+ new Promise((resolve, reject) => {
+ // `ensureReady` can resolve after the worker has been torn down (crash or timeout), so
+ // the worker may be gone by the time the request is registered.
+ if (!worker) {
+ reject(new Error('The D worker is not available'));
+ return;
+ }
+ const id = nextId++;
+ const timer = setTimeout(() => {
+ // The worker hung — kill it and reject, so the next run respawns a fresh worker.
+ teardown(new Error('The D compiler timed out'));
+ }, RUN_TIMEOUT_MS);
+ pending[id] = { resolve, reject, timer };
+ worker.postMessage({ id, code, input });
+ }),
+ );
+
+ return { ensureReady, run };
+};
+
+/**
+ * Reads the program out of the result page.
+ *
+ * The result page writes the code inline as the `innerHTML` of a
+ * `
+`.trimStart(),
+ },
+ style: {
+ language: 'css',
+ content: `
+.container,
+.container button {
+ text-align: center;
+ font: 1em sans-serif;
+}
+.logo {
+ width: 150px;
+}
+`.trimStart(),
+ },
+ script: {
+ language: 'd',
+ content: `
+import std.stdio;
+import std.string : strip;
+import std.conv : to;
+
+void main()
+{
+ writeln("D");
+ int count = readln().strip().to!int;
+ writeln(count + 1);
+}
+`.trimStart(),
+ },
+};
diff --git a/src/livecodes/templates/starter/index.ts b/src/livecodes/templates/starter/index.ts
index 7b172772c7..32d76376e4 100644
--- a/src/livecodes/templates/starter/index.ts
+++ b/src/livecodes/templates/starter/index.ts
@@ -19,6 +19,7 @@ import { coffeescriptStarter } from './coffeescript-starter';
import { commonlispStarter } from './commonlisp-starter';
import { cppStarter } from './cpp-starter';
import { csharpWasmStarter } from './csharp-wasm-starter';
+import { dWasmStarter } from './d-wasm-starter';
import { d3Starter } from './d3-starter';
import { daisyuiStarter } from './daisyui-starter';
import { diagramsStarter } from './diagrams-starter';
@@ -139,6 +140,7 @@ export const starterTemplates = [
cppWasmStarter,
objcWasmStarter,
fortranWasmStarter,
+ dWasmStarter,
rustWasmStarter,
swiftWasmStarter,
zigWasmStarter,
diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts
index 8fd81b246f..4223b9f86f 100644
--- a/src/livecodes/vendors.ts
+++ b/src/livecodes/vendors.ts
@@ -134,6 +134,8 @@ export const ddietrCmThemesBaseUrl = /* @__PURE__ */ getUrl(
'@ddietr/codemirror-themes@1.4.2/dist/theme/',
);
+export const dlangWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/dlang-wasm@0.1.0/');
+
export const doppioJvmBaseUrl = 'https://unpkg.com/@seth0x41/doppio@1.0.0/';
export const dotUrl = /* @__PURE__ */ getUrl('dot@1.1.3/doT.js');
diff --git a/src/sdk/models.ts b/src/sdk/models.ts
index b746207a07..eace0cbac1 100644
--- a/src/sdk/models.ts
+++ b/src/sdk/models.ts
@@ -185,6 +185,11 @@ export type Language =
| 'ftn'
| 'fortran-wasm'
| 'lfortran'
+ | 'd'
+ | 'di'
+ | 'dlang'
+ | 'dmd'
+ | 'd-wasm'
| 'cpp-wasm'
| 'cppwasm'
| 'wasm.cpp'
@@ -484,6 +489,7 @@ export type TemplateName =
| 'cpp-wasm'
| 'objc-wasm'
| 'fortran-wasm'
+ | 'd-wasm'
| 'java'
| 'csharp-wasm'
| 'blazor-wasm'
diff --git a/storybook/_stories/EmbedOptions/template.ts b/storybook/_stories/EmbedOptions/template.ts
index d9ff83d65c..f7ddca884c 100644
--- a/storybook/_stories/EmbedOptions/template.ts
+++ b/storybook/_stories/EmbedOptions/template.ts
@@ -51,6 +51,7 @@ const storyDef: StoryDef = {
CWasm: { props: { template: 'c-wasm' } },
CppWasm: { props: { template: 'cpp-wasm' } },
ObjcWasm: { props: { template: 'objc-wasm' } },
+ DWasm: { props: { template: 'd-wasm' } },
RustWasm: { props: { template: 'rust-wasm' } },
SwiftWasm: { props: { template: 'swift-wasm' } },
ZigWasm: { props: { template: 'zig-wasm' } },
diff --git a/storybook/preact/stories/EmbedOptions/template.stories.ts b/storybook/preact/stories/EmbedOptions/template.stories.ts
index a9e2264a5f..9425051cdd 100644
--- a/storybook/preact/stories/EmbedOptions/template.stories.ts
+++ b/storybook/preact/stories/EmbedOptions/template.stories.ts
@@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' });
export const CppWasm = livecodesStory({ template: 'cpp-wasm' });
export const ObjcWasm = livecodesStory({ template: 'objc-wasm' });
export const FortranWasm = livecodesStory({ template: 'fortran-wasm' });
+export const DWasm = livecodesStory({ template: 'd-wasm' });
export const RustWasm = livecodesStory({ template: 'rust-wasm' });
export const SwiftWasm = livecodesStory({ template: 'swift-wasm' });
export const ZigWasm = livecodesStory({ template: 'zig-wasm' });
diff --git a/storybook/react/stories/EmbedOptions/template.stories.ts b/storybook/react/stories/EmbedOptions/template.stories.ts
index a9e2264a5f..9425051cdd 100644
--- a/storybook/react/stories/EmbedOptions/template.stories.ts
+++ b/storybook/react/stories/EmbedOptions/template.stories.ts
@@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' });
export const CppWasm = livecodesStory({ template: 'cpp-wasm' });
export const ObjcWasm = livecodesStory({ template: 'objc-wasm' });
export const FortranWasm = livecodesStory({ template: 'fortran-wasm' });
+export const DWasm = livecodesStory({ template: 'd-wasm' });
export const RustWasm = livecodesStory({ template: 'rust-wasm' });
export const SwiftWasm = livecodesStory({ template: 'swift-wasm' });
export const ZigWasm = livecodesStory({ template: 'zig-wasm' });
diff --git a/storybook/solid/stories/EmbedOptions/template.stories.ts b/storybook/solid/stories/EmbedOptions/template.stories.ts
index a9e2264a5f..9425051cdd 100644
--- a/storybook/solid/stories/EmbedOptions/template.stories.ts
+++ b/storybook/solid/stories/EmbedOptions/template.stories.ts
@@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' });
export const CppWasm = livecodesStory({ template: 'cpp-wasm' });
export const ObjcWasm = livecodesStory({ template: 'objc-wasm' });
export const FortranWasm = livecodesStory({ template: 'fortran-wasm' });
+export const DWasm = livecodesStory({ template: 'd-wasm' });
export const RustWasm = livecodesStory({ template: 'rust-wasm' });
export const SwiftWasm = livecodesStory({ template: 'swift-wasm' });
export const ZigWasm = livecodesStory({ template: 'zig-wasm' });
diff --git a/storybook/svelte/stories/EmbedOptions/template.stories.ts b/storybook/svelte/stories/EmbedOptions/template.stories.ts
index a9e2264a5f..9425051cdd 100644
--- a/storybook/svelte/stories/EmbedOptions/template.stories.ts
+++ b/storybook/svelte/stories/EmbedOptions/template.stories.ts
@@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' });
export const CppWasm = livecodesStory({ template: 'cpp-wasm' });
export const ObjcWasm = livecodesStory({ template: 'objc-wasm' });
export const FortranWasm = livecodesStory({ template: 'fortran-wasm' });
+export const DWasm = livecodesStory({ template: 'd-wasm' });
export const RustWasm = livecodesStory({ template: 'rust-wasm' });
export const SwiftWasm = livecodesStory({ template: 'swift-wasm' });
export const ZigWasm = livecodesStory({ template: 'zig-wasm' });
diff --git a/storybook/vue/stories/EmbedOptions/template.stories.ts b/storybook/vue/stories/EmbedOptions/template.stories.ts
index a9e2264a5f..9425051cdd 100644
--- a/storybook/vue/stories/EmbedOptions/template.stories.ts
+++ b/storybook/vue/stories/EmbedOptions/template.stories.ts
@@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' });
export const CppWasm = livecodesStory({ template: 'cpp-wasm' });
export const ObjcWasm = livecodesStory({ template: 'objc-wasm' });
export const FortranWasm = livecodesStory({ template: 'fortran-wasm' });
+export const DWasm = livecodesStory({ template: 'd-wasm' });
export const RustWasm = livecodesStory({ template: 'rust-wasm' });
export const SwiftWasm = livecodesStory({ template: 'swift-wasm' });
export const ZigWasm = livecodesStory({ template: 'zig-wasm' });
diff --git a/storybook/web-components/stories/EmbedOptions/template.stories.ts b/storybook/web-components/stories/EmbedOptions/template.stories.ts
index a9e2264a5f..9425051cdd 100644
--- a/storybook/web-components/stories/EmbedOptions/template.stories.ts
+++ b/storybook/web-components/stories/EmbedOptions/template.stories.ts
@@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' });
export const CppWasm = livecodesStory({ template: 'cpp-wasm' });
export const ObjcWasm = livecodesStory({ template: 'objc-wasm' });
export const FortranWasm = livecodesStory({ template: 'fortran-wasm' });
+export const DWasm = livecodesStory({ template: 'd-wasm' });
export const RustWasm = livecodesStory({ template: 'rust-wasm' });
export const SwiftWasm = livecodesStory({ template: 'swift-wasm' });
export const ZigWasm = livecodesStory({ template: 'zig-wasm' });
diff --git a/vendor-licenses.md b/vendor-licenses.md
index 1df1547e4a..deb8153ea0 100644
--- a/vendor-licenses.md
+++ b/vendor-licenses.md
@@ -344,6 +344,10 @@ wabt.js: [Apache-2.0 license](https://github.com/AssemblyScript/wabt.js/blob/182
wacl: [BSD 3-Clause License](https://github.com/ecky-l/wacl/blob/9daacabb0102a9986f33263261350edfeebdd83b/LICENSE)
+@live-codes/dlang-wasm: [MIT License](https://github.com/live-codes/browser-d/blob/main/LICENSE)
+
+DMD / druntime / Phobos (Wasm): [BSL-1.0 License](https://github.com/dlang/dmd/blob/master/LICENSE.txt)
+
@live-codes/lfortran-wasm: [MIT License](https://github.com/live-codes/browser-fortran/blob/main/LICENSE)
LFortran (Wasm): [BSD 3-Clause License](https://github.com/lfortran/lfortran/blob/main/LICENSE)
From 2927237acfee897edfe5f7774bd768998659cb33 Mon Sep 17 00:00:00 2001
From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com>
Date: Tue, 6 Oct 2026 09:18:39 +0000
Subject: [PATCH 2/5] fix: post loading status to the app origin in D (Wasm)
---
.../languages/d-wasm/lang-d-wasm-script.ts | 27 ++++++++++++++++---
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/src/livecodes/languages/d-wasm/lang-d-wasm-script.ts b/src/livecodes/languages/d-wasm/lang-d-wasm-script.ts
index 5e0a241ec7..366006c92c 100644
--- a/src/livecodes/languages/d-wasm/lang-d-wasm-script.ts
+++ b/src/livecodes/languages/d-wasm/lang-d-wasm-script.ts
@@ -224,6 +224,25 @@ const setResult = (
return { input, output, error, exitCode };
};
+// The result runs in an iframe; post status updates to the app origin. Mirrors
+// the other WASM language scripts (`haskell-wasm`).
+const parentOrigin =
+ window.parent === window
+ ? window.location.origin
+ : window.location.ancestorOrigins?.[0] ||
+ (() => {
+ if (!document.referrer) return '*';
+ try {
+ return new URL(document.referrer).origin;
+ } catch {
+ // Ignore malformed referrers and use the wildcard fallback below.
+ return '*';
+ }
+ })();
+
+const postLoading = (payload: boolean) =>
+ parent.postMessage({ type: 'loading', payload }, parentOrigin); // NOSONAR - fallback is safe with source/origin checks in the parent.
+
window.livecodes.d ??= {} as DWasmApi;
const dWasm = window.livecodes.d;
@@ -237,11 +256,11 @@ const ensureLoaded = (runner: Runner): Promise => {
let init = dWasm.init;
if (!init) {
init = (async () => {
- parent.postMessage({ type: 'loading', payload: true }, '*');
+ postLoading(true);
try {
await runner.ensureReady();
} finally {
- parent.postMessage({ type: 'loading', payload: false }, '*');
+ postLoading(false);
}
})().catch((error: Error) => {
// Reset so a later run can retry the download.
@@ -300,7 +319,7 @@ dWasm.run = async (input?: string) => {
window.livecodes.dlang = dWasm;
window.addEventListener('load', async () => {
- parent.postMessage({ type: 'loading', payload: true }, '*');
+ postLoading(true);
await dWasm.run(dWasm.input);
- parent.postMessage({ type: 'loading', payload: false }, '*');
+ postLoading(false);
});
From 82162a2858e50a4f30f70e68efb3938f36fc548e Mon Sep 17 00:00:00 2001
From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com>
Date: Tue, 6 Oct 2026 13:52:23 +0000
Subject: [PATCH 3/5] feat: add D (Wasm) translations and update logo
---
src/livecodes/assets/templates/d.svg | 183 +++++++++++++++++-
.../locales/ar/language-info.lokalise.json | 9 +
.../i18n/locales/ar/language-info.ts | 5 +
.../i18n/locales/ar/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/ar/translation.ts | 1 +
.../locales/bn/language-info.lokalise.json | 9 +
.../i18n/locales/bn/language-info.ts | 5 +
.../i18n/locales/bn/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/bn/translation.ts | 1 +
.../locales/de/language-info.lokalise.json | 9 +
.../i18n/locales/de/language-info.ts | 5 +
.../i18n/locales/de/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/de/translation.ts | 1 +
.../locales/es/language-info.lokalise.json | 9 +
.../i18n/locales/es/language-info.ts | 5 +
.../i18n/locales/es/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/es/translation.ts | 1 +
.../locales/fa/language-info.lokalise.json | 9 +
.../i18n/locales/fa/language-info.ts | 5 +
.../i18n/locales/fa/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/fa/translation.ts | 1 +
.../locales/fr/language-info.lokalise.json | 9 +
.../i18n/locales/fr/language-info.ts | 5 +
.../i18n/locales/fr/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/fr/translation.ts | 1 +
.../locales/hi/language-info.lokalise.json | 9 +
.../i18n/locales/hi/language-info.ts | 5 +
.../i18n/locales/hi/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/hi/translation.ts | 1 +
.../locales/hu/language-info.lokalise.json | 9 +
.../i18n/locales/hu/language-info.ts | 5 +
.../i18n/locales/hu/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/hu/translation.ts | 1 +
.../locales/id/language-info.lokalise.json | 9 +
.../i18n/locales/id/language-info.ts | 5 +
.../i18n/locales/id/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/id/translation.ts | 1 +
.../locales/it/language-info.lokalise.json | 9 +
.../i18n/locales/it/language-info.ts | 5 +
.../i18n/locales/it/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/it/translation.ts | 1 +
.../locales/ja/language-info.lokalise.json | 9 +
.../i18n/locales/ja/language-info.ts | 5 +
.../i18n/locales/ja/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/ja/translation.ts | 1 +
.../locales/nl/language-info.lokalise.json | 9 +
.../i18n/locales/nl/language-info.ts | 5 +
.../i18n/locales/nl/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/nl/translation.ts | 1 +
.../locales/pt/language-info.lokalise.json | 9 +
.../i18n/locales/pt/language-info.ts | 5 +
.../i18n/locales/pt/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/pt/translation.ts | 1 +
.../locales/ru/language-info.lokalise.json | 9 +
.../i18n/locales/ru/language-info.ts | 5 +
.../i18n/locales/ru/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/ru/translation.ts | 1 +
.../locales/tr/language-info.lokalise.json | 9 +
.../i18n/locales/tr/language-info.ts | 5 +
.../i18n/locales/tr/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/tr/translation.ts | 1 +
.../locales/ur/language-info.lokalise.json | 9 +
.../i18n/locales/ur/language-info.ts | 5 +
.../i18n/locales/ur/translation.lokalise.json | 3 +
src/livecodes/i18n/locales/ur/translation.ts | 1 +
.../locales/zh-CN/language-info.lokalise.json | 9 +
.../i18n/locales/zh-CN/language-info.ts | 5 +
.../locales/zh-CN/translation.lokalise.json | 3 +
.../i18n/locales/zh-CN/translation.ts | 1 +
69 files changed, 482 insertions(+), 7 deletions(-)
diff --git a/src/livecodes/assets/templates/d.svg b/src/livecodes/assets/templates/d.svg
index 45aa32b149..8f2f56bc69 100644
--- a/src/livecodes/assets/templates/d.svg
+++ b/src/livecodes/assets/templates/d.svg
@@ -1,8 +1,177 @@
-