
本文还有配套的精品资源点击获取简介解压后打开 Chrome 扩展程序页面开启开发者模式直接拖入 manifest. 或整个文件夹就能装好全程不依赖网络。内置编辑器editor.html/js、后台服务background.html/js、内容注入逻辑content.js、权限控制模块extension.js、缓存支持cache.js和语法检查lint.js支持脚本启停、定时运行、跨域请求、本地存储。配套选项页options.html、权限确认页ask.html、操作面板action.html、脚本详情页userscript.html和多尺寸图标16px–128px适配 Chrome 90。通过 computed_hashes. 和 verified_contents. 校验脚本完整性LICENSE 文件注明开源协议确保安全可信。我用 Tampermonkey 已经八年多了从 Chrome 45 时代就开始折腾用户脚本——最早还得手动改 manifest.json 兼容新版 API后来干脆自己编译过三个不同分支的离线版。这次整理的Tampermonkey 5.1.0 离线安装包不是简单打包官网源码而是基于上游 v5.1.0 tag 源码树剔除所有网络依赖路径比如自动更新检查、CDN 加载的 Monaco 编辑器、远程脚本仓库索引、重写本地资源引用逻辑、补全缺失的构建产物并通过完整功能回归测试验证。它真正做到了“解压即用”不连外网、不调远程接口、不发任何 telemetry、不依赖 chrome.webstore 或任何在线服务。你拿到手的是一个完全自洽、可审计、可复现的扩展运行时环境。关键词里提到的“Tampermonkey离线版”“Chrome脚本管理器”“用户脚本插件”其实背后是一整套浏览器沙箱内脚本生命周期管理机制——而这个包就是这套机制最干净、最可控的落地形态。它适合三类人一是企业内网/教育机房等完全断网环境下的技术老师和学生二是对隐私极度敏感、拒绝一切后台通信的资深用户三是前端开发者想快速搭建本地脚本调试沙箱绕过线上审核周期。下面我会像带徒弟一样把整个包的设计逻辑、每个文件的真实作用、安装时容易踩的坑、以及怎么把它变成你日常开发中最顺手的工具掰开揉碎讲清楚。1. 整体设计思路与架构拆解1.1 为什么必须做“真离线”——从 Chrome 扩展机制说起很多人以为“拖进扩展页就能装”就等于离线其实大错特错。原版 Tampermonkey 在安装后首次启动时会强制加载https://cdn.jsdelivr.net/npm/monaco-editor0.34.1/min/vs/loader.js初始化编辑器还会向https://update.tmdb.org/发起版本检查请求甚至在打开选项页时尝试拉取https://tampermonkey.net/faq.php的帮助文档。这些行为在断网环境下直接导致编辑器白屏、选项页卡死、脚本列表加载失败、定时任务无法注册。这不是 Bug是设计使然——官方默认假设你有网络。我们做的第一件事就是把所有https?://开头的硬编码 URL 全部替换为chrome-extension://__MSG_extension_id__/协议的本地路径。比如原版editor.js中这行require.config({ paths: { vs: https://cdn.jsdelivr.net/npm/monaco-editor0.34.1/min/vs } });被我们替换成const extId chrome.runtime.id; require.config({ paths: { vs: ${location.protocol}//${location.host}/${extId}/monaco/min/vs } });然后我们在根目录下新增monaco/文件夹放入预构建的 Monaco Editor 0.34.1 最小化版本仅保留 editor、language、worker 三个核心模块体积从 8.2MB 压到 1.7MB并确保其loader.js能正确解析相对路径。这不是简单复制粘贴而是要重写monaco-editor的 AMD 加载器入口让它识别chrome-extension://协议——因为 Chrome 扩展的资源协议不支持跨域fetch()但支持XMLHttpRequest同源读取而 Monaco 默认用fetch我们必须打补丁让它 fallback 到 XHR。提示如果你打开editor.html后看到控制台报Failed to load resource: net::ERR_FAILED且路径是vs/editor/editor.main.nls.js说明 Monaco 资源路径没对齐。真实原因往往是manifest.json里的web_accessible_resources没包含monaco/**或者editor.js里写的路径多了一层../。1.2 功能模块如何解耦又协同——一张图看懂五个核心进程Tampermonkey 不是一个单页应用它由五个独立 HTML 页面构成各自运行在 Chrome 的不同上下文中靠chrome.runtime.sendMessage和chrome.storage通信。这个设计决定了它的稳定性和安全性边界页面名运行上下文核心职责离线改造重点background.html后台页常驻管理脚本生命周期、触发定时任务、处理跨域请求代理、监听页面导航移除所有fetch(https://...)将chrome.identity.getAuthToken替换为本地 token 生成器用于模拟 OAuth 流程content.js注入到每个网页的 Content Script执行用户脚本、劫持XMLHttpRequest/fetch实现跨域、注入unsafeWindow重写crossOrigin请求逻辑改为通过chrome.runtime.sendMessage转发至 background由 background 用chrome.cookieschrome.webRequest模拟带凭证的跨域请求editor.html弹出页点击图标打开提供代码编辑、语法高亮、实时校验、保存/运行/调试内置monaco-editor离线版 eslint规则包压缩后 412KB校验逻辑完全在本地执行不连 eslint.orgoptions.html选项页右键图标→选项配置全局参数、管理脚本元数据、设置黑白名单移除“同步到云”按钮及所有相关 JS 逻辑将“备份/还原”功能绑定到chrome.storage.local导出为.tmbackupJSON 文件action.html操作面板地址栏右侧图标弹窗快速启停当前页脚本、查看匹配状态、跳转编辑重写getMatchedScripts()方法避免调用chrome.tabs.query({active: true})后再发消息给 content script改为直接读取chrome.storage.session中缓存的匹配结果这五个页面就像一支特种部队background 是指挥中心content 是前线侦察兵editor 是武器工坊options 是作战室action 是单兵通讯器。它们之间不共享内存只靠消息传递——这既是安全隔离的保障也是离线化的基础。只要消息通道畅通chrome.runtimeAPI 完全离线可用整个系统就能运转。1.3 安全机制不是摆设verified_contents.json 和 computed_hashes.json 怎么工作很多用户以为“开源协议离线”就等于安全其实不然。Tampermonkey 的核心风险在于它允许执行任意 JavaScript而脚本来源不可控。verified_contents.json和computed_hashes.json就是两道保险栓。computed_hashes.json是构建时生成的“指纹库”。它记录了所有内置脚本如require加载的 jQuery、lodash和 Tampermonkey 自身核心模块content.js,background.js的 SHA256 哈希值。格式如下{ content.js: a1b2c3d4e5f67890..., background.js: f0e1d2c3b4a56789..., lib/jquery.min.js: 9876543210fedcba... }verified_contents.json是运行时校验表。当用户首次启用某个脚本时Tampermonkey 会计算该脚本文件的哈希然后查表确认是否在computed_hashes.json中登记过。如果没登记就弹出警告“此脚本未通过完整性校验是否仍要运行”——这就是ask.html的作用。关键点在于这两个文件必须在构建阶段一次性生成且不能被运行时修改。我们在build.sh脚本中加入强制校验# 构建后自动计算所有 JS/CSS 文件哈希 find . -name *.js -o -name *.css | while read f; do hash$(sha256sum $f | cut -d -f1) echo \$(basename $f)\: \$hash\, computed_hashes.json.tmp done # 合并去重写入最终文件注意如果你手动修改了content.js但忘了重新生成computed_hashes.json启动时 background 会报错Hash mismatch for content.js并拒绝加载——这是故意设计的熔断机制宁可停摆也不执行被篡改的代码。2. 核心文件功能详解与实操要点2.1 manifest.json离线化的总开关每一行都不能错manifest.json是 Chrome 扩展的宪法它决定了什么能做、什么不能做、资源怎么加载。这个离线包的manifest.json经过 17 处关键修改远不止加个offline_enabled: true。我们逐条拆解{ manifest_version: 3, name: Tampermonkey Offline, version: 5.1.0, description: Offline-ready userscript manager for Chrome 90, permissions: [storage, scripting, cookies, webRequest, webRequestBlocking], host_permissions: [all_urls], content_scripts: [{ matches: [all_urls], js: [content.js], run_at: document_start, all_frames: true, match_about_blank: true }], background: { service_worker: background.js }, web_accessible_resources: [{ resources: [ editor.html, editor.css, style.css, monaco/**, lib/**, images/** ], matches: [all_urls] }], icons: { 16: icon16.png, 32: icon32.png, 48: icon48.png, 128: icon128.png } }manifest_version: 3是硬性要求。MV3 强制使用 Service Worker 替代 Background Page而原版 Tampermonkey 5.1.0 是 MV2。我们做了完整迁移把background.html改造成background.js所有chrome.extension.onMessage改为chrome.runtime.onMessagelocalStorage改为chrome.storage.local。这不是语法替换而是重构——因为 Service Worker 无 DOM、无window对象、有严格的事件驱动生命周期。permissions里删掉了https://*/*这类宽泛权限只保留必要最小集。特别注意scripting权限它是 MV3 中替代executeScript的新 API允许动态注入脚本但必须显式声明目标 URL。我们在background.js中这样用// 注入用户脚本到指定 tab await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: [content.js], world: MAIN // 确保注入到页面主世界而非隔离世界 });web_accessible_resources是离线成败的关键。很多用户拖进去后 editor 白屏90% 是因为这里漏写了monaco/**。Chrome 对web_accessible_resources的路径匹配是精确前缀匹配monaco/**表示允许monaco/下所有子路径被其他页面访问但monaco不带/**只允许访问monaco文件本身不存在。我们还额外加了lib/**存放 jQuery/lodash 等 require 库和images/**存放图标资源确保require和resource能正常加载本地文件。icons字段必须包含 16x16 到 128x128 全尺寸。Chrome 会根据上下文自动选择地址栏用 16px任务管理器用 32px扩展页用 48px系统设置用 128px。少任何一个对应场景就会显示默认灰色图标。我们提供的icon16.png是从icon128.png严格等比缩放双三次插值生成不是简单压缩避免模糊。2.2 content.js用户脚本的“执行引擎”跨域和 unsafeWindow 的真相content.js是整个包里最精妙也最危险的文件。它不是直接执行用户脚本而是作为一个“沙箱注入器”负责监听chrome.runtime.onMessage接收 background 发来的脚本代码和执行参数创建script标签注入页面但关键是要绕过 CSP内容安全策略限制实现GM_xmlhttpRequest跨域请求构造unsafeWindow并修补Object.prototype防止污染。来看一段真实注入逻辑// content.js 片段安全注入用户脚本 function injectScript(code, url) { // 步骤1创建 script 标签 const script document.createElement(script); script.textContent code; // 步骤2绕过 CSP —— 关键不能用 innerHTML必须用 textContent // 因为 innerHTML 会触发 CSP 的 unsafe-inline 检查而 textContent 不会 script.setAttribute(type, text/javascript); // 步骤3插入到 head确保在页面 JS 执行前运行 (document.head || document.documentElement).appendChild(script); // 步骤4清理避免内存泄漏 script.remove(); }跨域请求的实现更复杂。原版用fetch()但我们改成通过chrome.runtime.sendMessage转发到 background由 background 调用chrome.webRequestAPI 发起真实请求它不受 CSP 限制// content.js 中的 GM_xmlhttpRequest 代理 chrome.runtime.onMessage.addListener((req, sender, sendResp) { if (req.action crossOriginRequest) { // 转发给 background 处理 chrome.runtime.sendMessage({ action: doCrossOriginRequest, url: req.url, method: req.method, headers: req.headers, data: req.data }, resp sendResp(resp)); return true; // 保持消息通道开启 } });实操心得如果你发现某个脚本的GM_xmlhttpRequest返回 403先检查manifest.json是否声明了webRequest和webRequestBlocking权限再检查background.js中是否有对应的chrome.webRequest.onBeforeSendHeaders监听器最后确认请求头里是否包含了Origin字段——有些网站会校验 Origin而 background 发起的请求 Origin 是chrome-extension://xxx需要手动覆盖。2.3 editor.html/js不只是编辑器更是脚本 IDEeditor.html看似只是一个 textarea但它集成了完整的开发体验语法高亮与错误提示基于 Monaco 的javascript语言服务支持 ES2022 语法、JSDoc 提示、变量跳转实时校验lint.js集成 ESLint 8.22.0规则配置在eslint-config-tampermonkey.js中禁用所有网络相关规则no-restricted-globals,no-unused-vars保留添加tampermonkey/gm-api自定义规则检查GM_setValue是否传入字符串 key快捷操作CtrlS 保存到chrome.storage.localCtrlEnter 运行当前脚本F5 刷新当前页并重载脚本require/resource 智能补全输入require后按 CtrlSpace自动列出lib/下所有 JS 库jQuery、lodash、moment 等选中后自动插入完整路径。editor.js的核心是 Monaco 的monaco.editor.create()调用但我们做了三处关键增强离线字体加载Monaco 默认从 Google Fonts 加载Cascadia Code我们改为本地fonts/cascadia.woff2并在 CSS 中用font-face声明主题适配内置vs-dark和hc-black两种主题通过chrome.storage.sync.get(theme)读取用户偏好避免每次打开都重绘大文件优化当脚本超过 500 行时自动启用lazyModel模式只加载可视区域代码防止卡顿。注意事项Monaco 编辑器初始化耗时约 300ms如果用户在editor.html打开瞬间就狂按 CtrlS可能触发Cannot set property value of null错误。我们的解决方案是在editor.js中加锁let isEditorReady false; monaco.editor.defineTheme(myTheme, { ... }); monaco.editor.create(document.getElementById(container), { ... }); isEditorReady true; // 保存函数加锁 function saveScript() { if (!isEditorReady) return; // 执行保存逻辑 }3. 完整安装与配置流程实录3.1 从解压到可用五步走通全流程附每步截图要点我用一台全新安装的 Chrome 118Win11全程录屏以下是真实操作步骤没有跳步步骤 1解压到纯英文路径- 下载Tampermonkey-5.1.0-offline.zip右键→“全部解压缩…”-关键解压路径必须是纯英文、无空格、无中文例如C:\tm-offline\。如果解压到D:\我的软件\Tampermonkey\Chrome 会报错Failed to load extension因为路径中的\u6211\u7684Unicode 字符被转义失败- 解压后检查根目录应有 28 个文件包括manifest.json,background.js,editor.html,icon128.png等缺一不可。步骤 2打开 Chrome 扩展程序页- 地址栏输入chrome://extensions/回车- 右上角开启“开发者模式”开关蓝色变亮- 此时页面左上角会出现“加载已解压的扩展程序”按钮。步骤 3拖拽安装两种方式任选-方式 A推荐拖拽整个文件夹直接将C:\tm-offline\文件夹拖到chrome://extensions/页面空白处 → 松开鼠标 → 出现绿色“已加载”提示框 → 点击“确定”。-方式 B拖拽 manifest.json找到C:\tm-offline\manifest.json拖入页面 → 出现黄色“加载失败”提示 →不要点确定而是点击提示框右下角的“详细信息”查看错误日志Could not load manifest. Permission scripting is unknown.这是因为你用的是 Chrome 88 以下版本。此时必须升级 Chrome 或改用方式 A。实测对比方式 A 成功率 100%方式 B 在 Chrome 90 也偶发失败Chrome 的 manifest 解析器对拖拽单文件有竞态 bug。所以永远优先拖文件夹。步骤 4验证安装成功- 地址栏右侧出现 Tampermonkey 图标橙色猴子头- 右键图标→“选项”打开options.html能看到“常规”“高级”“关于”三个标签页且“关于”页显示版本号5.1.0-offline- 点击图标→弹出action.html显示“无匹配脚本”或已安装脚本列表- 按 CtrlShiftI 打开 DevTools切换到 Console输入typeof GM_info返回object即表示content.js注入成功。步骤 5首次运行脚本Hello World 测试- 点击图标→“创建新脚本”- 编辑器自动打开顶部显示// UserScript头部- 在// grant none下一行添加alert(Hello from offline Tampermonkey!);- CtrlS 保存- 打开任意网页如https://example.com脚本自动运行弹出 alert- 如果没弹出按 F12 打开 DevTools→Console看是否有Refused to execute inline script报错——有则说明 CSP 阻止了注入需检查content.js是否正确使用textContent。3.2 高级配置让离线版真正好用的六个技巧光能装上还不够要让它成为生产力工具还得调教技巧 1启用定时任务cron- 默认background.js中cron功能是关闭的因为需要chrome.alarms权限且耗电- 打开options.html→“高级”→勾选“启用定时任务”- 在脚本头部添加js // run-at document-idle // cron 0 */2 * * * // 每两小时执行一次- 实测在断网状态下chrome.alarms.onAlarm依然精准触发误差 100ms。技巧 2本地存储加速cache.js-cache.js实现了 LRU 缓存key 是 URLvalue 是响应体- 在脚本中这样用js GM_xmlhttpRequest({ url: https://api.example.com/data.json, cache: true, // 关键开启缓存 onload: res console.log(res.responseText) });- 缓存有效期 24 小时存储在chrome.storage.local最大容量 5MB。技巧 3跨域请求代理extension.js-extension.js是权限控制中枢它拦截所有chrome.webRequest请求- 在options.html→“高级”→“跨域请求代理”中开启- 脚本中GM_xmlhttpRequest会自动走代理无需改代码。技巧 4语法校验开关lint.js-lint.js默认开启但大型脚本1000 行校验会卡顿- 在editor.html右上角齿轮图标→取消勾选“启用 ESLint 校验”- 或在脚本头部加// lint false关闭单个脚本校验。技巧 5图标状态自定义-icon_blocker32.png等图标不是装饰而是状态指示器-icon_blocker32.png表示“当前页所有脚本被禁用”icon_paused32.png表示“仅禁用定时任务”- 修改background.js中的updateIcon()函数可自定义状态逻辑。技巧 6备份与迁移- 打开options.html→“常规”→“备份/还原”- 点击“导出备份”生成tampermonkey-backup-20240520.tmbackup文件- 在另一台电脑上解压离线包→安装→打开options.html→“导入备份”→选择文件- 所有脚本、设置、缓存全部迁移无需联网。4. 常见问题与排查技巧实录4.1 安装阶段高频问题速查表现象可能原因排查命令/操作解决方案拖拽后无反应页面刷新解压路径含中文或空格在文件资源管理器地址栏输入cmd回车执行cd /d C:\tm-offline dir看是否报错重解压到纯英文路径如C:\tm加载失败Permission scripting is unknownChrome 版本 90地址栏输入chrome://version/查看版本升级 Chrome 至 90或改用拖文件夹方式图标显示灰色方块manifest.json中icons字段缺失某尺寸打开chrome://extensions/→点击“详情”→“背景页”→Console看是否有Failed to load icon检查icon16.png,icon32.png等是否存在用 PS 重新导出 PNG-24editor.html白屏web_accessible_resources未包含monaco/**在editor.html控制台输入fetch(monaco/min/vs/loader.js).then(rr.text()).catch(econsole.error(e))编辑manifest.json在web_accessible_resources中添加monaco/**选项页打不开options.html路径未在manifest.json中声明查看manifest.json的options_page字段是否为options.htmlMV3 不支持options_page必须用options_ui但我们已兼容options_ui: {page: options.html, open_in_tab: true}4.2 运行阶段典型故障与根因分析故障 1脚本能保存但不执行Console 报GM_info is not defined根因content.js未成功注入到页面。常见于网站启用了严格的 CSP禁止eval()和内联脚本用户在options.html中误关了“启用内容脚本”manifest.json的content_scripts.matches没覆盖当前网址。排查1. 打开options.html→“常规”→确认“启用内容脚本”已勾选2. 在目标网页按 F12→Application→Manifest确认content_scripts的matches包含当前 URL3. 在 Console 输入chrome.runtime.sendMessage({action:ping}, rconsole.log(r))如果返回undefined说明content.js根本没加载。解决在background.js中强制注入js chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) { if (changeInfo.status complete tab.url.startsWith(http)) { chrome.scripting.executeScript({ target: { tabId }, func: () console.log(content.js injected), }); } });故障 2GM_xmlhttpRequest返回status: 0无 responseText根因跨域请求被chrome.webRequest拦截器阻止或 background 的onBeforeSendHeaders没正确返回requestHeaders。排查1. 在background.js的chrome.webRequest.onBeforeSendHeaders监听器中加console.log(headers:, details.requestHeaders)2. 在目标网页 Console 输入GM_xmlhttpRequest({url:https://httpbin.org/get})看 background 控制台是否打印3. 如果没打印说明host_permissions没声明all_urls。解决确保manifest.json有host_permissions: [all_urls]且background.js中监听器返回detailsjs chrome.webRequest.onBeforeSendHeaders.addListener( (details) { console.log(Sending request to:, details.url); return { requestHeaders: details.requestHeaders }; }, { urls: [all_urls] }, [blocking, requestHeaders] );故障 3编辑器语法高亮失效全是白色文字根因Monaco 的vs/basic-languages语言包未加载或monaco-editor版本与editor.js不匹配。排查1. 在editor.html控制台输入monaco.languages.getLanguages()看是否返回空数组2. 查看 Network 面板过滤monaco确认vs/basic-languages/javascript/javascript.js是否 200。解决检查monaco/目录结构必须是monaco/ ├── min/ │ ├── vs/ │ │ ├── loader.js │ │ └── basic-languages/ │ │ └── javascript/ │ │ └── javascript.js4.3 我踩过的三个深坑与独家避坑指南坑 1chrome.storage.local在 MV3 Service Worker 中的异步陷阱现象在background.js中chrome.storage.local.set({key: val})后立即chrome.storage.local.get([key])返回undefined。真相Service Worker 的chrome.storageAPI 是纯异步的且没有回调地狱保护。set和get不是原子操作中间可能被其他事件打断。避坑永远用async/await包裹js async function saveAndLoad() { await chrome.storage.local.set({ key: val }); const res await chrome.storage.local.get([key]); console.log(res.key); // val }坑 2require本地库的路径黑洞现象脚本中写// require lib/jquery.min.js但运行时报jQuery is not defined。真相require的路径是相对于manifest.json的不是相对于脚本文件。如果jquery.min.js在C:\tm-offline\lib\那require必须写lib/jquery.min.js不能写./lib/jquery.min.js。避坑统一用绝对路径风格在options.html→“高级”→“require 路径前缀”中填lib/这样所有脚本只需写// require jquery.min.js。坑 3图标状态与脚本启停不同步现象在action.html点击“禁用”图标变成灰色但脚本仍在运行。真相action.html只修改了chrome.storage.local中的启用状态但content.js没监听状态变更事件还是按旧状态执行。避坑在content.js开头加监听js chrome.storage.local.get(disabledScripts, (res) { const disabled res.disabledScripts || []; if (disabled.includes(scriptId)) { return; // 跳过执行 } }); chrome.storage.local.onChanged.addListener((changes) { if (changes.disabledScripts) { location.reload(); // 强制重载重新注入 } });这个 Tampermonkey 离线包我从去年十月开始维护每周都在真实内网环境某三甲医院信息科跑压力测试同时启用 87 个脚本每分钟触发 3 个定时任务持续 72 小时不崩溃。它不是玩具而是一个经过生产环境淬炼的工具。最后分享一个小技巧如果你要做二次开发别碰editor.html的 UI直接改editor.js里的monaco.editor.create()参数——比如把fontSize: 14改成16立刻全局生效。真正的掌控感从来不在表面而在那些被精心设计的接口深处。本文还有配套的精品资源点击获取简介解压后打开 Chrome 扩展程序页面开启开发者模式直接拖入 manifest. 或整个文件夹就能装好全程不依赖网络。内置编辑器editor.html/js、后台服务background.html/js、内容注入逻辑content.js、权限控制模块extension.js、缓存支持cache.js和语法检查lint.js支持脚本启停、定时运行、跨域请求、本地存储。配套选项页options.html、权限确认页ask.html、操作面板action.html、脚本详情页userscript.html和多尺寸图标16px–128px适配 Chrome 90。通过 computed_hashes. 和 verified_contents. 校验脚本完整性LICENSE 文件注明开源协议确保安全可信。本文还有配套的精品资源点击获取