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

3,345 阅读6分钟

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

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 官方地址为: 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 包资源都可访问到。

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

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

4.1 为什么要私有化部署

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

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

4.2 私有化部署存在的问题

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

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

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

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

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

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

该仓库的主要修改点有:

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

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

可以展望的更多改进:

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

4.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 镜像进行部署。

5 总结与参考

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

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