搭建支持内网私有 npm 仓库的 unpkg CDN 站点

摘要

本文对 unpkg 的服务和实现原理作了简单介绍,并提供了私有化部署的 fork 修改方案参考。希望能对有此类需求的同学提供一定的帮助。

目录
[隐藏]

1 unpkg 简介

1.1 什么是 unpkg

unpkg 是一个实现对 npm 上的资源提供 CDN 的服务。官方的介绍是这样的:

The CDN for everything on npm.
unpkg is a fast, global content delivery network for everything on npm.

unpkg 官方地址为: https://unpkg.com

1.2 使用基于 unpkg 的 CDN 服务

unpkg 引用资源的基本格式是这样的:

unpkg.com/:package@:version/:file

以下为具体的例子:

# 直接访问 npm 包:会跳转至最新版本,可以以目录的形式查看该包的具体资源列表
https://unpkg.com/console-log-colors/

# 以目录的形式,查看某个版本的资源列表
https://unpkg.com/browse/console-log-colors@0.3.5/

# 查看一个文件的内容
https://unpkg.com/browse/console-log-colors@0.3.5/src/index.js

# 查看一个文件的内容(raw 原始源码,可作为 CDN 资源的引用)
https://unpkg.com/console-log-colors@0.3.5/src/index.js

以 CDN 方式引用 bootstrap 资源的示例:

<link rel="stylesheet" href="https://unpkg.com/bootstrap@5.2.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
<script src="https://unpkg.com/bootstrap@5.2.3/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>

1.3 unpkg 的实现原理

npm 包的更新与下载是通过 http://registry.npmjs.com 提供的标准接口实现的,可以通过该 registry 标准接口服务获取一个包的最新信息。获取到的包信息中包含了其所有版本的基本信息和 tgz 下载地址。示例:

unpkg 的原理即基于 http://registry.npmjs.com 提供的标准服务接口,在访问一个包时从 npmjs 获取包信息(并缓存一定的时间),从包信息中取得查询版本或最新版本的信息,拼接出 tgz 下载地址,然后请求下载并以流的方式解压,将解压后的资源进行处理后返回。如果是目录形式或文件预览的地址请求格式,还会进行服务器端渲染(React),返回渲染后的 html 页面。

由于 npm 包具体版本的资源内容是不会变更的,unpkg 对每一个确定性的版本目录或文件都设置了长达一年的 http 长缓存 header 信息,在标准浏览器下只要成功访问过一次,后续基本都会仅从浏览器缓存中读取,不再请求服务器资源。

2 unpkg 在国内

在国内类似的资源,可以搜索到且比较稳定的有如下两个:

简单分析了解一下可以得出如下初步的结论:

  • elemecdn 是完全基于 unpkg 官方源码进行的私有化部署,界面、功能表现、可访问资源均基本与 unpkg 官方表现一致。从异常资源报错信息来看缓存资源应该存放于阿里云 CDN 上。
  • zhimg 的访问形式基本与 unpkg 保持一致,但在目录访问的界面上有一定的差异,可以简单推测为参考 unpkg 做了个性化定制。而且添加了白名单机制,并非所有的 npm 包资源都可访问到。

此外,它们均没有提供官方的对外开放服务声明,也就是说个人学习研究默认可以白嫖,企业级应用是需要避免使用的,以免带来不确定的生产级问题。

3 部署支持私有 npm 仓库的 unpkg

3.1 为什么要私有化部署

  • unpkg 官方 CDN 服务器在境外,国内部分地区访问速度并不理想。
  • elemecdn、zhimg 等国内可用资源不能作企业级服务。
  • 内网私有 npm 仓库上的包无法通过外部服务访问。
  • 提供一个快捷浏览内网 npm 私有包资源的渠道。
  • more...

就个人开发来说,unpkg 服务提供的目录访问方式,可以让我们无需下载即可方便快捷的了解一个内网私有 npm 包的资源。

3.2 私有化部署存在的问题

unpkg 代码仓库是开源的,因此我们可以基于其开源仓库代码进行私有化部署:

然而,直接使用官方仓库进行部署基本是行不通的,其也没有提供明确的私有化部署说明文档。根据个人分析总结,官方仓库存在的问题:

  • 以 unpkg.com 的开发和部署为主,没有私有化部署的直接说明文档,私有化部署一般需作部分逻辑修改。
  • 默认使用了 CLOUDFLARE 上的服务做统计。私有化部署一般不会使用,可修改代码移除。
  • 近两年未更新,社区有少量 PR 但无人合并。
  • more...

3.3 基于官方仓库修改以适配私有化部署

前文已经做过分析说明,直接基于官方仓库代码进行私有化部署多多少少的会存在一些问题。我们可以 fork 官方仓库,修改源码以适配私有 npm 仓库的部署。

下面是本人 fork 官方仓库并做了一些修改适配的仓库,并添加了开发与私有部署的步骤指引:

该仓库的主要修改点有:

  • 支持并使用 pnpm 安装和管理依赖。
  • 将所有外部依赖包全部升级至当前最新版本并进行了兼容(2023-02)。
  • 升级并适配了 React 18。
  • 包下载地址由拼接改为从包信息中读取。私有化部署的下载地址可能与标准格式有差异。
  • 支持 .env[.local|.prod] 的方式配置环境变量,实现简单的私有化部署配置方式。
  • 支持 CLOUDFLARE 统计开关,默认关闭。
  • 支持 Google Analytics 配置为私有 ID,不配置默认为关闭。
  • 参考了部分官方仓库未合并的 PR 的修改。
  • More...

