⤴Top⤴

NPM 私有仓库搭建

博客分类: 前端
修改内容:add unpkg and lru cache

NPM 私有仓库搭建

NPM 私有仓库搭建

目前文章主要摘自npm 私有仓库工具 Verdaccio 搭建 - 匠心博客,后续会补上自己的实践

为什么需要 npm 私有仓库

在构建前端项目的过程中,核心包、开发脚手架、前端组件库等 JS SDK 依赖资源需要依托于 npm 去管理。而对于企业开发来说,不能将核心代码上传到完全开放的公网环境,所以一般会搭建企业内部 npm 私有仓库。

npm registry

用户 install 后向私有 npm 发起请求,服务器会先查询所请求的这个模块是否是我们自己的私有模块或已经缓存过的公共模块,如果是则直接返回给用户;如果请求的是一个还没有被缓存的公共模块,那么则会向上游源请求模块并进行缓存后返回给用户。上游的源可以是 npm 仓库,也可以是淘宝镜像。

如何搭建

npm 私有仓库搭建有以下几种方式:

  1. 付费购买 npm 企业私有仓库
  2. 使用 git + ssh 这种方式直接引用到 GitHub 项目地址
  3. 开源代码源代码方式或者 docker 化构建

常用的 npm 私有仓库框架:

常用的仓库地址:

  1. npm
  2. cnpm
  3. taobao
  4. nj
  5. rednpm
  6. npmMirror
  7. edunpm

Verdaccio

Verdaccio 是一个 Node.js 创建的轻量的私有 npm proxy registry。提供 Docker 和 Kubernetes 支持;与 yarn, npm 和 pnpm 100% 兼容;forked 于 sinopia@1.4.0 并且 100% 向后兼容:

verdaccio

要想在本地体验一下的话十分方便,只用全局装上依赖并启动服务即可:

# 安装
npm install -g verdaccio
yarn global add verdaccio
pnpm install -g verdaccio

# 安装完毕后,启动
$> verdaccio
warn --- config file  - /home/.config/verdaccio/config.yaml
warn --- http address - http://localhost:4873/ - verdaccio/4.8.1

打开 http://localhost:4873 就可以看到已经启动起来了:

verdaccio page

下面介绍一下 docker 部署方式,当然还有其他的方式,这里就不做介绍了:

# 拉取 Verdaccio 的 docker 镜像:
docker pull verdaccio/verdaccio
# 在根目录下创建 docker 文件
mkdir -p ~/docker/data
cd ~/docker/data
# 从 git 拉取示例到 data 到目录下
git clone https://github.com/verdaccio/docker-examples
cd ~/docker/data/docker-examples
# 移动配置文件
mv docker-local-storage-volume ~/docker/verdaccio
# 设置文件夹权限
chown -R 100:101 ~/docker/verdaccio
# 启动镜像 - 使用 docker-compose 启动:
cd ~/docker/verdaccio
docker-compose build
docker-compose up

# 或者使用 docker run 命令启动:
V_PATH=~/docker/verdaccio; docker run -it --rm --name verdaccio \
  -p 4873:4873 \
  -v $V_PATH/conf:/verdaccio/conf \
  -v $V_PATH/storage:/verdaccio/storage \
  -v $V_PATH/plugins:/verdaccio/plugins \
  verdaccio/verdaccio

cnpmjs.org

cmpjs.org 服务搭建是需要数据库支撑的,官方提供了 mysql、sqlite、postgres、mariadb 等数据库的支持,在这里我们选用 mysql 来提供数据服务。具体搭建方式官方文档已经很详细了,这里只是带过一下:

# clone from github
git clone git://github.com/cnpm/cnpmjs.org.git $HOME/cnpmjs.org
cd $HOME/cnpmjs.org

# create mysql tables
mysql -u yourname -p # 登陆
mysql> create database cnpmjs # 创建数据库
mysql> use cnpmjs; # 切换到 cnpmjs 数据库
mysql> source docs/db.sql # 导入 cnpm 数据库配置文件,生成 tables

