theseus/addon-build/docx-editor/build.mjs
Local Dev 5e4bd22609 refactor(docx-editor): ship it as a community extension, not part of the browser
A .docx editor is a megabyte of vendored library. Bundling it would charge
that to everyone who wanted a browser, including the people who will never
open a Word document in it.

So it leaves the build: out of bundled-addons/, out of extraResources, absent
from a fresh profile. It arrives the way anyone else's extension does —
Settings › Extensions › Community, from the catalogue the gateway builds, and
listed on theseus.x/extensions alongside everything else published there.
That also means it is signed by the owner of a BNS name rather than by the
operator key, which is the right trust story for something that isn't part of
the browser.

`npm run pack` produces the tarball the publish page takes; the signature
needs the publisher name's wallet, so it isn't something the repo can do.

The end-to-end test now installs the extension into a throwaway profile the
way the community installer would, and asserts up front that a fresh profile
doesn't already have it — the bundling is what was being removed, so it is
worth a test that would notice it coming back.
2026-09-21 01:06:58 +02:00

77 lines
3.7 KiB
JavaScript

// Bundles the extension's npm dependencies into
// extensions/docx-editor/vendor/docx-vendor.js.
//
// npm run build (from addon-build/docx-editor/)
//
// mammoth is patched on the way through — see patches.mjs for why. The
// patched copy is materialised under .patched/ so the node round-trip tests
// exercise exactly the same reader the browser does.
import * as esbuild from "esbuild";
import { mkdirSync, writeFileSync, readFileSync, statSync, rmSync, cpSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { applyPatches, patches } from "./patches.mjs";
const here = path.dirname(fileURLToPath(import.meta.url));
const out = path.resolve(here, "../../extensions/docx-editor/vendor");
const patchedRoot = path.join(here, ".patched");
// --- 1. patched mammoth ---------------------------------------------------
rmSync(patchedRoot, { recursive: true, force: true });
mkdirSync(patchedRoot, { recursive: true });
cpSync(path.join(here, "node_modules", "mammoth"), path.join(patchedRoot, "mammoth"), { recursive: true });
const touched = new Set();
for (const p of patches) {
const f = path.join(patchedRoot, "mammoth", p.file);
if (touched.has(f)) continue;
touched.add(f);
}
for (const f of touched) {
const rel = "mammoth/" + path.relative(path.join(patchedRoot, "mammoth"), f).split(path.sep).join("/");
const { code, hits } = applyPatches(rel, readFileSync(f, "utf8"));
writeFileSync(f, code);
console.log(`patched ${rel} (${hits} hunks)`);
}
// --- 2. bundle ------------------------------------------------------------
mkdirSync(out, { recursive: true });
const res = await esbuild.build({
entryPoints: [path.join(here, "vendor-entry.js")],
bundle: true,
minify: true,
format: "iife",
platform: "browser",
target: ["chrome120"],
legalComments: "none",
outfile: path.join(out, "docx-vendor.js"),
define: { "process.env.NODE_ENV": '"production"' },
alias: { mammoth: path.join(patchedRoot, "mammoth") },
logLevel: "info",
});
if (res.errors.length) process.exit(1);
// --- 3. licences ----------------------------------------------------------
// Vendored code without its licence text is the kind of thing that bites
// later; this file ships next to the bundle.
const pkgs = ["mammoth", "docx", "jszip", "underscore", "orderedmap", "w3c-keyname",
"rope-sequence", "prosemirror-state", "prosemirror-view", "prosemirror-model",
"prosemirror-schema-basic", "prosemirror-schema-list", "prosemirror-tables",
"prosemirror-history", "prosemirror-commands", "prosemirror-keymap",
"prosemirror-inputrules", "prosemirror-dropcursor", "prosemirror-gapcursor",
"prosemirror-transform"];
let notice = "Third-party code bundled into vendor/docx-vendor.js\n" +
"===================================================\n\n" +
"mammoth is shipped with small local patches (colour, paragraph spacing,\n" +
"numbering format); see addon-build/docx-editor/patches.mjs.\n\n";
for (const p of pkgs) {
let j; try { j = JSON.parse(readFileSync(path.join(here, "node_modules", p, "package.json"), "utf8")); }
catch { continue; }
let text = "";
for (const f of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "license", "LICENSE-MIT"]) {
try { text = readFileSync(path.join(here, "node_modules", p, f), "utf8").trim(); break; } catch {}
}
notice += `--- ${p} ${j.version}${j.license || "see project"} ---\n` +
(text || `(no licence file in the package; see ${j.homepage || j.repository?.url || "the project homepage"})`) + "\n\n";
}
writeFileSync(path.join(out, "LICENSES.txt"), notice);
console.log(`vendor bundle: ${(statSync(path.join(out, "docx-vendor.js")).size / 1024).toFixed(0)} KB`);