你也可以基于仓库提交记录进行详细了解。

可以展望的更多改进:

  • 文件首次访问慢(因为需要下载 tgz 并解压),可考虑对 tgz 包进行服务端缓存,开发包缓存策略
  • 支持需要授权的私有包请求(官方已有未合并的相关 PR)
  • More...

3.4 unpkg 私有化部署流程参考

拉取代码并安装依赖:

git clone https://github.com/renxia/unpkg.git
cd unpkg
pnpm i

创建并编辑 .env.prod 内容,设置相关环境变量配置:

cp .env.sample .env.prod

项目构建并打包:

set NODE_ENV=production
pnpm build
pnpm pack

服务器部署:

tar zxvf unpkg-1.0.0.tgz
cd package
npm i --omit dev
# pnpm i -P
pm2 -n unpkg start.js

也可以基于以上命令编写 dockerfile,基于 docker 镜像进行部署。

4 开源项目结合免费 CDN 服务的另类玩法

除了 unpkg,著名的针对开源项目提供免费 CDN 服务的 jsdelivr 也支持 npm 资源的 CDN 访问。此外,它还支持 GitHub 仓库、WordPress 插件的资源 CDN 访问服务。由于 GitHub 在国内访问体验极差,基于 jsdelivr 服务实现代理请求的方案也衍生了许多有意思的开源实现。

unpkg 和 jsdelivr 的基本目标是为开源社区项目提供一种免费 CDN 服务方案。但基于其基本服务能力和开源社区的开放能力,衍生了许多曲径通幽效果的应用方案。

4.1 服务请求代理加速

npm 和 github 在国内访问的速度体验不太好,基于免费 CDN 服务做功能扩展,可实现对开源社区资源的加速访问。

如著名的 hunshcn/gh-proxy 项目,基于 CDN 服务实现对 github 资源代理请求。ghproxy 的基本能力:

  • 文件访问: https://mirror.ghproxy.com/github.com/lzwme/blog-examples/blob/main/README.md
  • 附件下载:wget https://mirror.ghproxy.com/https://github.com/stilleshan/ServerStatus/archive/master.zip
  • 仓库 clone:git clone https://mirror.ghproxy.com/github.com/lzwme/scoop-proxy-cn

基于 ghproxy 的基本能力,则又可衍生许多针对 github 服务加速的项目。如:

  • 资源类项目加速。如 lzwme/scoop-proxy-cn 即是本人尝试性创建的一个这样的仓库资源。它是一个 scoop 应用资源镜像库,基于 ghproxy 的能力进行下载资源链接转换,针对国内用户使用 scoop 安装从 github 下载的应用提供加速支持。
  • github 浏览加速。如 du33169/gh-proxy-buttons 是一个基于 gh-proxy 的油猴脚本,为 github 中的特定链接(releases、文件、项目地址)添加一个悬浮按钮,提供代理后的加速链接。此类魔改脚本非常多。
  • more...

github 全站代理访问。

  • https://github.com/netnr/workers
  • https://cors.zme.ink/https://github.com/lzwme/

至于 npm 资源,则有搭建私有 npm 的完整开源解决方案(如 cnpm 等),结合 unpkg 则可实现任意 npm 资源的极速访问。

其他类似 ghproxy 服务的项目:

  • (git-cloner/gitcache)[https://github.com/git-cloner/gitcache]

4.2 静态网页托管

提供静态托管服务的有许多,如 gh-pages、Vercel 等,但都会因为合规问题,容易被防火长城拦截,因 DNS 污染而无法访问。

基本需求:简单的托管在线 demo 用于演示。只需要将静态网站项目发布为 npm 包,然后即可通过 unpkg 服务进行访问。示例:

  • https://unpkg.com/cocos2d/tests/public/index.html
  • http://10.49.121.43:8080/@gf/html-print@0.0.1/demo/comm/include.html

4.3 附件网盘、另类图床

网站 CDN 服务大多用于 css、js 等资源的托管,但也可以上传附件、图片等。于是出现了利用 unpkg、jsdelivr 服务托管图床的方案。

  • 对于 github:创建仓库并上传图片资源,然后利用 jsdelivr 服务引用。
  • 对于 npm:将 github 图床仓库发布为 npm 包,基于 unpkg 服务引用图片。
  • 基于 github actions 创建自动发布任务,新图片提交则发布新版本至 npm。

示例:

  • http://unpkg.com/yongruizhang-blogimg/

需注意:

  • 将 npm 作为图床可能会因包体积过大引发封号风险,一般单个包应不超过 1G。过大的资源包可以分多个包发布。
  • jsdelivr 对 github 仓库大小有限制,不超过 50M。

这类方案如果流行起来,npm 及各种 npm 镜像都要被玩坏了。

5 总结与参考

本文对 unpkg 的服务和实现原理作了简单介绍,并提供了私有化部署的 fork 修改方案参考。希望能对有此类需求的同学提供一定的帮助。

除了 unpkg,著名的针对开源项目提供免费 CDN 服务的 jsdelivr 也支持 npm 资源的 CDN 访问。此外,它还支持 GitHub 仓库、WordPress 插件的资源 CDN 访问服务。由于 GitHub 在国内访问体验极差,基于 jsdelivr 服务实现代理请求的方案也衍生了许多有意思的开源实现,有兴趣可以进一步的进行相关探索。