Chrome扩展中epub.js、jszip.js等文件加载失败求助
问题
开发Chrome扩展用于读取解析上传的EPUB文件,因Content Security Policy(CSP)限制无法外链JS文件,已下载epub.js、jszip.min.js、jszip-utils.min.js并配置到web_accessible_resources中。但上传文件时,loadBook函数陷入循环,反复输出日志:Error: JSZip library not loaded, trying again。
上传文件执行的代码如下:
function handleFileSelection(event) { const file = event.target.files[0]; // 获取选中的文件 const reader = new FileReader(); reader.onload = function (event) { // 文件读取完成 const fileData = event.target.result; // 获取文件数据 console.log('fileData: ', fileData); // 创建EPUB.js的脚本元素 const epubScript = document.createElement('script'); epubScript.src = chrome.runtime.getURL('epub.js'); document.head.appendChild(epubScript); // 创建JSZip的脚本元素 const jszipScript = document.createElement('script'); jszipScript.src = chrome.runtime.getURL('jszip.min.js'); document.head.appendChild(jszipScript); // 创建JSZip Utils的脚本元素 const jszipUtilsScript = document.createElement('script'); jszipUtilsScript.src = chrome.runtime.getURL('jszip-utils.min.js'); document.head.appendChild(jszipUtilsScript); // 等待所有脚本加载完成 Promise.all([ new Promise((resolve) => { epubScript.onload = resolve; }), new Promise((resolve) => { jszipScript.onload = resolve; }), new Promise((resolve) => { jszipUtilsScript.onload = resolve; }), ]) .then(() => { // 所有脚本加载完成,开始使用 console.log('EPUB.js and JSZip libraries are loaded'); // 创建书籍实例 const book = ePub(); // 执行书籍逻辑 loadBook(); function loadBook() { if (typeof JSZip === 'undefined' || typeof JSZip.utils === 'undefined') { console.log('Error: JSZip library not loaded, trying again'); setTimeout(loadBook, 1000); return; } } }) .catch((error) => { console.error('Failed to load EPUB.js or JSZip:', error); }); }; }
解决方案及优化方案
一、修复当前加载失败问题
调整脚本加载顺序
EPUB.js依赖JSZip,必须保证JSZip先于EPUB.js加载。当前代码同时添加三个脚本到页面,执行顺序无法保证。调整加载顺序为:jszip.min.js→jszip-utils.min.js→epub.js,确保依赖关系正确:// 先加载JSZip const jszipScript = document.createElement('script'); jszipScript.src = chrome.runtime.getURL('jszip.min.js'); document.head.appendChild(jszipScript); // 再加载JSZip Utils const jszipUtilsScript = document.createElement('script'); jszipUtilsScript.src = chrome.runtime.getURL('jszip-utils.min.js'); document.head.appendChild(jszipUtilsScript); // 最后加载EPUB.js const epubScript = document.createElement('script'); epubScript.src = chrome.runtime.getURL('epub.js'); document.head.appendChild(epubScript);验证
web_accessible_resources配置
在manifest.json中确认配置路径与实际项目结构一致,示例配置:"web_accessible_resources": [ { "resources": ["epub.js", "jszip.min.js", "jszip-utils.min.js"], "matches": ["<all_urls>"] } ]若文件存放在子目录(如
lib/),需修改路径为lib/epub.js这类格式。移除冗余的
loadBook循环
既然已经通过Promise.all确保所有脚本加载完成,无需再检查JSZip是否存在,直接在then回调中执行后续逻辑:.then(() => { console.log('EPUB.js and JSZip libraries are loaded'); const book = ePub(); // 直接执行EPUB解析逻辑 book.open(fileData).then(() => { // 处理书籍元数据、章节等内容 console.log('书籍标题:', book.metadata.title); }); })
二、更优方案:利用扩展背景页/服务工作者处理
直接在页面上下文加载第三方库易引发CSP问题和变量污染,推荐将解析逻辑移至扩展的隔离环境:
配置服务工作者背景页
在manifest.json中声明:"background": { "service_worker": "background.js" }在
background.js中通过importScripts加载依赖库,并监听消息:importScripts('jszip.min.js', 'jszip-utils.min.js', 'epub.js'); chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'parseEpub') { const book = ePub(); book.open(message.fileData).then(() => { sendResponse({ success: true, title: book.metadata.title, chapters: book.spine.items.map(item => item.href) }); }); return true; // 保持端口开放,异步返回结果 } });页面脚本发送消息调用解析逻辑
reader.onload = function(event) { const fileData = event.target.result; chrome.runtime.sendMessage( {type: 'parseEpub', fileData}, (response) => { if (response.success) { console.log('解析完成,书籍标题:', response.title); } } ); };避免重复加载脚本
当前代码每次上传文件都会重新加载三个脚本,可通过标记变量避免重复加载:let librariesLoaded = false; let loadLibrariesPromise; function loadLibraries() { if (!loadLibrariesPromise) { loadLibrariesPromise = new Promise((resolve) => { // 脚本加载逻辑... Promise.all([/* 加载Promise数组 */]).then(resolve); }); } return loadLibrariesPromise; } function handleFileSelection(event) { loadLibraries().then(() => { // 执行解析逻辑 }); }
内容的提问来源于stack exchange,提问作者SirBT

