perf: load document libraries only when needed

This commit is contained in:
tdurieux
2026-09-09 13:28:44 +02:00
parent 984906a89a
commit e68ab037eb
15 changed files with 151 additions and 53 deletions
+7 -4
View File
@@ -47,15 +47,18 @@ describe("asset build", function () {
it("preserves script dependencies and CSS precedence and hashes completed assets", function () {
const result = build();
expect(result.status, result.stderr).to.equal(0);
for (const [bundle, group] of [["core", "coreJsFiles"], ["vendor", "vendorJsFiles"], ["mermaid", "mermaidFiles"]]) {
for (const [bundle, group] of [["core", "coreJsFiles"], ["markdown", "markdownFiles"], ["pdf", "pdfFiles"], ["editor", "editorFiles"], ["notebook", "notebookFiles"], ["org", "orgFiles"], ["mermaid", "mermaidFiles"]]) {
const context = { assetOrder: [] };
vm.runInNewContext(fs.readFileSync(path.join(directory, `public/script/${bundle}.min.js`), "utf8"), context);
expect(context.assetOrder).to.deep.equal(groups[group]);
if (bundle === "vendor") expect(context.appLoaded).to.equal(true);
}
expect(fs.readFileSync(path.join(directory, "public/css/all.min.css"), "utf8")).to.equal(".cascade{color:#00f}".repeat(groups.cssFiles.length - 1) + ".cascade{color:red}");
const manifest = JSON.parse(fs.readFileSync(path.join(directory, "public/asset-manifest.json"), "utf8"));
expect(Object.keys(manifest)).to.have.length(4);
expect(Object.keys(manifest)).to.have.length(9);
const appContext = {};
vm.runInNewContext(fs.readFileSync(path.join(directory, "public/script/vendor.min.js"), "utf8"), appContext);
expect(appContext.appLoaded).to.equal(true);
for (const [name, hashed] of Object.entries(manifest)) {
const content = fs.readFileSync(path.join(directory, "public", name.endsWith(".css") ? "css" : "script", name));
const hash = require("node:crypto").createHash("md5").update(content).digest("hex").slice(0, 10);
@@ -72,7 +75,7 @@ describe("asset build", function () {
});
it("fails on invalid JavaScript without publishing a manifest", function () {
fs.writeFileSync(path.join(directory, groups.vendorJsFiles[0]), "function {");
fs.writeFileSync(path.join(directory, groups.pdfFiles[0]), "function {");
const result = build();
expect(result.status).not.to.equal(0);
expect(result.stderr).to.include("uglify");
+28 -4
View File
@@ -1,5 +1,5 @@
const { expect } = require("chai");
const { JSDOM, VirtualConsole } = require("jsdom");
const { JSDOM, VirtualConsole, ResourceLoader } = require("jsdom");
const fs = require("fs");
const path = require("path");
const { URL } = require("node:url");
@@ -9,13 +9,22 @@ const publicDir = path.join(__dirname, "../public");
const bundles = ["core.min.js", "vendor.min.js"].map(name => fs.readFileSync(path.join(publicDir, "script", name), "utf8"));
async function browser(route = "/", overrides = {}) {
const errors = [], requests = [];
const errors = [], requests = [], assets = [];
const virtualConsole = new VirtualConsole();
virtualConsole.on("jsdomError", error => {
if (!error.message.includes("navigation (except hash changes)")) errors.push(error.message);
});
virtualConsole.on("error", error => errors.push(error?.message || String(error)));
const dom = new JSDOM('<!doctype html><html><head></head><body><div id="app"></div></body></html>', {
resources: new class extends ResourceLoader {
fetch(url) {
assets.push(new URL(url).pathname);
if (/\/pdf\.[a-f0-9]+\.min\.js$/.test(new URL(url).pathname) && dom.window.pdfjsLib) return Promise.resolve(Buffer.from(""));
const pathname = new URL(url).pathname.replace(/\.[a-f0-9]{10}\.min\.js$/, ".min.js");
if (pathname.startsWith("/script/")) return Promise.resolve(fs.readFileSync(path.join(publicDir, pathname)));
return null;
}
}(),
url: "http://localhost" + route, runScripts: "dangerously", pretendToBeVisual: true, virtualConsole,
});
const window = dom.window;
@@ -46,7 +55,7 @@ async function browser(route = "/", overrides = {}) {
await app.router.isReady();
await delay(30);
return {
window, app, errors, requests,
window, app, errors, requests, assets,
async go(path) { await app.router.push(path); await delay(30); },
async input(selector, value, event = "input") {
const input = window.document.querySelector(selector);
@@ -79,6 +88,21 @@ describe("Vue 3 UI", function () {
expect(ui.errors).to.deep.equal([]);
});
it("loads document libraries on demand and reuses them across navigation", async function () {
ui = await browser("/dashboard");
expect(ui.assets.filter(url => url.endsWith(".js"))).to.deep.equal([]);
await ui.go("/r/test/README.md");
expect(ui.assets.some(url => /\/markdown\./.test(url))).to.equal(true);
expect(ui.assets.some(url => /\/(pdf|editor|notebook)\./.test(url))).to.equal(false);
await ui.go("/faq");
await ui.go("/r/test/README.md");
expect(ui.assets.filter(url => /\/markdown\./.test(url))).to.have.length(1);
await ui.go("/r/test/hello.js");
expect(ui.assets.some(url => /\/editor\./.test(url))).to.equal(true);
expect(ui.window.document.querySelector(".ace_editor")).not.to.equal(null);
expect(ui.errors).to.deep.equal([]);
});
it("validates a claim, binds input values and renders server validation failures", async function () {
ui = await browser("/claim", { "/api/repo/claim": { __status: 404, body: {} } });
const form = ui.window.document.querySelector("form");
@@ -231,7 +255,7 @@ describe("Vue 3 UI", function () {
it("loads PDF pages, changes documents and releases the previous document", async function () {
ui = await browser("/faq");
const loaded = [], destroyed = [];
ui.window.pdfjsLib = { getDocument({ url }) {
ui.window.pdfjsLib = { GlobalWorkerOptions: {}, getDocument({ url }) {
loaded.push(url);
return { promise: Promise.resolve({
numPages: 2,