当你的扩展需要在 manifest.json 中无法妥善表达的入口或资产时,使用特殊文件夹。
在不拆分项目结构的前提下,处理额外页面、运行时注入的脚本、需要精确路径的静态资产,以及用于本地开发的附属扩展。
特殊文件夹放在哪里
特殊文件夹从项目根目录解析,而且只从那里解析。规则是:
- 项目根目录是包含你的
package.json(或 deno.json)的目录。
- 把
pages/、scripts/、public/ 与 extensions/ 放在那个目录里,和 package.json 并排。
src/scripts/ 不是特殊文件夹。Extension.js 会忽略嵌套的特殊文件夹副本,所以放在那里的文件永远不会成为入口或被复制的资产。
- 把
manifest.json 移进 src/ 不会移动项目根目录。脚手架模板把 src/manifest.json 和上一层的 package.json 一起发布,特殊文件夹属于 package.json 所在的层级。
唯一一个以 src 为根的例外:一个既没有 package.json 也没有 deno.json 的项目。此时包含 manifest.json 的目录成为项目根目录。当那个 manifest 位于 src/ 时,src/ 就是根目录,特殊文件夹(以及 dist/)都住在 src/ 里。
**我的 scripts/ 文件夹没有出现在 dist/ 里。**这几乎总是对应上面的规则:
这个文件夹放在了 src/ 下面,而不是和 package.json 并排。把它移到项目根目录。
如果它已经在那里,请确认你的项目里有地方引用了该文件路径。
模板示例
special-folders-pages
通过额外的 HTML 入口看看 pages/ 特殊文件夹是如何工作的。
代码仓库:extension-js/examples/special-folders-pages
special-folders-scripts
通过独立的脚本入口看看 scripts/ 特殊文件夹是如何工作的。
代码仓库:extension-js/examples/special-folders-scripts
为什么重要
manifest 并不会直接声明很多扩展文件,比如 iframe 页面、你用 chrome.scripting.executeScript 动态注入的脚本,以及静态厂商资产。开发期你还可能需要附属扩展。特殊文件夹让这些都成为构建流水线中的一等公民。
工作原理
每个特殊文件夹都有特定的角色:
pages/:额外的 HTML 入口
把 pages/ 用于额外的扩展页面,例如 sandbox iframe、诊断页或内部工具。
Extension.js 会把 pages/ 中的每个 .html 文件视为一个入口,按和 manifest 声明页面相同的方式编译。
sandbox iframe 示例可参见 Chrome Sandbox Sample。
scripts/:独立的脚本入口
把 scripts/ 用于可执行脚本,这些脚本你会动态加载、且并不绑定到某个 HTML 页面入口。
Extension.js 会把 scripts/ 中的文件作为入口编译,使用与项目其他部分相同的扩展解析流水线。
需要在某处引用该文件路径
只有当 scripts/ 条目相对项目的路径出现在你的源码中的某个位置时,它才会被保留:manifest、
某个 HTML 文件,或者 JavaScript / TypeScript 字符串,例如你传给
chrome.scripting.executeScript 的参数。正是这一点让仅在运行时注入成为可能,
因为该路径永远不会出现在 manifest 中。
没有任何地方提到的文件会被当作死代码,并在没有警告的情况下从构建中丢弃,
所以在为一个新入口做第一次构建之后,请检查 dist/<browser>/scripts/。
重要契约
当你把 scripts/ 中的某个条目当作类似 content script 的运行时入口使用时,请遵循 content script 初始化模式。这是 Extension.js 期望的默认导出契约,用于安全地热重载注入脚本:
- 导出一个默认函数。
- 在该函数内完成初始化。
- 可选地返回一个同步的清理函数。
这在开发期尤其重要,Extension.js 会安全地重新挂载类似 content script 的入口,而不是触发整页重载。
scripts/ 中不允许放 Node.js 脚本
Extension.js 会用浏览器 content-script 挂载运行时包装 scripts/ 中的每个文件。如果你把一个只能在 Node.js 中运行的文件(例如 CLI 启动器或构建辅助脚本)放进去,包装器会破坏该文件。
shebang 不再位于第 1 行,且 Node 专属 API 在浏览器上下文中无法使用。
Extension.js 会检测两类 Node.js 标志,并在构建时抛出错误:
- 第 1 行的 shebang(
#!/usr/bin/env node)。
- 来自
node: 协议的 import(例如 import fs from 'node:fs')。
如果你看到 scripts/ is a reserved folder in Extension.js,请把文件移到项目根目录下其他文件夹,比如
bin/、tools/、ops/、tasks/ 或 ci-scripts/。
动态注入示例可参见 Chrome Scripting Sample。
public/:仅复制的静态资产
当你需要稳定的文件路径且不希望经过打包/转换时,使用 public/。
Extension.js 会把 public/ 下的所有内容 1:1 复制到输出根目录。
public/ 的重要保护
不要把 manifest.json 放到 public/manifest.json。为了避免在编译过程中覆盖生成的 manifest,Extension.js 会阻止这种用法。
extensions/:附属扩展(仅加载)
当你使用附属扩展(例如 DevTools 辅助工具)时,Extension.js 在 dev/preview/start 流程中支持把 extensions/ 文件夹作为仅加载来源。
简要说明:
- 扫描
extensions/ 下含 manifest.json 的子文件夹作为未打包的扩展根。
- Extension.js 会把附属扩展和你的主扩展一起加载。
- 把这个文件夹用于加载附属扩展,而不是把它们构建进你的主产物。
你也可以通过 --extensions CLI 参数或 extension.config.js 中的 extensions 键加载附属扩展:
Extension.js 会自动下载、解包并与你的扩展一起加载这些商店 URL。
开发期行为(watch 模式)
在 watch 模式下,Extension.js 会监控 pages/ 与 scripts/ 的文件集合变化:
- 添加受支持的文件会触发一个警告(你可以继续工作)。
- 删除受支持的文件会触发一个编译错误。重启开发服务器即可恢复。
这能保护正在运行的编译图,防止它出现过期或损坏的入口。
最佳实践
- 把共享的运行时资产放在
public/:用于必须在输出中保留原文件名和路径的文件。
- 把
pages/ 与 scripts/ 用于真正的入口:让 manifest 之外的执行路径保持显式。
- 入口变化后重启开发服务器:尤其在删除
pages/ 或 scripts/ 下的文件之后。
- 让附属扩展保持隔离:把
extensions/ 视为本地工作流中的仅加载依赖。
- 不要把
manifest.json 放进 public/:Extension.js 会阻止 public/manifest.json,以保护生成的扩展产物。
下一步
模板演示