Files
CookieBridge/extension/esbuild.config.mjs
徐枫 f39ff8c215 feat: implement M3 multi-browser support (Firefox, Edge, Safari)
Add browser abstraction layer (compat.ts) that normalizes Chrome/Firefox/
Edge/Safari extension APIs behind a unified promise-based interface.
Replace all direct chrome.* calls with compat layer across service worker,
sync engine, badge, storage, popup, and options modules.

- Browser-specific manifests in manifests/ (Firefox MV3 with gecko settings,
  Edge/Safari variants)
- Multi-target build system: `npm run build` produces all four browser
  builds in build/<browser>/
- Per-browser build scripts: build:chrome, build:firefox, build:edge, build:safari
- Auto-detects browser at runtime for platform-specific device registration
- All 38 existing tests pass, typecheck clean

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-17 16:58:44 +08:00

102 lines
2.7 KiB
JavaScript

import * as esbuild from "esbuild";
import { cpSync, mkdirSync, existsSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const isWatch = process.argv.includes("--watch");
const targetBrowser = process.argv.find((a) => a.startsWith("--browser="))?.split("=")[1] || "all";
const browsers = targetBrowser === "all"
? ["chrome", "firefox", "edge", "safari"]
: [targetBrowser];
// Browser-specific esbuild targets
const browserTargets = {
chrome: "chrome120",
edge: "chrome120", // Edge is Chromium-based
firefox: "firefox109",
safari: "safari16",
};
const sharedBuildOptions = {
entryPoints: [
"src/background/service-worker.ts",
"src/popup/popup.ts",
"src/options/options.ts",
],
bundle: true,
format: "esm",
sourcemap: true,
minify: !isWatch,
// Force CJS resolution for libsodium (ESM entry has broken sibling import)
alias: {
"libsodium-wrappers-sumo":
"./node_modules/libsodium-wrappers-sumo/dist/modules-sumo/libsodium-wrappers.js",
},
};
/**
* Copy static assets (HTML, icons, manifest) to a browser-specific build directory.
*/
function copyStaticAssets(browser) {
const outDir = join(__dirname, "build", browser);
mkdirSync(outDir, { recursive: true });
// Copy manifest
const manifestSrc = join(__dirname, "manifests", `${browser}.json`);
if (existsSync(manifestSrc)) {
cpSync(manifestSrc, join(outDir, "manifest.json"));
}
// Copy src (HTML, icons)
const srcDir = join(__dirname, "src");
cpSync(srcDir, join(outDir, "src"), { recursive: true, filter: (src) => !src.endsWith(".ts") });
}
async function buildBrowser(browser) {
const outDir = join("build", browser, "dist");
const options = {
...sharedBuildOptions,
outdir: outDir,
target: browserTargets[browser] || "es2020",
};
if (isWatch && browsers.length === 1) {
const ctx = await esbuild.context(options);
await ctx.watch();
console.log(`Watching for changes (${browser})...`);
} else {
await esbuild.build(options);
}
copyStaticAssets(browser);
console.log(`Build complete: ${browser} → build/${browser}/`);
}
// Also build the default dist/ for backwards compatibility (Chrome)
async function buildDefault() {
const options = {
...sharedBuildOptions,
outdir: "dist",
target: "chrome120",
};
if (isWatch) {
const ctx = await esbuild.context(options);
await ctx.watch();
console.log("Watching for changes (default)...");
} else {
await esbuild.build(options);
console.log("Build complete (default dist/).");
}
}
// Build all targets
await buildDefault();
for (const browser of browsers) {
await buildBrowser(browser);
}