117 lines
5.8 KiB
TypeScript
117 lines
5.8 KiB
TypeScript
import { fileURLToPath, URL } from "node:url";
|
|
import { readFile } from "node:fs/promises";
|
|
import { createRequire } from "node:module";
|
|
import { build as buildStandaloneScript } from "esbuild";
|
|
import { defaultClientConditions, defineConfig } from "vite";
|
|
import react from "@vitejs/plugin-react";
|
|
import tailwindcss from "@tailwindcss/vite";
|
|
import extensionPackage from "./package.json";
|
|
import { buildExtensionManifest, type ExtensionTarget } from "./manifest";
|
|
|
|
const target: ExtensionTarget = process.env.EDGE_EVER_EXTENSION_TARGET === "firefox"
|
|
? "firefox"
|
|
: "chromium";
|
|
const require = createRequire(import.meta.url);
|
|
|
|
export default defineConfig({
|
|
root: fileURLToPath(new URL(".", import.meta.url)),
|
|
resolve: { conditions: ["worker", ...defaultClientConditions], alias: { "@": fileURLToPath(new URL("../web/src", import.meta.url)) }, dedupe: ["react", "react-dom"] },
|
|
plugins: [
|
|
react(),
|
|
tailwindcss(),
|
|
{
|
|
name: "edgeever-extension-manifest",
|
|
async generateBundle(_options, bundle) {
|
|
this.emitFile({
|
|
type: "asset",
|
|
fileName: "manifest.json",
|
|
source: `${JSON.stringify(buildExtensionManifest(target, extensionPackage.version), null, 2)}\n`,
|
|
});
|
|
|
|
// chrome.scripting.executeScript({ files }) loads classic scripts, so
|
|
// capture must not inherit Vite's shared ESM chunks.
|
|
for (const name of ["capture", "capture-platform", "social-target"]) {
|
|
const capture = await buildStandaloneScript({
|
|
entryPoints: [fileURLToPath(new URL(`./src/${name}.ts`, import.meta.url))],
|
|
outfile: `${name}.js`,
|
|
bundle: true,
|
|
format: "iife",
|
|
platform: "browser",
|
|
target: "es2022",
|
|
minify: true,
|
|
write: false,
|
|
});
|
|
this.emitFile({
|
|
type: "asset",
|
|
fileName: `assets/${name}.js`,
|
|
source: capture.outputFiles[0].contents,
|
|
});
|
|
}
|
|
// Reuse the generated Tailwind stylesheet so the shadcn component and
|
|
// its theme tokens behave identically in pages and in the shadow root.
|
|
const themeStyles = Object.values(bundle)
|
|
.filter((item) => item.type === "asset" && item.fileName.endsWith(".css"))
|
|
.map((item) => item.type === "asset" ? typeof item.source === "string" ? item.source : new TextDecoder().decode(item.source) : "")
|
|
.join("\n");
|
|
if (!themeStyles) throw new Error("Missing compiled extension theme for shadow notifications");
|
|
const feedback = await buildStandaloneScript({
|
|
entryPoints: [fileURLToPath(new URL("./src/ui/clip-feedback.tsx", import.meta.url))],
|
|
bundle: true, format: "iife", platform: "browser", target: "es2022", minify: true, write: false,
|
|
jsx: "automatic",
|
|
define: {
|
|
"process.env.NODE_ENV": '"production"',
|
|
__CLIP_FEEDBACK_CSS__: JSON.stringify(`${await readFile(require.resolve("sonner/dist/styles.css"), "utf8")}\n${themeStyles}`),
|
|
},
|
|
plugins: [{
|
|
name: "shadow-only-sonner-styles",
|
|
setup(build) {
|
|
build.onLoad({ filter: /sonner[\\/]dist[\\/]index\.(mjs|js)$/ }, async ({ path }) => {
|
|
// Sonner's default module injects CSS into document.head. The
|
|
// same published stylesheet is instead mounted in our shadow.
|
|
const source = await readFile(path, "utf8");
|
|
const contents = source.replace(/__insertCSS\("(?:\\.|[^"\\])*"\);/g, "");
|
|
if (contents === source) throw new Error("Sonner CSS injection changed; review shadow isolation before upgrading");
|
|
return { contents, loader: "js" };
|
|
});
|
|
},
|
|
}],
|
|
});
|
|
this.emitFile({ type: "asset", fileName: "assets/clip-feedback.js", source: feedback.outputFiles[0].contents });
|
|
},
|
|
},
|
|
],
|
|
build: {
|
|
outDir: target === "firefox" ? "dist-firefox" : "dist",
|
|
emptyOutDir: true,
|
|
// Chromium cannot reuse these extension-page preloads across worlds.
|
|
// Load the local chunks through their regular module imports instead.
|
|
modulePreload: false,
|
|
rollupOptions: {
|
|
input: {
|
|
popup: fileURLToPath(new URL("./popup.html", import.meta.url)),
|
|
options: fileURLToPath(new URL("./options.html", import.meta.url)),
|
|
"image-save": fileURLToPath(new URL("./image-save.html", import.meta.url)),
|
|
"tweet-save": fileURLToPath(new URL("./tweet-save.html", import.meta.url)),
|
|
"xhs-save": fileURLToPath(new URL("./xhs-save.html", import.meta.url)),
|
|
"zhihu-save": fileURLToPath(new URL("./zhihu-save.html", import.meta.url)),
|
|
background: fileURLToPath(new URL("./src/background.ts", import.meta.url)),
|
|
"capture-image": fileURLToPath(new URL("./src/capture-image.ts", import.meta.url)),
|
|
"capture-tweet": fileURLToPath(new URL("./src/capture-tweet.ts", import.meta.url)),
|
|
"capture-github": fileURLToPath(new URL("./src/capture-github.ts", import.meta.url)),
|
|
"capture-xhs": fileURLToPath(new URL("./src/capture-xhs.ts", import.meta.url)),
|
|
"capture-zhihu": fileURLToPath(new URL("./src/capture-zhihu.ts", import.meta.url)),
|
|
"capture-reddit": fileURLToPath(new URL("./src/capture-reddit.ts", import.meta.url)),
|
|
"tweet-target": fileURLToPath(new URL("./src/tweet-target.ts", import.meta.url)),
|
|
"xhs-target": fileURLToPath(new URL("./src/xhs-target.ts", import.meta.url)),
|
|
"zhihu-target": fileURLToPath(new URL("./src/zhihu-target.ts", import.meta.url)),
|
|
"reddit-target": fileURLToPath(new URL("./src/reddit-target.ts", import.meta.url)),
|
|
},
|
|
output: {
|
|
entryFileNames: "assets/[name].js",
|
|
chunkFileNames: "assets/[name]-[hash].js",
|
|
assetFileNames: "assets/[name]-[hash][extname]",
|
|
},
|
|
},
|
|
},
|
|
});
|