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所在的层级。
package.json 也没有 deno.json 的项目。此时包含 manifest.json 的目录成为项目根目录。当那个 manifest 位于 src/ 时,src/ 就是根目录,特殊文件夹(以及 dist/)都住在 src/ 里。
模板示例
special-folders-pages

pages/ 特殊文件夹是如何工作的。
special-folders-scripts

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/ 设置问题都出在这两点。
位置。scripts/ 放在 package.json(或 deno.json)旁边,也就是项目根目录,而不是 src/ 里。即使 manifest.json 位于 src/,这条规则也成立。唯一的例外是既没有 package.json 也没有 deno.json 的项目,此时 manifest 所在目录就是根目录。参见特殊文件夹放在哪里。
**输出路径。**注入编译后的文件。scripts/foo.ts 源文件会被编译为 scripts/foo.js,所以 chrome.scripting.executeScript({ files }) 和 chrome.scripting.registerContentScripts({ js }) 必须写 .js 文件。写 .ts 路径时构建正常,但在浏览器里会 404。Extension.js 会在构建时对编译源路径字面量发出警告,并给出应使用的输出路径。
background.ts
需要在某处引用该文件路径
只有当scripts/ 条目相对项目的路径出现在你的源码中的某个位置时,它才会被保留:manifest、
某个 HTML 文件,或者 JavaScript / TypeScript 字符串,例如你传给
chrome.scripting.executeScript 的参数。正是这一点让仅在运行时注入成为可能,
因为该路径永远不会出现在 manifest 中。
没有任何地方提到的文件会被当作死代码,并在没有警告的情况下从构建中丢弃,
所以在为一个新入口做第一次构建之后,请检查 dist/<browser>/scripts/。
重要契约
当你把scripts/ 中的某个条目当作类似 content script 的运行时入口使用时,请遵循 content script 初始化模式。这是 Extension.js 期望的默认导出契约,用于安全地热重载注入脚本:
- 导出一个默认函数。
- 在该函数内完成初始化。
- 可选地返回一个同步的清理函数。
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')。
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的子文件夹作为未打包的扩展根。 - 以浏览器命名的子文件夹只会为该浏览器家族加载:
extensions/chrome/用于 Chromium 目标,extensions/edge/用于 Edge,extensions/firefox/用于 Gecko 目标。根级子文件夹与你显式配置的条目在所有浏览器上都会加载。 - Extension.js 会把附属扩展和你的主扩展一起加载。
- 把这个文件夹用于加载附属扩展,而不是把它们构建进你的主产物。
--extensions CLI 参数或 extension.config.js 中的 extensions 键加载附属扩展:
chromewebstore.google.com(以及旧的 chrome.google.com/webstore 形式)、microsoftedge.microsoft.com 与 addons.mozilla.org,带不带协议或 www. 前缀都可以。下载的商店扩展会落在 extensions/<browser>/ 下,并且只为该浏览器加载。来自其他主机的链接、裸的商店 id,或既不是链接也不是路径的条目会被报告为错误,而不是被悄悄丢弃。
开发期行为(watch 模式)
在 watch 模式下,Extension.js 会监控pages/ 与 scripts/ 的文件集合变化:
- 添加受支持的文件会触发一个警告(你可以继续工作)。
- 删除受支持的文件会触发一个编译错误。重启开发服务器即可恢复。
最佳实践
- 把共享的运行时资产放在
public/:用于必须在输出中保留原文件名和路径的文件。 - 把
pages/与scripts/用于真正的入口:让 manifest 之外的执行路径保持显式。 - 入口变化后重启开发服务器:尤其在删除
pages/或scripts/下的文件之后。 - 让附属扩展保持隔离:把
extensions/视为本地工作流中的仅加载依赖。 - 不要把
manifest.json放进public/:Extension.js 会阻止public/manifest.json,以保护生成的扩展产物。
下一步
- 进一步了解页面重载与热模块替换 (HMR)。
- 在 Content scripts 中了解挂载契约。
- 浏览模板 以快速搭建你的下一个扩展。

