- 开发工具
- 前端
- CLI
【免费下载链接】browser-sync
Keep multiple browsers & devices in sync when building websites. https://browsersync.io
BrowserSync 是一款在网站构建过程中让多台浏览器与设备保持实时同步的开发工具,其核心能力来自两个底层设计:在 HTML 响应中注入连接脚本(Snippet),以及基于 Immutable 数据结构的选项系统。本文以仓库根目录 README.md 为主线,结合packages/browser-sync下的源码与测试,系统讲解 Snippet 注入的工作原理与自定义规则、1.x 升级到 2.x 后选项 API 的变化,并给出覆盖 CLI 与 Node API 的可运行实战方案。
一、它解决什么问题:构建网站时的多端同步
当你在开发一个网站时,往往需要同时在桌面浏览器、手机模拟器、平板等设备上查看页面效果,并让它们保持"动作一致"。Browsersync 的核心定位是Keep multiple browsers & devices in sync when building websites,它主要提供两类同步能力:
- Ghost Mode(镜像模式):点击、滚动、表单输入会在所有连接的设备间互相镜像。对应默认配置位于 default-config.js:
ghostMode: { clicks: true, scroll: true, location: true, forms: { submit: true, inputs: true, toggles: true } }- 文件变更响应:CSS 与图片变更会被直接注入页面(无需整页刷新),其余文件类型触发全页刷新。相关默认值见 default-config.js 中的
injectFileTypes(css/png/jpg/jpeg/svg/gif/webp/map)与injectChanges: true。
要让浏览器"听懂"这些同步指令,Browsersync 必须先把一段客户端脚本送进页面——这就是 Snippet 注入机制。
二、核心机制:Snippet 注入的工作原理
2.1 基本原理与硬性前提
README 明确说明了注入方式:Browsersync works by injecting an asynchronous script tag (<script async>...</script>) right after the<body>tag during initial request。
也就是说,在首次请求 HTML 页面时,Browsersync 会通过中间件改写响应,在<body>标签之后插入一段异步脚本。因此:
页面中必须存在
<body>标签,注入才能正常工作。如果页面没有<body>(例如 SPA 的异常响应或纯 JSON 接口),就需要通过snippetOptions提供自定义的匹配规则与注入函数。
这一前提在源码中有完整印证:
- 默认匹配规则定义于 default-config.js,默认正则正是
/ <body[^>]*>/i,默认注入函数为match + snippet,即"在匹配到的<body>标签后拼接 snippet":
snippetOptions: { async: true, whitelist: [], blacklist: [], rule: { match: /<body[^>]*>/i, fn: function(snippet, match) { return match + snippet; } } }- 注入的脚本模板位于 templates/script-tags.html,其中会根据
%async%决定是否设置script.async = true,并把%script%替换为客户端脚本地址(默认形如HOST:3000/browser-sync/browser-sync-client.js):
<script id="__bs_script__">//<![CDATA[ (function() { try { var script = document.createElement('script'); if ('%async%') { script.async = true; } script.src = '%script%'.replace("HOST", location.hostname); if (document.body) { document.body.appendChild(script); } else if (document.head) { document.head.appendChild(script); } } catch (e) { console.error("Browsersync: could not append script tag", e); } })() //]]></script>2.2 注入中间件的实现链路
snippet 注入实际上由一组响应改写中间件完成,核心实现位于 lib/snippet.js:
getRegex(snippet, options):读取snippetOptions.rule中的match正则与fn函数,生成{match, fn, once: true, id: "bs-snippet"}规则;getRules(snippet, options, rewriteRules):把 snippet 规则与用户附加的rewriteRules合并,并读取blacklist/whitelist;getSnippetMiddleware(...):借助resp-modifier创建可插入请求处理链的中间件。
connect-utils.js中的 scriptTags 则负责生成最终的脚本标签:根据运行模式(server / proxy / snippet)和scriptPath、script.domain、localOnly等选项计算src,再读取模板替换%script%与%async%。
2.3 排除规则:哪些响应不会被注入
不是所有响应都应该被注入 snippet,例如 CSS、图片、字体等二进制或静态资源。源码通过excludedFileTypes与isExcluded实现:
- 默认排除类型完整列表见 default-config.js,包括
js/css/pdf/svg/ico/woff/json/png/jpg/jpeg/webp/gif/mp4/mp3/ogg/...; - snippet.js 中的
isExcluded(url, excludeList)会按 URL 扩展名判断,并且带查询字符串(?)的请求也会被排除; - 测试 test/specs/server/server.snippet.js 对此有完整断言:
/styles/core.css?rel=12322、/core.js、/files.png等均返回true(被排除),而根路径/返回false(可注入)。
另外,针对 IE8 及以下浏览器,isOldIe 会根据 User-Agent 中MSIE x.y判断,若版本号小于 9,会把accept头改写为text/html,迫使服务器返回 HTML 以便注入;对排除列表内的资源则跳过改写(见 server.snippet.js 的 "Header replacement for IE8" 用例)。
2.4 用 snippetOptions 自定义注入规则
当页面没有<body>标签,或你想把脚本注入到别的位置时,可通过snippetOptions自定义rule.match与rule.fn。README 给出的链接指向官方snippetOptions文档,其参数结构在 default-config.js 中定义:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
async | Boolean | true | 脚本标签是否带async属性 |
whitelist | Array | [] | 仅对匹配的 URL 注入 |
blacklist | Array | [] | 对匹配的 URL 跳过注入 |
rule.match | RegExp | /<body[^>]*>/i | 用于定位注入锚点的正则 |
rule.fn | Function | (snippet, match) => match + snippet | 接收 snippet 与匹配结果,返回改写后的 HTML |
此外 options.ts 中的fixSnippetIgnorePaths/fixSnippetIncludePaths还兼容了历史版本的snippetOptions.ignorePaths/snippetOptions.includePaths,会把它们自动归一化(补全前导/)后映射为blacklist/whitelist。若想完全禁止注入,可设置snippet: false(见 connect-utils.js 的enabled()判断)。
三、选项系统演进:1.x 到 2.x 的 Immutable 改造
3.1 为什么需要关注这个变化
README 明确指出:从 1.x 升级到 2.x,只要没有访问内部属性,公共 API 没有破坏性变更;但内部选项存储改用了Immutable 数据结构。因此"读取选项"的写法发生了变化,这是从旧版本迁移时最容易踩坑的地方。
升级前(1.x)——直接访问对象属性:
browserSync({server: true}, function(err, bs) { console.log(bs.options.urls.local); });升级后(2.x+)——通过getIn(["urls", "local"])按路径读取:
browserSync({server: true}, function(err, bs) { console.log(bs.options.getIn(["urls", "local"])); });3.2 源码层面的印证
bs.options是immutable.Map,这一点在 connect-utils.js 的注释(@param {Immutable.Map} options)以及 options.ts 的import * as Immutable from "immutable"中均有体现;- 选项的读取全部走
get/getIn,写入走set/setIn/withMutations。实例方法定义于 browser-sync.js:getOption(name)、getOptionIn(path)、setOption(name, value)、setOptionIn(path, value)、setMany(fn); - 顶层配置在初始化时由 CLI/API 合并后转为 Immutable,模式判定(server / proxy / snippet)、scheme 推导、snippet 黑白名单归一化等转换逻辑集中在 options.ts(如
setMode、setScheme、fixSnippetIgnorePaths)。
3.3 迁移建议与配套 API
- 读取深层选项一律使用
getIn([...路径]),路径写法与 JavaScript 对象访问一一对应:bs.options.urls.local→bs.options.getIn(["urls", "local"]); - 取全部 URL 可调用
bs.options.get("urls").toJS()还原为普通对象,例如 examples/basic/run.js 中的用法:
urls: bs.options.get("urls").toJS()- 运行时动态修改选项使用
setOption/setOptionIn,它们会触发options:set事件(见 browser-sync.js)。
四、核心配置参数速查
README 将完整特性指向官方文档,而仓库自身的 default-config.js 就是这些特性的"默认值事实来源"。以下是与日常使用最相关的一组参数(当前仓库browser-sync版本为3.0.4,见 package.json):
| 配置项 | 默认值 | 作用 |
|---|---|---|
server | false | 使用内置静态服务器,传目录路径或true(以 cwd 为根) |
proxy | false | 代理已有站点,支持{target, ws, middleware, proxyReq, proxyRes} |
port | 3000 | 主服务端口 |
ui.port | 3001 | UI 控制面板端口 |
files | false | 需要监听的文件 glob 列表 |
watch | false | 自动监听(自动忽略node_modules、.git、.sass-cache等目录) |
ignore | [] | 供监听器忽略的额外模式 |
watchEvents | ["change"] | 响应哪些事件(add/change/unlink/addDir/unlinkDir) |
open | "local" | 自动打开local/external/ui/tunnel或false |
ghostMode | 见上文 | 点击/滚动/位置/表单镜像开关 |
injectChanges | true | CSS 与图片变更是否直接注入而非整页刷新 |
notify | true | 浏览器内的小气泡通知 |
reloadDelay | 0 | 文件变更到触发 reload 的延迟(毫秒) |
reloadDebounce | 500 | 事件静默窗口后统一发送 reload |
logLevel/logPrefix | "info"/"Browsersync" | 日志级别与前缀 |
socket | 见源码 | Socket.IO 的path(默认/browser-sync/socket.io)、namespace(默认/browser-sync)、clients.heartbeatTimeout(5000)等 |
cors | false | 为静态资源添加 CORS 头 |
single | false | 为 SPA 开启 history 回退中间件 |
CLI 侧对应的参数定义在 cli-options/opts.start.json,例如-s/--server、-p/--proxy、-w/--watch、-f/--files、--port、--no-open、--no-notify、--no-snippet、--tunnel、--config、--cors等,可供查阅完整的命令行形态。
五、实战:安装、启动与三种运行模式
5.1 安装与前提
在 Node.js(package.json中engines.node为>= 8.0.0,见 package.json)环境中,通过 npm 安装即可:
npm install browser-sync运行 Browsersync 有两种方式:CLI 命令与Node API。无论哪种方式,首次页面响应时都会执行上文所述的 snippet 注入,因此页面需包含<body>标签。
5.2 模式一:内置静态服务器(server)
CLI 方式,监听当前目录并自动打开浏览器:
browser-sync start --server --files "css/*.css, *.html"其中--files指定监听文件,-w(--watch)可启用自动监听模式。等价命令简写为:
browser-sync -s app -wbin.ts中展示了"无命令智能推断":直接提供路径会启动 server 模式,例如browser-sync app/src等价于browser-sync start --server app/src;如果当前目录存在index.html,直接运行browser-sync也可启动(见 lib/bin.ts)。
Node API 方式(参考 examples/basic/run.js):
const bs = require("browser-sync").create(); bs.init({ server: "./app", open: false, watch: true, online: false }, (err, bs) => { console.log(bs.options.get("urls").toJS()); });5.3 模式二:代理已有站点(proxy)
当后端服务已存在,只需给其套上同步与注入能力时使用代理模式:
browser-sync start --proxy "localhost:8080" --files "css/*.css"CLI 也支持直接传入 URL 触发代理:browser-sync https://example.com,若同时还想托管静态文件可写成browser-sync https://example.com htdocs/themes/example(见 bin.ts)。代理相关的配置项(target、ws、middleware、proxyReq、proxyRes)定义于 default-config.js,WebSocket 代理通过--ws或proxy.ws开启。
5.4 模式三:Snippet 模式(不托管、不代理)
如果不使用server也不使用proxy,Browsersync 会进入snippet 模式:仅启动一个轻量服务用于提供客户端脚本与 Socket,由你在自己的页面里手工插入生成的脚本标签。这正是 README 强调"页面必须有<body>标签"的典型场景——因为整个模式都建立在注入动作之上。此模式下日志默认会打印 snippet 内容(logSnippet: true)。模式判定逻辑见 options.ts 的setMode:优先server、其次proxy、否则snippet。
5.5 常用辅助命令
browser-sync init:生成bs-config.js配置文件;browser-sync reload:通过 HTTP 协议通知浏览器刷新(支持--port,见 bin.ts);browser-sync recipe <name>:生成某种构建工具集成的样板文件(如browser-sync recipe gulp.sass)。
这四条命令(start/init/reload/recipe)在 lib/bin.ts 中注册,是 CLI 的全部入口。
5.6 启动流程一览
一次启动并非只做"开一个端口",而是按序执行一组异步任务,完整清单见 lib/async-tasks.js:查找空闲端口 → 为代理申请额外端口 → 检测在线状态 → 解析内联插件 → 计算 URL 等派生选项 → 设置内部事件 → 建立文件监听 → 合并中间件 → 启动 HTTP(S) 服务 → 启动 HTTPS 隧道(可选)→ 启动 Socket.IO → 启动 UI → 合并 UI 设置 → 初始化用户插件。其中 Socket.IO 的连接参数由 templates/connector.tmpl 生成注入客户端的___browserSync___全局对象,完成socketConfig/socketUrl/options的传递。
六、小结
回到 README 的两条主线:
- Snippet 注入是 Browsersync 一切同步能力的地基——它依赖响应改写中间件,在
<body>后插入异步脚本,通过snippetOptions可完全自定义匹配规则、注入函数、白名单与黑名单,并有excludedFileTypes与 IE8 降级处理兜底; - Immutable 选项系统从 2.x 起重构了内部状态管理,公共 API 保持不变,但读取选项的方式从
bs.options.urls.local迁移为bs.options.getIn(["urls", "local"]),这是升级迁移的核心要点。
掌握这两点后,无论是用 CLI 快速起步,还是用 Node API 在构建脚本中深度集成,你都能准确预判 Browsersync 的行为,并能在"页面没有 body、代理 WebSocket、注入位置需要调整"等边界场景下从容配置。
延伸阅读(本仓库内):
- 注入逻辑实现:lib/snippet.js、lib/connect-utils.js
- 注入规则测试:test/specs/server/server.snippet.js
- 选项默认值与转换:lib/default-config.js、lib/options.ts
- 实例与选项读写 API:lib/browser-sync.js
- CLI 实现与参数:lib/bin.ts、cli-options/opts.start.json
- API 运行示例:examples/basic/run.js
- 开发工具
- 前端
- CLI
【免费下载链接】browser-sync
Keep multiple browsers & devices in sync when building websites. https://browsersync.io
相关推荐
RAG-Challenge-2并行处理指南:大幅提升PDF解析效率
RAG Challenge 2并行处理指南:大幅提升PDF解析效率 RAG Challenge 2是一个在企业RAG挑战赛中赢得所有类别的RAG系统实现,其核心
人工智能RAG提示工程Instatic视觉编辑器插件开发服务:打造定制化CMS扩展的终极指南
Instatic视觉编辑器插件开发服务:打造定制化CMS扩展的终极指南 Instatic作为一款现代化的自托管视觉CMS系统,不仅提供了强大的内容管理功能,更拥
CMS后端前端VideoLingo云端同步:多设备状态同步机制深度解析
VideoLingo云端同步:多设备状态同步机制深度解析 引言:多设备协作的挑战与机遇 在当今分布式工作环境中,视频翻译处理往往需要在多个设备间无缝切换。你可能
音视频语音视频处理AI 应用大模型
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考