Merge pull request #2283 from alixander/d2js-split
d2js: remove entrypoints and code split esm module
This commit is contained in:
commit
a416253a05
10 changed files with 117 additions and 148 deletions
|
|
@ -21,4 +21,4 @@ node_modules:
|
||||||
|
|
||||||
.PHONY: cleanup
|
.PHONY: cleanup
|
||||||
cleanup: test
|
cleanup: test
|
||||||
prefix "$@" git checkout -- src/platform.js
|
prefix "$@" git checkout -- src/platform.js src/worker.js
|
||||||
|
|
|
||||||
|
|
@ -31,36 +31,18 @@ bun add @terrastruct/d2
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Browser
|
D2.js uses webworkers to call a WASM file.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
// Same for Node or browser
|
||||||
import { D2 } from '@terrastruct/d2';
|
import { D2 } from '@terrastruct/d2';
|
||||||
|
// Or using a CDN
|
||||||
|
// import { D2 } from 'https://esm.sh/@terrastruct/d2';
|
||||||
|
|
||||||
const d2 = new D2();
|
const d2 = new D2();
|
||||||
|
|
||||||
const result = await d2.compile('x -> y');
|
const result = await d2.compile('x -> y');
|
||||||
const svg = await d2.render(result.diagram);
|
const svg = await d2.render(result.diagram);
|
||||||
|
|
||||||
const result = await d2.compile('x -> y', {
|
|
||||||
layout: 'dagre',
|
|
||||||
sketch: true
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Node
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { D2 } from '@terrastruct/d2';
|
|
||||||
|
|
||||||
const d2 = new D2();
|
|
||||||
|
|
||||||
async function createDiagram() {
|
|
||||||
const result = await d2.compile('x -> y');
|
|
||||||
const svg = await d2.render(result.diagram);
|
|
||||||
console.log(svg);
|
|
||||||
}
|
|
||||||
|
|
||||||
createDiagram();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Reference
|
## API Reference
|
||||||
|
|
@ -87,7 +69,7 @@ D2.js uses Bun, so install this first.
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/terrastruct/d2.git
|
git clone https://github.com/terrastruct/d2.git
|
||||||
cd d2/d2js/js
|
cd d2/d2js/js
|
||||||
./make.sh
|
./make.sh all
|
||||||
```
|
```
|
||||||
|
|
||||||
If you change the main D2 source code, you should regenerate the WASM file:
|
If you change the main D2 source code, you should regenerate the WASM file:
|
||||||
|
|
@ -97,6 +79,8 @@ If you change the main D2 source code, you should regenerate the WASM file:
|
||||||
|
|
||||||
### Running the Development Server
|
### Running the Development Server
|
||||||
|
|
||||||
|
Make sure you've built already, then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run dev
|
bun run dev
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,11 @@ await writeFile(
|
||||||
);
|
);
|
||||||
|
|
||||||
const commonConfig = {
|
const commonConfig = {
|
||||||
splitting: false,
|
|
||||||
sourcemap: "external",
|
sourcemap: "external",
|
||||||
minify: true,
|
minify: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function buildPlatformFile(platform) {
|
async function buildDynamicFiles(platform) {
|
||||||
const platformContent =
|
const platformContent =
|
||||||
platform === "node"
|
platform === "node"
|
||||||
? `export * from "./platform.node.js";`
|
? `export * from "./platform.node.js";`
|
||||||
|
|
@ -36,51 +35,50 @@ async function buildPlatformFile(platform) {
|
||||||
|
|
||||||
const platformPath = join(SRC_DIR, "platform.js");
|
const platformPath = join(SRC_DIR, "platform.js");
|
||||||
await writeFile(platformPath, platformContent);
|
await writeFile(platformPath, platformContent);
|
||||||
|
|
||||||
|
const workerSource =
|
||||||
|
platform === "node"
|
||||||
|
? join(SRC_DIR, "worker.node.js")
|
||||||
|
: join(SRC_DIR, "worker.browser.js");
|
||||||
|
|
||||||
|
const workerTarget = join(SRC_DIR, "worker.js");
|
||||||
|
const workerContent = await readFile(workerSource, "utf8");
|
||||||
|
await writeFile(workerTarget, workerContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildAndCopy(buildType) {
|
async function buildAndCopy(buildType) {
|
||||||
const configs = {
|
const configs = {
|
||||||
browser: {
|
browser: {
|
||||||
outdir: resolve(ROOT_DIR, "dist/browser"),
|
outdir: resolve(ROOT_DIR, "dist/browser"),
|
||||||
|
splitting: false,
|
||||||
format: "esm",
|
format: "esm",
|
||||||
target: "browser",
|
target: "browser",
|
||||||
platform: "browser",
|
platform: "browser",
|
||||||
loader: {
|
loader: {
|
||||||
".js": "jsx",
|
".js": "jsx",
|
||||||
},
|
},
|
||||||
entrypoints: [
|
entrypoints: [resolve(SRC_DIR, "index.js"), resolve(SRC_DIR, "worker.js")],
|
||||||
resolve(SRC_DIR, "index.js"),
|
|
||||||
resolve(SRC_DIR, "worker.js"),
|
|
||||||
resolve(SRC_DIR, "platform.js"),
|
|
||||||
resolve(SRC_DIR, "wasm-loader.browser.js"),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
"node-esm": {
|
"node-esm": {
|
||||||
outdir: resolve(ROOT_DIR, "dist/node-esm"),
|
outdir: resolve(ROOT_DIR, "dist/node-esm"),
|
||||||
|
splitting: true,
|
||||||
format: "esm",
|
format: "esm",
|
||||||
target: "node",
|
target: "node",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
entrypoints: [
|
entrypoints: [resolve(SRC_DIR, "index.js"), resolve(SRC_DIR, "worker.js")],
|
||||||
resolve(SRC_DIR, "index.js"),
|
|
||||||
resolve(SRC_DIR, "worker.js"),
|
|
||||||
resolve(SRC_DIR, "platform.js"),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
"node-cjs": {
|
"node-cjs": {
|
||||||
outdir: resolve(ROOT_DIR, "dist/node-cjs"),
|
outdir: resolve(ROOT_DIR, "dist/node-cjs"),
|
||||||
|
splitting: false,
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
target: "node",
|
target: "node",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
entrypoints: [
|
entrypoints: [resolve(SRC_DIR, "index.js"), resolve(SRC_DIR, "worker.js")],
|
||||||
resolve(SRC_DIR, "index.js"),
|
|
||||||
resolve(SRC_DIR, "worker.js"),
|
|
||||||
resolve(SRC_DIR, "platform.js"),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = configs[buildType];
|
const config = configs[buildType];
|
||||||
await buildPlatformFile(config.platform);
|
await buildDynamicFiles(config.platform);
|
||||||
|
|
||||||
const result = await build({
|
const result = await build({
|
||||||
...commonConfig,
|
...commonConfig,
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="output"></div>
|
<div id="output"></div>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { D2 } from "../src/index.js";
|
import { D2 } from "../dist/browser/index.js";
|
||||||
const d2 = new D2();
|
const d2 = new D2();
|
||||||
window.compile = async () => {
|
window.compile = async () => {
|
||||||
const input = document.getElementById("input").value;
|
const input = document.getElementById("input").value;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "@terrastruct/d2",
|
"name": "@terrastruct/d2",
|
||||||
"author": "Terrastruct, Inc.",
|
"author": "Terrastruct, Inc.",
|
||||||
"description": "D2.js is a wrapper around the WASM build of D2, the modern text-to-diagram language.",
|
"description": "D2.js is a wrapper around the WASM build of D2, the modern text-to-diagram language.",
|
||||||
"version": "0.1.11",
|
"version": "0.1.19",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/terrastruct/d2.git",
|
"url": "git+https://github.com/terrastruct/d2.git",
|
||||||
|
|
@ -21,10 +21,14 @@
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"browser": "./dist/browser/index.js",
|
"browser": "./dist/browser/index.js",
|
||||||
"import": "./dist/node-esm/index.js",
|
"import": {
|
||||||
|
"browser": "./dist/browser/index.js",
|
||||||
|
"default": "./dist/node-esm/index.js"
|
||||||
|
},
|
||||||
"require": "./dist/node-cjs/index.js",
|
"require": "./dist/node-cjs/index.js",
|
||||||
"default": "./dist/node-esm/index.js"
|
"default": "./dist/node-esm/index.js"
|
||||||
}
|
},
|
||||||
|
"./worker": "./dist/browser/worker.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,19 @@ export async function loadFile(path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createWorker() {
|
export async function createWorker() {
|
||||||
// Combine wasmExecJs with worker script
|
let response = await fetch(new URL("./worker.js", import.meta.url));
|
||||||
const workerResponse = await fetch(new URL("./worker.js", import.meta.url));
|
if (!response.ok)
|
||||||
if (!workerResponse.ok) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to load worker.js: ${workerResponse.status} ${workerResponse.statusText}`
|
`Failed to load worker.js: ${response.status} ${response.statusText}`
|
||||||
);
|
);
|
||||||
}
|
let workerScript = await response.text();
|
||||||
const workerJs = await workerResponse.text();
|
|
||||||
|
|
||||||
const blob = new Blob(["(() => {", wasmExecJs, "})();", workerJs], {
|
let blob = new Blob([wasmExecJs, workerScript], {
|
||||||
type: "application/javascript",
|
type: "text/javascript;charset=utf-8",
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Worker(URL.createObjectURL(blob));
|
const worker = new Worker(URL.createObjectURL(blob), {
|
||||||
|
type: "module",
|
||||||
|
});
|
||||||
|
return worker;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
d2js/js/src/worker.browser.js
Normal file
10
d2js/js/src/worker.browser.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { setupMessageHandler } from "./worker.shared.js";
|
||||||
|
|
||||||
|
async function initWasmBrowser(wasmBinary) {
|
||||||
|
const go = new Go();
|
||||||
|
const result = await WebAssembly.instantiate(wasmBinary, go.importObject);
|
||||||
|
go.run(result.instance);
|
||||||
|
return self.d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupMessageHandler(false, self, initWasmBrowser);
|
||||||
|
|
@ -1,92 +1 @@
|
||||||
const isNode = typeof process !== "undefined" && process.release?.name === "node";
|
// Replaced at build time
|
||||||
let currentPort;
|
|
||||||
let wasm;
|
|
||||||
let d2;
|
|
||||||
|
|
||||||
async function initWasm(wasmBinary) {
|
|
||||||
const go = new Go();
|
|
||||||
const result = await WebAssembly.instantiate(wasmBinary, go.importObject);
|
|
||||||
go.run(result.instance);
|
|
||||||
return isNode ? global.d2 : self.d2;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupMessageHandler(port) {
|
|
||||||
currentPort = port;
|
|
||||||
if (isNode) {
|
|
||||||
port.on("message", handleMessage);
|
|
||||||
} else {
|
|
||||||
port.onmessage = (e) => handleMessage(e.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleMessage(e) {
|
|
||||||
const { type, data } = e;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "init":
|
|
||||||
try {
|
|
||||||
if (isNode) {
|
|
||||||
eval(data.wasmExecContent);
|
|
||||||
}
|
|
||||||
d2 = await initWasm(data.wasm);
|
|
||||||
currentPort.postMessage({ type: "ready" });
|
|
||||||
} catch (err) {
|
|
||||||
currentPort.postMessage({
|
|
||||||
type: "error",
|
|
||||||
error: err.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "compile":
|
|
||||||
try {
|
|
||||||
const result = await d2.compile(JSON.stringify(data));
|
|
||||||
const response = JSON.parse(result);
|
|
||||||
if (response.error) {
|
|
||||||
throw new Error(response.error.message);
|
|
||||||
}
|
|
||||||
currentPort.postMessage({
|
|
||||||
type: "result",
|
|
||||||
data: response.data,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
currentPort.postMessage({
|
|
||||||
type: "error",
|
|
||||||
error: err.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "render":
|
|
||||||
try {
|
|
||||||
const result = await d2.render(JSON.stringify(data));
|
|
||||||
const response = JSON.parse(result);
|
|
||||||
if (response.error) {
|
|
||||||
throw new Error(response.error.message);
|
|
||||||
}
|
|
||||||
currentPort.postMessage({
|
|
||||||
type: "result",
|
|
||||||
data: atob(response.data),
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
currentPort.postMessage({
|
|
||||||
type: "error",
|
|
||||||
error: err.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
if (isNode) {
|
|
||||||
const { parentPort } = await import("node:worker_threads");
|
|
||||||
setupMessageHandler(parentPort);
|
|
||||||
} else {
|
|
||||||
setupMessageHandler(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init().catch((err) => {
|
|
||||||
console.error("Initialization error:", err);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
11
d2js/js/src/worker.node.js
Normal file
11
d2js/js/src/worker.node.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { parentPort } from "node:worker_threads";
|
||||||
|
import { setupMessageHandler } from "./worker.shared.js";
|
||||||
|
|
||||||
|
async function initWasmNode(wasmBinary) {
|
||||||
|
const go = new Go();
|
||||||
|
const result = await WebAssembly.instantiate(wasmBinary, go.importObject);
|
||||||
|
go.run(result.instance);
|
||||||
|
return global.d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupMessageHandler(true, parentPort, initWasmNode);
|
||||||
52
d2js/js/src/worker.shared.js
Normal file
52
d2js/js/src/worker.shared.js
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
let currentPort;
|
||||||
|
let d2;
|
||||||
|
|
||||||
|
export function setupMessageHandler(isNode, port, initWasm) {
|
||||||
|
currentPort = port;
|
||||||
|
|
||||||
|
const handleMessage = async (e) => {
|
||||||
|
const { type, data } = e;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "init":
|
||||||
|
try {
|
||||||
|
if (isNode) {
|
||||||
|
eval(data.wasmExecContent);
|
||||||
|
}
|
||||||
|
d2 = await initWasm(data.wasm);
|
||||||
|
currentPort.postMessage({ type: "ready" });
|
||||||
|
} catch (err) {
|
||||||
|
currentPort.postMessage({ type: "error", error: err.message });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "compile":
|
||||||
|
try {
|
||||||
|
const result = await d2.compile(JSON.stringify(data));
|
||||||
|
const response = JSON.parse(result);
|
||||||
|
if (response.error) throw new Error(response.error.message);
|
||||||
|
currentPort.postMessage({ type: "result", data: response.data });
|
||||||
|
} catch (err) {
|
||||||
|
currentPort.postMessage({ type: "error", error: err.message });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "render":
|
||||||
|
try {
|
||||||
|
const result = await d2.render(JSON.stringify(data));
|
||||||
|
const response = JSON.parse(result);
|
||||||
|
if (response.error) throw new Error(response.error.message);
|
||||||
|
currentPort.postMessage({ type: "result", data: atob(response.data) });
|
||||||
|
} catch (err) {
|
||||||
|
currentPort.postMessage({ type: "error", error: err.message });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNode) {
|
||||||
|
port.on("message", handleMessage);
|
||||||
|
} else {
|
||||||
|
port.onmessage = (e) => handleMessage(e.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue