简介
逛 GitHub 时经常会遇到一种情况:仓库里明明放了一个 HTML 页面,但点开 view raw 后浏览器看到的只是源码,没法像网页那样直接预览。
为了解决这个问题,我做了一个小工具:GitHub Preview。它不需要克隆仓库,也不需要下载文件,只要给它一个 GitHub 的 raw 或 blob 链接,就可以直接在线预览页面。使用方式也很简单:把链接粘进去,点击 Preview 即可。
原理
Content-Type
问题的根源其实不复杂:浏览器能不能“把文件当网页渲染”,很大程度上取决于响应头里的 Content-Type。比如一个正常 HTML 文件返回的是:
Content-Type: text/html
这告诉浏览器:你要把这个文件当作 HTML 渲染。但是,GitHub 托管仓库文件的服务器 (raw.githubusercontent.com) 返回的是:
Content-Type: text/plain
这告诉浏览器把它当成纯文本展示,而不是当成网页解析。这就是为什么 raw 链接只能看代码,却不渲染网页。
Service Worker
这个工具的核心是 Service Worker。简单来说,它就相当于一个浏览器层面的 HTTP 代理,可以拦截并处理当前站点下发出的请求。
我把预览地址设计成这样:
https://pro-2684.github.io/GitHub-Preview/owner/repo/ref/path/to/index.html
当浏览器访问这个地址时,Service Worker 会:
- 拦截请求
- 把路径映射到 raw.githubusercontent.com
- 拉取原始文件内容
- 按扩展名修正 Content-Type (例如
.html -> text/html, .js -> application/javascript...)
- 再把结果返回给浏览器
这样一来,浏览器拿到的就是内容一样,但 Content-Type 被修正的响应,于是 HTML 就能正常渲染了。更重要的是,这种方式不只对首页生效。页面里后续加载的脚本、样式、图片,以及运行时的 fetch / XHR 请求,也会继续经过同一套映射逻辑,因此相对路径资源均能能正常使用。
关键代码
核心逻辑其实很简单,就是把请求转发到 raw.githubusercontent.com,再根据规则修正响应头:
// 请求原文件
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${rest.join("/")}`;
const res = await fetch(rawUrl);
// 修改响应头
const newHeaders = new Headers(res.headers);
newHeaders.set(
"Content-Type",
fixMIME(rest[rest.length - 1], res.headers.get("Content-Type")),
);
// 返回结果
return new Response(res.body, {
status: res.status,
statusText: res.statusText,
headers: newHeaders,
});
其中 fixMIME() 会根据扩展名推断类型,例如:
.html -> text/html
.js -> application/javascript
.css -> text/css
.json -> application/json
首页则负责把用户输入的链接统一解析成 raw 格式,再跳转到预览地址。
缺陷
- 如果网页使用了
/assets/app.css 这种绝对地址,请求会跑到当前预览站点根路径下,从而导致 404。
- Service Worker 无法拦截浏览器对其它 Service Worker 脚本的请求。因此目标页面如果自己还想注册另一个 Service Worker,就会出现 404 错误。
对比与总结
和现有方案相比,这个项目最大的特点是:完全前端实现,不修改响应内容,不依赖后端代理。
链接