separate node esm and browser esm builds
This commit is contained in:
parent
efa632dc83
commit
e7f4df37ad
12 changed files with 188 additions and 74 deletions
1
d2js/js/.gitignore
vendored
1
d2js/js/.gitignore
vendored
|
|
@ -2,6 +2,7 @@ node_modules
|
||||||
.npm
|
.npm
|
||||||
bun.lockb
|
bun.lockb
|
||||||
|
|
||||||
|
src/wasm-loader.browser.js
|
||||||
wasm/d2.wasm
|
wasm/d2.wasm
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
|
|
||||||
113
d2js/js/build.js
113
d2js/js/build.js
|
|
@ -1,35 +1,110 @@
|
||||||
import { build } from "bun";
|
import { build } from "bun";
|
||||||
import { copyFile, mkdir } from "node:fs/promises";
|
import { copyFile, mkdir, writeFile, readFile, rm } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join, resolve } from "node:path";
|
||||||
|
|
||||||
await mkdir("./dist/esm", { recursive: true });
|
const __dirname = new URL(".", import.meta.url).pathname;
|
||||||
await mkdir("./dist/cjs", { recursive: true });
|
const ROOT_DIR = resolve(__dirname);
|
||||||
|
const SRC_DIR = resolve(ROOT_DIR, "src");
|
||||||
|
|
||||||
|
await rm("./dist", { recursive: true, force: true });
|
||||||
|
await mkdir("./dist/browser", { recursive: true });
|
||||||
|
await mkdir("./dist/node-esm", { recursive: true });
|
||||||
|
await mkdir("./dist/node-cjs", { recursive: true });
|
||||||
|
|
||||||
|
const wasmBinary = await readFile("./wasm/d2.wasm");
|
||||||
|
const wasmExecJs = await readFile("./wasm/wasm_exec.js", "utf8");
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
join(SRC_DIR, "wasm-loader.browser.js"),
|
||||||
|
`export const wasmBinary = Uint8Array.from(atob("${Buffer.from(wasmBinary).toString(
|
||||||
|
"base64"
|
||||||
|
)}"), c => c.charCodeAt(0));
|
||||||
|
export const wasmExecJs = ${JSON.stringify(wasmExecJs)};`
|
||||||
|
);
|
||||||
|
|
||||||
const commonConfig = {
|
const commonConfig = {
|
||||||
target: "node",
|
|
||||||
splitting: false,
|
splitting: false,
|
||||||
sourcemap: "external",
|
sourcemap: "external",
|
||||||
minify: true,
|
minify: true,
|
||||||
naming: {
|
};
|
||||||
entry: "[dir]/[name].js",
|
|
||||||
chunk: "[name]-[hash].js",
|
async function buildPlatformFile(platform) {
|
||||||
asset: "[name]-[hash][ext]",
|
const platformContent =
|
||||||
|
platform === "node"
|
||||||
|
? "export * from './platform.node.js';"
|
||||||
|
: "export * from './platform.browser.js';";
|
||||||
|
|
||||||
|
const platformPath = join(SRC_DIR, "platform.js");
|
||||||
|
await writeFile(platformPath, platformContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildAndCopy(buildType) {
|
||||||
|
const configs = {
|
||||||
|
browser: {
|
||||||
|
outdir: resolve(ROOT_DIR, "dist/browser"),
|
||||||
|
format: "esm",
|
||||||
|
target: "browser",
|
||||||
|
platform: "browser",
|
||||||
|
loader: {
|
||||||
|
".js": "jsx",
|
||||||
|
},
|
||||||
|
entrypoints: [
|
||||||
|
resolve(SRC_DIR, "index.js"),
|
||||||
|
resolve(SRC_DIR, "worker.js"),
|
||||||
|
resolve(SRC_DIR, "platform.js"),
|
||||||
|
resolve(SRC_DIR, "wasm-loader.browser.js"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"node-esm": {
|
||||||
|
outdir: resolve(ROOT_DIR, "dist/node-esm"),
|
||||||
|
format: "esm",
|
||||||
|
target: "node",
|
||||||
|
platform: "node",
|
||||||
|
entrypoints: [
|
||||||
|
resolve(SRC_DIR, "index.js"),
|
||||||
|
resolve(SRC_DIR, "worker.js"),
|
||||||
|
resolve(SRC_DIR, "platform.js"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"node-cjs": {
|
||||||
|
outdir: resolve(ROOT_DIR, "dist/node-cjs"),
|
||||||
|
format: "cjs",
|
||||||
|
target: "node",
|
||||||
|
platform: "node",
|
||||||
|
entrypoints: [
|
||||||
|
resolve(SRC_DIR, "index.js"),
|
||||||
|
resolve(SRC_DIR, "worker.js"),
|
||||||
|
resolve(SRC_DIR, "platform.js"),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function buildAndCopy(format) {
|
const config = configs[buildType];
|
||||||
const outdir = `./dist/${format}`;
|
await buildPlatformFile(config.platform);
|
||||||
|
|
||||||
await build({
|
const result = await build({
|
||||||
...commonConfig,
|
...commonConfig,
|
||||||
entrypoints: ["./src/index.js", "./src/worker.js", "./src/platform.js"],
|
...config,
|
||||||
outdir,
|
|
||||||
format,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await copyFile("./wasm/d2.wasm", join(outdir, "d2.wasm"));
|
if (!result.outputs || result.outputs.length === 0) {
|
||||||
await copyFile("./wasm/wasm_exec.js", join(outdir, "wasm_exec.js"));
|
throw new Error(`No outputs generated for ${buildType} build`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await buildAndCopy("esm");
|
if (buildType !== "browser") {
|
||||||
await buildAndCopy("cjs");
|
await copyFile(resolve(ROOT_DIR, "wasm/d2.wasm"), join(config.outdir, "d2.wasm"));
|
||||||
|
await copyFile(
|
||||||
|
resolve(ROOT_DIR, "wasm/wasm_exec.js"),
|
||||||
|
join(config.outdir, "wasm_exec.js")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await buildAndCopy("browser");
|
||||||
|
await buildAndCopy("node-esm");
|
||||||
|
await buildAndCopy("node-cjs");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Build failed:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
"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.0",
|
"version": "0.1.11",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/terrastruct/d2.git",
|
"url": "git+https://github.com/terrastruct/d2.git",
|
||||||
"directory": "d2js/js"
|
"directory": "d2js/js"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
@ -20,8 +20,10 @@
|
||||||
"module": "./dist/esm/index.js",
|
"module": "./dist/esm/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./dist/esm/index.js",
|
"browser": "./dist/browser/index.js",
|
||||||
"require": "./dist/cjs/index.js"
|
"import": "./dist/node-esm/index.js",
|
||||||
|
"require": "./dist/node-cjs/index.js",
|
||||||
|
"default": "./dist/node-esm/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
@ -33,7 +35,7 @@
|
||||||
"test:integration": "bun test test/integration",
|
"test:integration": "bun test test/integration",
|
||||||
"test:all": "bun run test && bun run test:integration",
|
"test:all": "bun run test && bun run test:integration",
|
||||||
"dev": "bun --watch dev-server.js",
|
"dev": "bun --watch dev-server.js",
|
||||||
"prepublishOnly": "./make.sh"
|
"prepublishOnly": "./make.sh all"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"d2",
|
"d2",
|
||||||
|
|
@ -43,9 +45,6 @@
|
||||||
"text-to-diagram",
|
"text-to-diagram",
|
||||||
"go"
|
"go"
|
||||||
],
|
],
|
||||||
"engines": {
|
|
||||||
"bun": ">=1.0.0"
|
|
||||||
},
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun": "latest"
|
"bun": "latest"
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export class D2 {
|
||||||
|
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
this.worker.on("error", (error) => {
|
this.worker.on("error", (error) => {
|
||||||
console.error("Worker encountered an error:", error.message || error);
|
console.error("Worker (node) encountered an error:", error.message || error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.worker.onerror = (error) => {
|
this.worker.onerror = (error) => {
|
||||||
|
|
|
||||||
29
d2js/js/src/platform.browser.js
Normal file
29
d2js/js/src/platform.browser.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { wasmBinary, wasmExecJs } from "./wasm-loader.browser.js";
|
||||||
|
|
||||||
|
export async function loadFile(path) {
|
||||||
|
console.log("loading " + path);
|
||||||
|
if (path === "./d2.wasm") {
|
||||||
|
return wasmBinary.buffer;
|
||||||
|
}
|
||||||
|
if (path === "./wasm_exec.js") {
|
||||||
|
return new TextEncoder().encode(wasmExecJs).buffer;
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected file request: ${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createWorker() {
|
||||||
|
// Combine wasmExecJs with worker script
|
||||||
|
const workerResponse = await fetch(new URL("./worker.js", import.meta.url));
|
||||||
|
if (!workerResponse.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to load worker.js: ${workerResponse.status} ${workerResponse.statusText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const workerJs = await workerResponse.text();
|
||||||
|
|
||||||
|
const blob = new Blob(["(() => {", wasmExecJs, "})();", workerJs], {
|
||||||
|
type: "application/javascript",
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Worker(URL.createObjectURL(blob));
|
||||||
|
}
|
||||||
|
|
@ -1,37 +1 @@
|
||||||
export async function loadFile(path) {
|
export * from "./platform.node.js";
|
||||||
if (typeof window === "undefined") {
|
|
||||||
const fs = await import("node:fs/promises");
|
|
||||||
const { fileURLToPath } = await import("node:url");
|
|
||||||
const { join, dirname } = await import("node:path");
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await fs.readFile(join(__dirname, path));
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === "ENOENT") {
|
|
||||||
return await fs.readFile(join(__dirname, "../wasm", path.replace("./", "")));
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(new URL(path, import.meta.url));
|
|
||||||
return await response.arrayBuffer();
|
|
||||||
} catch {
|
|
||||||
const response = await fetch(
|
|
||||||
new URL(`../wasm/${path.replace("./", "")}`, import.meta.url)
|
|
||||||
);
|
|
||||||
return await response.arrayBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createWorker() {
|
|
||||||
if (typeof window === "undefined") {
|
|
||||||
const { Worker } = await import("node:worker_threads");
|
|
||||||
const { fileURLToPath } = await import("node:url");
|
|
||||||
const { join, dirname } = await import("node:path");
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
||||||
return new Worker(join(__dirname, "worker.js"));
|
|
||||||
}
|
|
||||||
return new window.Worker(new URL("./worker.js", import.meta.url));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
40
d2js/js/src/platform.node.js
Normal file
40
d2js/js/src/platform.node.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
let nodeModules = null;
|
||||||
|
|
||||||
|
async function loadNodeModules() {
|
||||||
|
if (!nodeModules) {
|
||||||
|
nodeModules = {
|
||||||
|
fs: await import("fs/promises"),
|
||||||
|
path: await import("path"),
|
||||||
|
url: await import("url"),
|
||||||
|
worker: await import("worker_threads"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return nodeModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadFile(path) {
|
||||||
|
const modules = await loadNodeModules();
|
||||||
|
const readFile = modules.fs.readFile;
|
||||||
|
const { join, dirname } = modules.path;
|
||||||
|
const { fileURLToPath } = modules.url;
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await readFile(join(__dirname, path));
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === "ENOENT") {
|
||||||
|
return await readFile(join(__dirname, "../../../wasm", path.replace("./", "")));
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createWorker() {
|
||||||
|
const modules = await loadNodeModules();
|
||||||
|
const { Worker } = modules.worker;
|
||||||
|
const { join, dirname } = modules.path;
|
||||||
|
const { fileURLToPath } = modules.url;
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const workerPath = join(__dirname, "worker.js");
|
||||||
|
return new Worker(workerPath);
|
||||||
|
}
|
||||||
8
d2js/js/src/wasm-loader.node.js
Normal file
8
d2js/js/src/wasm-loader.node.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { dirname, resolve } from "path";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
export async function getWasmBinary() {
|
||||||
|
return readFile(resolve(__dirname, "./d2.wasm"));
|
||||||
|
}
|
||||||
|
|
@ -27,8 +27,6 @@ async function handleMessage(e) {
|
||||||
try {
|
try {
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
eval(data.wasmExecContent);
|
eval(data.wasmExecContent);
|
||||||
} else {
|
|
||||||
importScripts(data.wasmExecUrl);
|
|
||||||
}
|
}
|
||||||
d2 = await initWasm(data.wasm);
|
d2 = await initWasm(data.wasm);
|
||||||
currentPort.postMessage({ type: "ready" });
|
currentPort.postMessage({ type: "ready" });
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { expect, test, describe } from "bun:test";
|
||||||
|
|
||||||
describe("D2 CJS Integration", () => {
|
describe("D2 CJS Integration", () => {
|
||||||
test("can require and use CJS build", async () => {
|
test("can require and use CJS build", async () => {
|
||||||
const { D2 } = require("../../dist/cjs/index.js");
|
const { D2 } = require("../../dist/node-cjs/index.js");
|
||||||
const d2 = new D2();
|
const d2 = new D2();
|
||||||
const result = await d2.compile("x -> y");
|
const result = await d2.compile("x -> y");
|
||||||
expect(result.diagram).toBeDefined();
|
expect(result.diagram).toBeDefined();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { expect, test, describe } from "bun:test";
|
import { expect, test, describe } from "bun:test";
|
||||||
import { D2 } from "../../dist/esm/index.js";
|
import { D2 } from "../../dist/node-esm/index.js";
|
||||||
|
|
||||||
describe("D2 ESM Integration", () => {
|
describe("D2 ESM Integration", () => {
|
||||||
test("can import and use ESM build", async () => {
|
test("can import and use ESM build", async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { expect, test, describe } from "bun:test";
|
import { expect, test, describe } from "bun:test";
|
||||||
import { D2 } from "../../src/index.js";
|
import { D2 } from "../../dist/node-esm/index.js";
|
||||||
|
|
||||||
describe("D2 Unit Tests", () => {
|
describe("D2 Unit Tests", () => {
|
||||||
test("basic compilation works", async () => {
|
test("basic compilation works", async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue