优化
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 构建管线:tsc 只做类型检查(--noEmit),esbuild 负责产出 dist/。
|
||||
*
|
||||
* 产物分三类,对应三种运行环境,不能混在同一个 bundle 里:
|
||||
* 1. 全局脚本(iife):utils / env-store / crypto-store
|
||||
* 通过 globalThis 暴露 API,被 content script 直接读全局、被 background 用
|
||||
* importScripts 加载,也必须能被 Node 测试 require,因此各自独立成文件。
|
||||
* 2. 独立入口(iife):background / content
|
||||
* 运行期自行 importScripts 或读上面的全局变量,esbuild 不会(也不能)跟踪它们。
|
||||
* 3. 模块入口(esm,可打包):popup/main
|
||||
* 由 popup.html 以 type="module" 加载,其静态依赖(i18n / strength / messaging /
|
||||
* ui-utils / crypto-file / popup/*)全部内联进 dist/popup/main.js。
|
||||
*
|
||||
* 用法:
|
||||
* node scripts/build.mjs 开发构建(带 sourcemap,不压缩)
|
||||
* node scripts/build.mjs --release 发布构建(压缩,无 sourcemap)
|
||||
* node scripts/build.mjs --watch 监听重建
|
||||
*/
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { readFile, rm } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import * as esbuild from "esbuild";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const DIST = path.join(ROOT, "dist");
|
||||
|
||||
/** 全局脚本:必须独立成文件,iife 格式,彼此通过 globalThis 通信 */
|
||||
const GLOBAL_SCRIPTS = ["utils", "env-store", "crypto-store", "key-store"];
|
||||
/** 独立入口:各自成文件,不做跨文件打包 */
|
||||
const STANDALONE_SCRIPTS = ["background", "content"];
|
||||
/** 由 background 通过 chrome.scripting 按需注入的脚本(manifest 里不再静态声明) */
|
||||
const INJECTED_ASSETS = ["dist/utils.js", "dist/content.js"];
|
||||
|
||||
function entryPoints() {
|
||||
return [
|
||||
// UMD 全局脚本:不打包、不指定 format,原样保留 module.exports 分支
|
||||
...GLOBAL_SCRIPTS.map((name) => ({ in: `src/${name}.ts`, out: `dist/${name}.js`, bundle: false })),
|
||||
// 独立入口:不打包(运行期靠 importScripts / 全局变量协作)
|
||||
...STANDALONE_SCRIPTS.map((name) => ({ in: `src/${name}.ts`, out: `dist/${name}.js`, bundle: false })),
|
||||
// popup:真正的 ESM 打包,静态依赖全部内联
|
||||
{ in: "src/popup/main.ts", out: "dist/popup/main.js", format: "esm", bundle: true },
|
||||
];
|
||||
}
|
||||
|
||||
/** 类型检查单独一步,确保类型错误不会被 esbuild 静默跳过 */
|
||||
function typecheck() {
|
||||
const tscBin = path.join(ROOT, "node_modules", "typescript", "bin", "tsc");
|
||||
execFileSync(process.execPath, [tscBin, "--noEmit"], { cwd: ROOT, stdio: "inherit" });
|
||||
}
|
||||
|
||||
function esbuildOptions({ release, format, bundle }) {
|
||||
const options = {
|
||||
bundle,
|
||||
platform: "browser",
|
||||
target: "chrome114",
|
||||
charset: "utf8",
|
||||
minify: release,
|
||||
sourcemap: release ? false : "linked",
|
||||
legalComments: "none",
|
||||
logLevel: "warning",
|
||||
};
|
||||
// 只在需要时指定 format:一旦指定 iife,esbuild 会把 UMD 的 module.exports 换成
|
||||
// 自己的合成模块对象,Node 测试 require 就取不到导出内容
|
||||
if (format) options.format = format;
|
||||
return options;
|
||||
}
|
||||
|
||||
/** manifest.json / popup.html 里引用到的文件必须真实存在,避免"构建成功但装不上" */
|
||||
async function verifyReferences() {
|
||||
const manifest = JSON.parse(await readFile(path.join(ROOT, "manifest.json"), "utf8"));
|
||||
const refs = new Set();
|
||||
|
||||
const add = (p) => { if (typeof p === "string") refs.add(p); };
|
||||
add(manifest.background?.service_worker);
|
||||
add(manifest.side_panel?.default_path);
|
||||
Object.values(manifest.icons || {}).forEach(add);
|
||||
Object.values(manifest.action?.default_icon || {}).forEach(add);
|
||||
(manifest.content_scripts || []).forEach((cs) => (cs.js || []).forEach(add));
|
||||
// manifest 不再静态声明 content_scripts,这些文件由 background 按需注入,同样必须存在
|
||||
INJECTED_ASSETS.forEach(add);
|
||||
|
||||
const html = await readFile(path.join(ROOT, "popup.html"), "utf8");
|
||||
for (const m of html.matchAll(/(?:src|href)="([^"]+)"/g)) {
|
||||
const ref = m[1];
|
||||
if (/^(https?:)?\/\//.test(ref) || ref.startsWith("#") || ref.startsWith("data:")) continue;
|
||||
refs.add(ref);
|
||||
}
|
||||
|
||||
const missing = [...refs].filter((ref) => !existsSync(path.join(ROOT, ref)));
|
||||
if (missing.length) {
|
||||
throw new Error(`以下被引用的文件不存在,构建产物不完整:\n - ${missing.join("\n - ")}`);
|
||||
}
|
||||
return [...refs];
|
||||
}
|
||||
|
||||
/** manifest.json 是版本号的唯一来源,package.json 需与之保持一致 */
|
||||
async function checkVersionConsistency() {
|
||||
const manifest = JSON.parse(await readFile(path.join(ROOT, "manifest.json"), "utf8"));
|
||||
const pkg = JSON.parse(await readFile(path.join(ROOT, "package.json"), "utf8"));
|
||||
if (manifest.version !== pkg.version) {
|
||||
console.warn(`[build] 版本号不一致:manifest.json=${manifest.version} package.json=${pkg.version}(执行 npm run release 可同步)`);
|
||||
}
|
||||
return manifest.version;
|
||||
}
|
||||
|
||||
export async function buildAll({ release = false, watch = false } = {}) {
|
||||
typecheck();
|
||||
const version = await checkVersionConsistency();
|
||||
|
||||
if (watch) {
|
||||
const contexts = await Promise.all(
|
||||
entryPoints().map((ep) => esbuild.context({ ...esbuildOptions({ release, format: ep.format, bundle: ep.bundle }), entryPoints: [ep.in], outfile: ep.out }))
|
||||
);
|
||||
await Promise.all(contexts.map((c) => c.watch()));
|
||||
console.log(`[build] 监听中… v${version}(Ctrl+C 退出)`);
|
||||
return { version, watching: true };
|
||||
}
|
||||
|
||||
await rm(DIST, { recursive: true, force: true }); // 清掉历史产物,避免残留旧文件被打进包
|
||||
for (const ep of entryPoints()) {
|
||||
await esbuild.build({ ...esbuildOptions({ release, format: ep.format, bundle: ep.bundle }), entryPoints: [ep.in], outfile: ep.out });
|
||||
}
|
||||
|
||||
const refs = await verifyReferences();
|
||||
console.log(`[build] v${version} ${release ? "release" : "dev"} 构建完成:${entryPoints().length} 个入口,校验 ${refs.length} 处引用`);
|
||||
return { version, refs };
|
||||
}
|
||||
|
||||
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
||||
if (isDirectRun) {
|
||||
const args = process.argv.slice(2);
|
||||
await buildAll({ release: args.includes("--release"), watch: args.includes("--watch") });
|
||||
}
|
||||
Reference in New Issue
Block a user