tables

设置 config/config.js:

module.exports = {
  debug: false,
  enableCluster: true, // enable cluster mode
  enablePrivate: true, // enable private mode, only admin can publish, other user just can sync package from source npm
  database: {
    db: 'cnpmjstest',
    host: 'localhost',
    port: 3306,unknown database cnpmjs
    username: 'cnpmjs',
    password: 'cnpmjs123'  
  },
  admins: {
    admin: 'admin@cnpmjs.org',
  },
  syncModel: 'exist' // 'none', 'all', 'exist'
}

安装所有依赖和启动,服务启动后会监听两个端口,分别是:

# 安装依赖
npm install --build-from-source --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node

# 启动服务
npm start

私有包管理

发布 publish

我们成功启动服务后,便可以进行发布 npm 包,并在上面进行管理和查看,一般我们有以下几种发布方式:

指定 registry

# 添加用户 - 输入 username、password 以及 Email 即可
npm adduser --registry http://localhost:4873

# 登录
npm login --registry http://localhost:4873
# 上传私有包
npm publish --registry http://localhost:4873

配置 .npmrc/.yarnrc

# .npmrc
registry=http://localhost:4873

# .yarnrc
registry "http://localhost:4873/"

关于 .yarnrc 的配置,详情可以参考这里,我们还可以去定义 scope 包,需要注意的是 scope 名字要与 package.json 里的 name 字段保持一致:

# .npmrc
@username:registry=http://localhost:4873

# .yarnrc
"@username:registry" "http://localhost:4873/"

配置 publishConfig

// package.json
{
  "publishConfig": {
    "registry": "http://localhost:4873"
  }
}
[GetColors] npm publish
# npm notice 
# npm notice 📦  @image-process-library/get-colors@1.0.0
# npm notice === Tarball Contents === 
# npm notice 518B  package.json           
# npm notice 1.2kB README.md              
# npm notice 1.8kB __tests__/index.spec.ts
# npm notice 1.6kB src/index.ts           
# npm notice === Tarball Details === 
# npm notice name:          @image-process-library/get-colors       
# npm notice version:       1.0.0                                   
# npm notice package size:  2.1 kB                                  
# npm notice unpacked size: 5.1 kB                                  
# npm notice shasum:        58c078a5258de6df3c75fa58f953aa4006b49bb0
# npm notice integrity:     sha512-VIiqpQD45lpjB[...]lOwgKyP1YKAgA==
# npm notice total files:   4                                       
# npm notice 
# + @image-process-library/get-colors@1.0.0

每次发布时版本号不能相同,否则无法发布成功

安装 install

同样,从 npm 私有仓库下载依赖,我们也有几种方式:

nrm 指定 registry

# 设置仓库源
npm set registry http://localhost:4873
# 安装
npm install

# 或者
npm install <packagename> --registry=http://localhost:4873

上面切换 registry 显然不推荐,需要指定和切换不同的 npm 源,当然我们可以通过管理工具 nrm 来稍微减少点工作量,具体可以参考这篇博客 👈

配置 .npmrc/.yarnrc

配置同发布,都需要在项目根目录下创建。

撤回 unpublish

# 彻底移除一个包:
npm unpublish <packagename> --force

# 移除指定个一个版本:
npm unpublish <packagename>@1.0.0

scope 管理发布包

经常有看到 @xxx/yyy 类型的开源 npm 包,原因是包名称难免会有重名,如果已经有人在 npm 上注册该包名,再次 npm publish 同名包时会告知发布失败,这时可以通过 scope 作用域来解决:

// package.json
{
  "name": "@username/project-name"
}

需要注意的是,如果是发布到官方 registry,scope 一定要是自己注册的用户名,而如果是发布到自己的 npm 私服,scope 可以不是用户名

作用域模块默认发布是私有的,发布到官方 registry 时,直接 npm publish 会报错,原因是只有付费用户才能发布私有 scope 包,免费用户只能发布公用包,因此需要添加 access=public 参数。npm 私服则不用加该参数:

