Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions d2js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.npmrc
51 changes: 46 additions & 5 deletions d2js/js/build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { build } from "bun";
import { copyFile, mkdir, writeFile, readFile, rm } from "node:fs/promises";
import { join, resolve } from "node:path";
import { brotliCompressSync } from "node:zlib";

const __dirname = new URL(".", import.meta.url).pathname;
const ROOT_DIR = resolve(__dirname);
Expand All @@ -14,11 +15,28 @@ await mkdir("./dist/node-cjs", { recursive: true });
const wasmBinary = await readFile("./wasm/d2.wasm");
const wasmExecJs = await readFile("./wasm/wasm_exec.js", "utf8");

const compressedWasm = brotliCompressSync(wasmBinary);
console.log(
`WASM compression: ${(wasmBinary.length / 1024 / 1024).toFixed(2)}MB → ${(
compressedWasm.length /
1024 /
1024
).toFixed(2)}MB`
);

// Store compressed WASM as base64 and include brotli decoder in the loader
// Don't decompress immediately - let the consumer decompress when needed
const brotliDecoder = await readFile("./vendor/decode.min.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));
`${brotliDecoder}

export const wasmBinaryCompressed = "${Buffer.from(compressedWasm).toString("base64")}";
export function getWasmBinary() {
const compressedBytes = Uint8Array.from(atob(wasmBinaryCompressed), c => c.charCodeAt(0));
return BrotliDecode(compressedBytes);
}
export const wasmExecJs = ${JSON.stringify(wasmExecJs)};`
);

Expand Down Expand Up @@ -48,9 +66,32 @@ async function buildDynamicFiles(platform) {
resolve(ROOT_DIR, "../../d2layouts/d2elklayout/setup.js"),
"utf8"
);

// Compress elk.js and setup.js
const elkJsCompressed = brotliCompressSync(new TextEncoder().encode(elkJs));
const setupJsCompressed = brotliCompressSync(new TextEncoder().encode(setupJs));

console.log(
`ELK compression: ${(elkJs.length / 1024 / 1024).toFixed(2)}MB → ${(
elkJsCompressed.length /
1024 /
1024
).toFixed(2)}MB`
);

const workerBase = await readFile(join(SRC_DIR, "worker.browser.js"), "utf8");
const elkVars = `const elkJs = ${JSON.stringify(elkJs)};
const setupJs = ${JSON.stringify(setupJs)};

// Bundle brotli decoder directly into the worker
const brotliDecoder = await readFile(
resolve(ROOT_DIR, "vendor/decode.min.js"),
"utf8"
);

const elkVars = `${brotliDecoder}
const elkJsCompressed = "${Buffer.from(elkJsCompressed).toString("base64")}";
const setupJsCompressed = "${Buffer.from(setupJsCompressed).toString("base64")}";
const elkJs = new TextDecoder().decode(BrotliDecode(Uint8Array.from(atob(elkJsCompressed), c => c.charCodeAt(0))));
const setupJs = new TextDecoder().decode(BrotliDecode(Uint8Array.from(atob(setupJsCompressed), c => c.charCodeAt(0))));
`;
await writeFile(join(SRC_DIR, "worker.js"), elkVars + workerBase);
}
Expand Down
8 changes: 6 additions & 2 deletions d2js/js/ci/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ set -eu
cd -- "$(dirname "$0")/.."

cd ../..
JS_VERSION=$(awk -F'"' '/"version"/ {print $4}' ./d2js/js/package.json)
sh_c "GOOS=js GOARCH=wasm go build -ldflags='-s -w -X oss.terrastruct.com/d2/d2js/d2wasm.jsVersion=${JS_VERSION}' -gcflags='-l=4' -trimpath -o main.wasm ./d2js"
if [ -n "${NPM_VERSION:-}" ]; then
JS_VERSION="$NPM_VERSION"
else
JS_VERSION=$(awk -F'"' '/"version"/ {print $4}' ./d2js/js/package.json)
fi
sh_c "GOOS=js GOARCH=wasm go build -ldflags='-s -w -X oss.terrastruct.com/d2/d2js/d2wasm.jsVersion=${JS_VERSION}' -trimpath -o main.wasm ./d2js"

if [ -n "${NPM_VERSION:-}" ]; then
# Optimize with wasm-opt if available
Expand Down
212 changes: 212 additions & 0 deletions d2js/js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions d2js/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@terrastruct/d2",
"author": "Terrastruct, Inc.",
"description": "D2.js is a wrapper around the WASM build of D2, the modern text-to-diagram language.",
"version": "0.1.28",
"version": "0.1.31",
"repository": {
"type": "git",
"url": "git+https://github.com/terrastruct/d2.git",
Expand Down Expand Up @@ -57,5 +57,6 @@
"license": "MPL-2.0",
"devDependencies": {
"bun": "latest"
}
},
"dependencies": {}
}
4 changes: 2 additions & 2 deletions d2js/js/src/platform.browser.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { wasmBinary, wasmExecJs } from "./wasm-loader.browser.js";
import { getWasmBinary, wasmExecJs } from "./wasm-loader.browser.js";
import workerScript from "./worker.js" with { type: "text" };

// For the browser version, we build the wasm files into a file (wasm-loader.browser.js)
// and loading a file just reads the text, so there's no external dependency calls
export async function loadFile(path) {
if (path === "./d2.wasm") {
return wasmBinary.buffer;
return getWasmBinary();
}
if (path === "./wasm_exec.js") {
return new TextEncoder().encode(wasmExecJs).buffer;
Expand Down
Loading