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>
This commit is contained in:
@@ -1,17 +1,33 @@
|
||||
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 buildOptions = {
|
||||
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,
|
||||
outdir: "dist",
|
||||
format: "esm",
|
||||
target: "chrome120",
|
||||
sourcemap: true,
|
||||
minify: !isWatch,
|
||||
// Force CJS resolution for libsodium (ESM entry has broken sibling import)
|
||||
@@ -21,11 +37,65 @@ const buildOptions = {
|
||||
},
|
||||
};
|
||||
|
||||
if (isWatch) {
|
||||
const ctx = await esbuild.context(buildOptions);
|
||||
await ctx.watch();
|
||||
console.log("Watching for changes...");
|
||||
} else {
|
||||
await esbuild.build(buildOptions);
|
||||
console.log("Build complete.");
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user