// package.json
{
  "publishConfig": {
    "access": "public"
  }
}
# 安装
npm install @username/project-name

unpkg

什么是 unpkg

unpkg 是一个内容源自 npm 的全球快速 CDN。它部署在 cloudflare 上,在大陆地区访问到的是香港节点。它支持 h/2 和很多新特性。

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

<!-- 使用固定版本号 -->
<!-- unpkg.com/react@16.7.0/umd/react.production.min.js -->
<!-- unpkg.com/react-dom@16.7.0/umd/react-dom.production.min.js -->

<!-- 使用语义化版本范围,亦可忽略版本和标签,直接使用最新的版本 -->
<!-- unpkg.com/react@^16/umd/react.production.min.js -->
<!-- unpkg.com/react/umd/react.production.min.js -->

<!-- 如果忽略了文件的路径,unpkg 会提供 package.json 里指定的文件,或降级到 main -->
<!-- unpkg.com/jquery -->

在网址最后添加斜线 /,可以查看一个包内的所有文件列表。如访问 https://unpkg.com/react/ 👈

那么 unpkg 上的发布流程是怎样的?如果你是 npm 包作者,只需要通过 npm publish 发布到 npm 仓库即可,减少了发布到 CDN 的麻烦。仅需 npm 包中包含 UMD 构建:

  1. 添加 umd(或 dist)目录到 .gitignore 文件中
  2. 添加 umd 目录到 package.json 文件数组(files)中
  3. 发布的时候,使用脚本构建 UMD 打包文件到 umd 目录

搭建 unpkg 服务

unpkg 是不能直接读取私服的包的,所以我们需要本地架设 unpkg 服务器:

1. 拉取[源码](https://github.com/mjackson/unpkg)
2. 在 package.json 的script 脚本添加 start 命令 - "start": "NODE_ENV=production node server.js"
3. npm run build 构建生成 server.js 文件
4. npm run start 启动服务
5. 通过访问 localhost:8080 看是否正常访问

接下来我们在 modules/utils/npm.js 里做一些修改:

# 兼容 http
+import http from 'http';

function get(options) {
  return new Promise((accept, reject) => {
+    if(options.isHttp) {
+      delete options.isHttp;
+      delete options.agent;
+      http.get(options, accept).on('error', reject);
+    } else {
      https.get(options, accept).on('error', reject);
+    }
  });
}

/**
 * Returns an object of available { versions, tags }.
 * Uses a cache to avoid over-fetching from the registry.
 */
export async function getVersionsAndTags(packageName, log) {
  const cacheKey = `versions-${packageName}`;
  const cacheValue = await cache.get(cacheKey);

+  /** 
+   * packages in scope which is in cache black list will not use cache
+  */
+  const blackList = (process.env.VERSION_CACHE_BLACK_LIST || '').split(',')
+  const scope = isScopedPackageName(packageName) ? packageName.split('/')[0] : null
+  if (scope && blackList.includes(scope)) return await fetchVersionsAndTags(packageName, log)

# ...

unpkg 默认是使用 lru(Least Recently Used) 做缓存机制的,当缓存满了的时候,不经常使用的就直接删除,挪出空间来缓存新的对象。也可以加个手动去除缓存的操作:

export async function delPackageMetaCache(packageName) {
  if (!packageName || typeof packageName !== 'string') return
  const versionsKey = `versions-${packageName}`
  await cache.del(versionsKey)
}
app.use('/cleanCache/:scope?/:packageName', async (req, res) => {
  try {
    const {
      scope,
      packageName
    } = req.params;
    const pn = scope ? `${scope}/${packageName}` : packageName;
    await delPackageMetaCache(pn);
    res.send('ok');
  } catch (e) {
    console.log(e);
  }
})

最终修改的代码可以参考这里 👈

参考链接

  1. npm 私有仓库工具 Verdaccio 搭建 - 匠心博客
  2. 使用 unpkg 来读取我们的私有库的包 By 弦奏