Refactor source structure and update build process

Moved source files into a structured src/ directory for better organization. Updated build.py to copy and flatten files from src/ during the build process for Chrome and Firefox, introducing a new flatten_for_browser function. Improved .gitignore to exclude additional build artifacts. Also reformatted and improved error handling in webext-compat.js.
This commit is contained in:
xixu-me committed 2025-11-09 18:30:25 +08:00
1 parent 00ee7e6592
commit 7dee5da5ed
10 files changed
+181 -81

No files matched your search

+2
View File
@@ -7,7 +7,9 @@ yarn-error.log*
# Build outputs # Build outputs
dist/ dist/
build/ build/
packages/
*.zip *.zip
*.xpi
extension.zip extension.zip
# OS generated files # OS generated files
+80 -12
View File
@@ -43,20 +43,22 @@ def copy_common_files(build_dir):
Args: Args:
build_dir (Path): 目标构建目录 build_dir (Path): 目标构建目录
""" """
common_files = [ # 创建 src 目录结构
"webext-compat.js", src_dir = build_dir / "src"
"background.js",
"content.js", # 复制源代码目录
"popup.js", if os.path.exists("src"):
"popup.html", shutil.copytree("src", src_dir, dirs_exist_ok=True)
"platforms.js",
# 复制根目录的文档文件
doc_files = [
"LICENSE", "LICENSE",
"README.md", "README.md",
"PRIVACY_POLICY.md", "PRIVACY_POLICY.md",
"SECURITY.md", "SECURITY.md",
] ]
for file in common_files: for file in doc_files:
if os.path.exists(file): if os.path.exists(file):
shutil.copy2(file, build_dir) shutil.copy2(file, build_dir)
@@ -81,8 +83,8 @@ def build_chrome():
# 复制 Chrome manifest # 复制 Chrome manifest
shutil.copy2("manifest.json", build_dir / "manifest.json") shutil.copy2("manifest.json", build_dir / "manifest.json")
# 修改 background.js 为 Chrome 优化 # 为 Chrome 创建扁平化的文件结构
optimize_for_chrome(build_dir) flatten_for_browser(build_dir, "chrome")
print(f"Chrome 版本构建完成: {build_dir}") print(f"Chrome 版本构建完成: {build_dir}")
return build_dir return build_dir
@@ -104,13 +106,79 @@ def build_firefox():
# 复制 Firefox manifest # 复制 Firefox manifest
shutil.copy2("manifest-firefox.json", build_dir / "manifest.json") shutil.copy2("manifest-firefox.json", build_dir / "manifest.json")
# 优化文件以适配 Firefox # 为 Firefox 创建扁平化的文件结构
optimize_for_firefox(build_dir) flatten_for_browser(build_dir, "firefox")
print(f"Firefox 版本构建完成: {build_dir}") print(f"Firefox 版本构建完成: {build_dir}")
return build_dir return build_dir
def flatten_for_browser(build_dir, platform):
"""
将 src 目录中的文件扁平化到构建目录根
Args:
build_dir (Path): 构建目录路径
platform (str): 目标平台 (chrome/firefox)
"""
src_dir = build_dir / "src"
if not src_dir.exists():
print(f"警告: {src_dir} 不存在")
return
# 将文件从 src 子目录移动到根目录
# background/index.js -> background.js
bg_file = src_dir / "background" / "index.js"
if bg_file.exists():
shutil.copy2(bg_file, build_dir / "background.js")
# content/index.js -> content.js
content_file = src_dir / "content" / "index.js"
if content_file.exists():
shutil.copy2(content_file, build_dir / "content.js")
# popup/index.html -> popup.html
popup_html = src_dir / "popup" / "index.html"
if popup_html.exists():
shutil.copy2(popup_html, build_dir / "popup.html")
# popup/popup.js -> popup.js
popup_js = src_dir / "popup" / "popup.js"
if popup_js.exists():
shutil.copy2(popup_js, build_dir / "popup.js")
# shared/platforms.js -> platforms.js
platforms_file = src_dir / "shared" / "platforms.js"
if platforms_file.exists():
shutil.copy2(platforms_file, build_dir / "platforms.js")
# shared/platform-detector.js -> platform-detector.js
detector_file = src_dir / "shared" / "platform-detector.js"
if detector_file.exists():
shutil.copy2(detector_file, build_dir / "platform-detector.js")
# shared/compat/webext-compat.js -> webext-compat.js
webext_compat = src_dir / "shared" / "compat" / "webext-compat.js"
if webext_compat.exists():
shutil.copy2(webext_compat, build_dir / "webext-compat.js")
# shared/compat/firefox-compat.js -> firefox-compat.js
firefox_compat = src_dir / "shared" / "compat" / "firefox-compat.js"
if firefox_compat.exists():
shutil.copy2(firefox_compat, build_dir / "firefox-compat.js")
# 删除 src 目录
if src_dir.exists():
shutil.rmtree(src_dir)
# 平台特定优化
if platform == "chrome":
optimize_for_chrome(build_dir)
elif platform == "firefox":
optimize_for_firefox(build_dir)
def optimize_for_chrome(build_dir): def optimize_for_chrome(build_dir):
""" """
为 Chrome 优化文件 为 Chrome 优化文件
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
@@ -23,7 +23,7 @@
const detectBrowser = () => { const detectBrowser = () => {
const ua = navigator.userAgent; const ua = navigator.userAgent;
let manifest; let manifest;
try { try {
manifest = chrome.runtime.getManifest(); manifest = chrome.runtime.getManifest();
} catch (error) { } catch (error) {
@@ -101,7 +101,7 @@ const webext = (() => {
runtime: { getManifest: () => ({ version: "0.0.0" }) }, runtime: { getManifest: () => ({ version: "0.0.0" }) },
storage: { local: {}, sync: {} }, storage: { local: {}, sync: {} },
tabs: {}, tabs: {},
downloads: {} downloads: {},
}; };
} }
@@ -162,7 +162,10 @@ const webext = (() => {
// sendMessage(message) // sendMessage(message)
runtimeAPI.sendMessage(args[0], (response) => { runtimeAPI.sendMessage(args[0], (response) => {
if (api.runtime.lastError) { if (api.runtime.lastError) {
console.warn("SendMessage failed:", api.runtime.lastError.message); console.warn(
"SendMessage failed:",
api.runtime.lastError.message
);
resolve(null); resolve(null);
} else { } else {
resolve(response); resolve(response);
@@ -170,15 +173,18 @@ const webext = (() => {
}); });
} else if (args.length === 2) { } else if (args.length === 2) {
// 检查第二个参数是否是函数(回调) // 检查第二个参数是否是函数(回调)
if (typeof args[1] === 'function') { if (typeof args[1] === "function") {
// sendMessage(message, callback) - 直接调用回调,不使用 Promise // sendMessage(message, callback) - 直接调用回调,不使用 Promise
runtimeAPI.sendMessage(args[0], args[1]); runtimeAPI.sendMessage(args[0], args[1]);
resolve(undefined); // 立即解析 Promise resolve(undefined); // 立即解析 Promise
} else if (typeof args[0] === 'string' && args[0].length > 10) { } else if (typeof args[0] === "string" && args[0].length > 10) {
// sendMessage(extensionId, message) // sendMessage(extensionId, message)
runtimeAPI.sendMessage(args[0], args[1], (response) => { runtimeAPI.sendMessage(args[0], args[1], (response) => {
if (api.runtime.lastError) { if (api.runtime.lastError) {
console.warn("SendMessage failed:", api.runtime.lastError.message); console.warn(
"SendMessage failed:",
api.runtime.lastError.message
);
resolve(null); resolve(null);
} else { } else {
resolve(response); resolve(response);
@@ -188,7 +194,10 @@ const webext = (() => {
// sendMessage(message, options) // sendMessage(message, options)
runtimeAPI.sendMessage(args[0], args[1], (response) => { runtimeAPI.sendMessage(args[0], args[1], (response) => {
if (api.runtime.lastError) { if (api.runtime.lastError) {
console.warn("SendMessage failed:", api.runtime.lastError.message); console.warn(
"SendMessage failed:",
api.runtime.lastError.message
);
resolve(null); resolve(null);
} else { } else {
resolve(response); resolve(response);
@@ -197,7 +206,7 @@ const webext = (() => {
} }
} else if (args.length === 3) { } else if (args.length === 3) {
// 检查第三个参数是否是函数(回调) // 检查第三个参数是否是函数(回调)
if (typeof args[2] === 'function') { if (typeof args[2] === "function") {
// sendMessage(extensionId, message, callback) 或 sendMessage(message, options, callback) // sendMessage(extensionId, message, callback) 或 sendMessage(message, options, callback)
runtimeAPI.sendMessage(args[0], args[1], args[2]); runtimeAPI.sendMessage(args[0], args[1], args[2]);
resolve(undefined); // 立即解析 Promise resolve(undefined); // 立即解析 Promise
@@ -205,14 +214,17 @@ const webext = (() => {
// sendMessage(extensionId, message, options) // sendMessage(extensionId, message, options)
runtimeAPI.sendMessage(args[0], args[1], args[2], (response) => { runtimeAPI.sendMessage(args[0], args[1], args[2], (response) => {
if (api.runtime.lastError) { if (api.runtime.lastError) {
console.warn("SendMessage failed:", api.runtime.lastError.message); console.warn(
"SendMessage failed:",
api.runtime.lastError.message
);
resolve(null); resolve(null);
} else { } else {
resolve(response); resolve(response);
} }
}); });
} }
} else if (args.length === 4 && typeof args[3] === 'function') { } else if (args.length === 4 && typeof args[3] === "function") {
// sendMessage(extensionId, message, options, callback) // sendMessage(extensionId, message, options, callback)
runtimeAPI.sendMessage(args[0], args[1], args[2], args[3]); runtimeAPI.sendMessage(args[0], args[1], args[2], args[3]);
resolve(undefined); // 立即解析 Promise resolve(undefined); // 立即解析 Promise
@@ -244,7 +256,10 @@ const webext = (() => {
// sendMessage(tabId, message) // sendMessage(tabId, message)
tabsAPI.sendMessage(tabId, message, (response) => { tabsAPI.sendMessage(tabId, message, (response) => {
if (api.runtime.lastError) { if (api.runtime.lastError) {
console.warn("Tabs sendMessage failed:", api.runtime.lastError.message); console.warn(
"Tabs sendMessage failed:",
api.runtime.lastError.message
);
resolve(null); resolve(null);
} else { } else {
resolve(response); resolve(response);
@@ -254,7 +269,10 @@ const webext = (() => {
// sendMessage(tabId, message, options) // sendMessage(tabId, message, options)
tabsAPI.sendMessage(tabId, message, options, (response) => { tabsAPI.sendMessage(tabId, message, options, (response) => {
if (api.runtime.lastError) { if (api.runtime.lastError) {
console.warn("Tabs sendMessage failed:", api.runtime.lastError.message); console.warn(
"Tabs sendMessage failed:",
api.runtime.lastError.message
);
resolve(null); resolve(null);
} else { } else {
resolve(response); resolve(response);
@@ -285,33 +303,35 @@ const webext = (() => {
}, },
// Tabs API // Tabs API
tabs: api.tabs ? { tabs: api.tabs
query: promisifyNoFail(api.tabs, "query", []), ? {
get: promisifyNoFail(api.tabs, "get"), query: promisifyNoFail(api.tabs, "query", []),
create: promisifyNoFail(api.tabs, "create"), get: promisifyNoFail(api.tabs, "get"),
update: promisifyNoFail(api.tabs, "update"), create: promisifyNoFail(api.tabs, "create"),
remove: promisifyNoFail(api.tabs, "remove"), update: promisifyNoFail(api.tabs, "update"),
sendMessage: createTabsSendMessage(api.tabs), remove: promisifyNoFail(api.tabs, "remove"),
executeScript: promisifyNoFail(api.tabs, "executeScript"), sendMessage: createTabsSendMessage(api.tabs),
insertCSS: promisifyNoFail(api.tabs, "insertCSS"), executeScript: promisifyNoFail(api.tabs, "executeScript"),
removeCSS: promisifyNoFail(api.tabs, "removeCSS"), insertCSS: promisifyNoFail(api.tabs, "insertCSS"),
onUpdated: api.tabs.onUpdated, removeCSS: promisifyNoFail(api.tabs, "removeCSS"),
onActivated: api.tabs.onActivated, onUpdated: api.tabs.onUpdated,
onRemoved: api.tabs.onRemoved, onActivated: api.tabs.onActivated,
} : { onRemoved: api.tabs.onRemoved,
query: async () => [], }
get: async () => null, : {
create: async () => null, query: async () => [],
update: async () => null, get: async () => null,
remove: async () => null, create: async () => null,
sendMessage: async () => null, update: async () => null,
executeScript: async () => null, remove: async () => null,
insertCSS: async () => null, sendMessage: async () => null,
removeCSS: async () => null, executeScript: async () => null,
onUpdated: { addListener: () => {}, removeListener: () => {} }, insertCSS: async () => null,
onActivated: { addListener: () => {}, removeListener: () => {} }, removeCSS: async () => null,
onRemoved: { addListener: () => {}, removeListener: () => {} }, onUpdated: { addListener: () => {}, removeListener: () => {} },
}, onActivated: { addListener: () => {}, removeListener: () => {} },
onRemoved: { addListener: () => {}, removeListener: () => {} },
},
// Storage API // Storage API
storage: { storage: {
@@ -338,35 +358,43 @@ const webext = (() => {
}, },
// Downloads API // Downloads API
downloads: api.downloads ? { downloads: api.downloads
download: promisifyNoFail(api.downloads, "download"), ? {
search: promisifyNoFail(api.downloads, "search", []), download: promisifyNoFail(api.downloads, "download"),
cancel: promisifyNoFail(api.downloads, "cancel"), search: promisifyNoFail(api.downloads, "search", []),
pause: promisifyNoFail(api.downloads, "pause"), cancel: promisifyNoFail(api.downloads, "cancel"),
resume: promisifyNoFail(api.downloads, "resume"), pause: promisifyNoFail(api.downloads, "pause"),
erase: promisifyNoFail(api.downloads, "erase"), resume: promisifyNoFail(api.downloads, "resume"),
removeFile: promisifyNoFail(api.downloads, "removeFile"), erase: promisifyNoFail(api.downloads, "erase"),
show: promisifyNoFail(api.downloads, "show"), removeFile: promisifyNoFail(api.downloads, "removeFile"),
showDefaultFolder: promisifyNoFail(api.downloads, "showDefaultFolder"), show: promisifyNoFail(api.downloads, "show"),
onCreated: api.downloads.onCreated, showDefaultFolder: promisifyNoFail(
onChanged: api.downloads.onChanged, api.downloads,
onErased: api.downloads.onErased, "showDefaultFolder"
onDeterminingFilename: api.downloads.onDeterminingFilename, ),
} : { onCreated: api.downloads.onCreated,
download: async () => null, onChanged: api.downloads.onChanged,
search: async () => [], onErased: api.downloads.onErased,
cancel: async () => null, onDeterminingFilename: api.downloads.onDeterminingFilename,
pause: async () => null, }
resume: async () => null, : {
erase: async () => null, download: async () => null,
removeFile: async () => null, search: async () => [],
show: async () => null, cancel: async () => null,
showDefaultFolder: async () => null, pause: async () => null,
onCreated: { addListener: () => {}, removeListener: () => {} }, resume: async () => null,
onChanged: { addListener: () => {}, removeListener: () => {} }, erase: async () => null,
onErased: { addListener: () => {}, removeListener: () => {} }, removeFile: async () => null,
onDeterminingFilename: { addListener: () => {}, removeListener: () => {} }, show: async () => null,
}, showDefaultFolder: async () => null,
onCreated: { addListener: () => {}, removeListener: () => {} },
onChanged: { addListener: () => {}, removeListener: () => {} },
onErased: { addListener: () => {}, removeListener: () => {} },
onDeterminingFilename: {
addListener: () => {},
removeListener: () => {},
},
},
// Browser Action / Action API (兼容不同版本) // Browser Action / Action API (兼容不同版本)
action: (() => { action: (() => {
@@ -435,7 +463,9 @@ if (typeof module !== "undefined" && module.exports) {
// 服务工作器环境 // 服务工作器环境
self.webext = webext; self.webext = webext;
self.webextFlavor = webextFlavor; self.webextFlavor = webextFlavor;
console.log("WebExt compatibility layer loaded in service worker environment"); console.log(
"WebExt compatibility layer loaded in service worker environment"
);
} else { } else {
// 全局环境 // 全局环境
this.webext = webext; this.webext = webext;
File renamed without changes.
File renamed without changes.