perf(plugins): minify client bundles while preserving interop and notices (#8076)

Reduce plugin client payloads with whitespace and syntax minification after eager host-export interop. Preserve identifiers, lowered async behavior and dependency license notices; leave server bundles readable.

Verify worker-produced code through registration, byte-reduction, immutable-export and retained-notice regressions. Carries forward #7625 on an upstream branch.

Pin Playwright-installing CI jobs to Ubuntu 24.04 after the ubuntu-latest rollout selected an unsupported browser-install platform.

Follow-up payload review motivated by Matt Cowger's plugin-loading discussion: https://discord.com/channels/1481169421832814616/1563217536269680744

Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
This commit is contained in:
Mohamed Boudra
2026-10-28 09:50:29 +07:00
committed by GitHub
co-authored by luke RoundRedDot
parent 2c23cd4927
commit abc23d7721
3 changed files with 127 additions and 11 deletions
+11 -10
View File
@@ -133,11 +133,12 @@ jobs:
if: ${{ !cancelled() && steps.install.outcome == 'success' && (needs.changes.outputs.full != 'false' || needs.changes.outputs.sdk != 'false') }}
run: npm run typecheck:examples --workspace=@getpaseo/client
# The locked Playwright installers do not support ubuntu-latest's Ubuntu 26.04.
typecheck:
name: typecheck
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.quality != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
@@ -395,7 +396,7 @@ jobs:
name: app-tests
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.app != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
@@ -429,7 +430,7 @@ jobs:
name: playwright (shard 1/8)
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PLAYWRIGHT_SHARD: "1/8"
PLAYWRIGHT_ARTIFACT: "1"
@@ -485,7 +486,7 @@ jobs:
name: playwright (shard 2/8)
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PLAYWRIGHT_SHARD: "2/8"
PLAYWRIGHT_ARTIFACT: "2"
@@ -495,7 +496,7 @@ jobs:
name: playwright (shard 3/8)
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PLAYWRIGHT_SHARD: "3/8"
PLAYWRIGHT_ARTIFACT: "3"
@@ -505,7 +506,7 @@ jobs:
name: playwright (shard 4/8)
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PLAYWRIGHT_SHARD: "4/8"
PLAYWRIGHT_ARTIFACT: "4"
@@ -515,7 +516,7 @@ jobs:
name: playwright (shard 5/8)
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PLAYWRIGHT_SHARD: "5/8"
PLAYWRIGHT_ARTIFACT: "5"
@@ -525,7 +526,7 @@ jobs:
name: playwright (shard 6/8)
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PLAYWRIGHT_SHARD: "6/8"
PLAYWRIGHT_ARTIFACT: "6"
@@ -535,7 +536,7 @@ jobs:
name: playwright (shard 7/8)
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PLAYWRIGHT_SHARD: "7/8"
PLAYWRIGHT_ARTIFACT: "7"
@@ -545,7 +546,7 @@ jobs:
name: playwright (shard 8/8)
needs: changes
if: ${{ !cancelled() && (needs.changes.outputs.full != 'false' || needs.changes.outputs.browser != 'false') }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PLAYWRIGHT_SHARD: "8/8"
PLAYWRIGHT_ARTIFACT: "8"
@@ -257,6 +257,22 @@ function makeHermesInteropEager(code: string): string {
return code.replaceAll("get: () => from[key]", "value: from[key]");
}
async function minifyClientBundle(code: string): Promise<string> {
const { transform } = loadEsbuild();
// Minify after the eager replace. Whitespace minify rewrites the getter this
// replace matches, and identifier minify can delete the helper entirely.
const result = await transform(code, {
minifyWhitespace: true,
minifySyntax: true,
minifyIdentifiers: false,
target: "es2020",
supported: { "async-await": false },
charset: "ascii",
legalComments: "inline",
});
return result.code;
}
function runtimeSpecifierError(
specifier: string,
target: PluginBuildTarget | "shared",
@@ -368,7 +384,10 @@ async function compileTarget(entryPath: string, target: PluginBuildTarget): Prom
checkSharedDependencies(result.metafile.inputs, pluginDirectory);
const output = result.outputFiles[0]?.text;
if (!output) throw new Error(`Plugin ${target} compilation produced no output`);
return wrapCommonJsBundle(makeHermesInteropEager(output));
const eager = makeHermesInteropEager(output);
let prepared = eager;
if (target === "client") prepared = await minifyClientBundle(eager);
return wrapCommonJsBundle(prepared);
}
export async function compilePlugin(entryPaths: {
@@ -3,6 +3,7 @@ import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import * as zod from "zod";
import { ZodError } from "zod";
import {
compilePlugin,
@@ -569,6 +570,101 @@ export default function contribute(client) {
expect(clientBundle).toContain("__async");
});
it("reduces client bytes while preserving registration and leaving server output readable", async () => {
const entries = await createSplitPlugin();
const source = `import { z } from "zod";
const commandSchema = z.object({ id: z.string(), title: z.string() });
export default function contribute(client) {
const command = commandSchema.parse({ id: "open", title: "Open – plugin" });
client.addCommandCenterItem({
...command,
icon: "Scan",
context: "global",
onSelect() { return Promise.resolve(command.title); },
});
return () => undefined;
}`;
await Promise.all([writeFile(entries.client, source), writeFile(entries.server, source)]);
const { clientBundle, serverBundle } = await compilePlugin(entries);
if (clientBundle === null || serverBundle === null) {
throw new Error("compilePlugin returned no bundle");
}
for (const bundle of [clientBundle, serverBundle]) {
const registered: { id: string; title: string; onSelect: () => Promise<string> }[] = [];
const exports = new Function(`return ${bundle}`)()((specifier: string) => {
expect(specifier).toBe("zod");
return zod;
});
const cleanup = exports.default({
addCommandCenterItem: (command: (typeof registered)[number]) => registered.push(command),
});
expect(registered).toMatchObject([{ id: "open", title: "Open – plugin" }]);
await expect(registered[0]!.onSelect()).resolves.toBe("Open – plugin");
expect(cleanup()).toBeUndefined();
}
expect(Buffer.byteLength(clientBundle)).toBeLessThan(Buffer.byteLength(serverBundle) * 0.9);
expect(clientBundle.split("").every((char) => char.charCodeAt(0) <= 0x7f)).toBe(true);
expect(serverBundle).toContain('title: "Open');
});
it("retains dependency license notices while minifying executable client code", async () => {
const entries = await createSplitPlugin();
const dependency = path.join(entries.directory, "node_modules/licensed-dependency");
const notice = "Copyright Example Authors. SPDX-License-Identifier: MIT";
await mkdir(dependency, { recursive: true });
await writeFile(
path.join(dependency, "package.json"),
JSON.stringify({ name: "licensed-dependency", main: "index.js" }),
);
await writeFile(
path.join(dependency, "index.js"),
`/*! ${notice} */
exports.title = function title(input) {
const trimmed = input.trim();
return "Licensed " + trimmed;
};`,
);
const source = `import { title } from "licensed-dependency";
export default function contribute() { return title(" plugin "); }`;
await Promise.all([writeFile(entries.client, source), writeFile(entries.server, source)]);
const { clientBundle, serverBundle } = await compilePlugin(entries);
if (clientBundle === null || serverBundle === null) {
throw new Error("compilePlugin returned no bundle");
}
for (const bundle of [clientBundle, serverBundle]) {
expect(bundle).toContain(notice);
const exports = new Function(`return ${bundle}`)()(() => {
throw new Error("The dependency must be bundled");
});
expect(exports.default()).toBe("Licensed plugin");
}
expect(Buffer.byteLength(clientBundle)).toBeLessThan(Buffer.byteLength(serverBundle) * 0.9);
});
it("keeps distinct host exports eager when minifying in the worker", async () => {
const entries = await createSplitPlugin();
await writeFile(
entries.client,
`import * as host from "zod";
export default function contribute() { return [host.first, host.second]; }`,
);
const { clientBundle } = await compilePlugin({ client: entries.client, server: null });
const host = { first: "first export", second: "second export" };
const factory = new Function(`return ${clientBundle}`)() as (
require: (specifier: string) => typeof host,
) => { default: () => unknown };
function requireHost(specifier: string): typeof host {
expect(specifier).toBe("zod");
return host;
}
const exports = factory(requireHost);
host.first = "changed first export";
host.second = "changed second export";
expect(exports.default()).toEqual(["first export", "second export"]);
});
it("rejects node imports from the client entry", async () => {
const entries = await createSplitPlugin();
await writeFile(