一个将私有源npm包处理为离线包的自动化工具

1,782 阅读5分钟

一、背景

npm作为一种包管理工具,无论你是泛前端还是大前端都已经离不开它😺

目前已经有很多成熟的开源 npm 源可以使用,如npm、cnpm、taobao等等,但是出于稳定性、私密性、安全性考虑,公司发展到一定都会搭建自己公司的私有仓库🧐

那么问题来了,私有包只能在公司内网访问,在访问不了公司内网时,比如交付场景下,需要交付代码且需在客户现场二次开发,私有源npm包是无法安装的😰

二、现状

私服安装逻辑👇

image.png

举个🌰:业务工程demo npm包依赖如下,其中@scope1作用域的包为私有包,即只在私有源上有相关包,在非公司内网,@scope1作用域的包是无法安装的🤯

// package.json
{
  "name": "dmeo",
  "version": "1.0.0",
  "scripts": {
    "start": "npm run serve",
  },
  "devDependencies": {
    "@commitlint/cli": "11.0.0",
    "@commitlint/config-conventional": "11.0.0",
    "@scope1/cli-plugin-babel": "^1.0.14",
    "@scope1/cli-plugin-vue": "^1.0.14",
    "@scope1/cli-service": "^1.0.14",
    "autoprefixer": "10.2.5",
    "axios": "^0.21.1",
    "babel-eslint": "10.1.0",
    "babel-plugin-import": "^1.13.3",
    "cross-env": "7.0.3",
    "cssnano": "4.1.10",
    "eslint": "7.21.0",
    "eslint-config-prettier": "8.1.0",
    "eslint-plugin-prettier": "3.1.4",
    "eslint-plugin-vue": "7.7.0",
    "globby": "^11.0.3",
    "husky": "^4.3.8",
    "lib-flexible": "^0.3.2",
    "lint-staged": "10.5.4"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "vue": "2.6.12",
    "vue-router": "3.5.1",
    "vuex": "3.6.0",
    "webpack-dev-server": "^3.11.2"
  }
}

三、需求

  • 在非公司a内网,实施现场b有私服,即公有包可走现场私服b下载,公司a私服的包无法下载,仅需处理a私服包

    • @scope1作用域的包(私有包)也是可以正常安装的👌
    • 如果@scope1/package1包的依赖子包@scope1/subpackage1,也是可以正常安装的@scope1/subpackage1包的依赖子包...即子包依赖存在@scope1都是可以正常安装的👌
    • @scope1/package1依赖子包@scope1/subpackage1@1.1.8, @scope1/package2依赖子包@scope1/subpackage1@2.0.4,@scope1/subpackage1版本不一致是可以正常安装的👌
  • 在非公司a内网,实施现场b无私服,即所有的包都需要处理,这时候的处理方案是搭建私服

四、常见解决方案

4.1 有私服场景

大家这时候通常会想到以下解决方案🤢

  • 将npm私有包发布到开源npm源
    • 存在包的稳定性、私密性、安全性问题🙅‍♂️
  • 将npm私有包发布到客户内部的私有npm源
    • 需要将包源码交付到客户内部,存在泄露私密性问题🙅‍♂️
    • 需要按照客户私服npm包发布流程发布包,沟通、成本高😷
  • 将私有包拎出来放在一个仓库,仓库部署在客户系统,将依赖改为仓库地址,利用仓库共享npm包
    • 需要手动将私包拷贝到git仓库,如果私有包的子包依赖中也有私有包,需要手动修改子包依赖的私有包npm install地址,依次递归;效率低且容易出错🙅‍♂️
    • 客户现场需要多部署一个仓库,走客户的部署流程,成本高 😷
  • 在客户现场搭建一个临时私服,将私有包放到私服上,通过私服安装项目依赖
    • 搭建临时私服,需要占用机器资源、消耗人力,成本高😷
    • 私服存在稳定性问题,机器受客户环境影响🙅‍♂️

4.2 无私服场景

verdaccio搭建私服

五、实践方案

npm install <package> ,这里的第三个参数package通常就是我们所要安装的包名,默认配置下npm会从默认的源 (Registry) 中查找该包名对应的包地址,并下载安装

但在 npm 的世界里,除了简单的指定包名, package 还可以是一个指向有效包名的文件夹路径。本方案基于此实现,具体如下👇

本工具设计为cli(命令行界面)

  • 提供两种命令privatify package <package> [scope]privatify scope <scope>供具体工程处理私有包,实现私有源npm包只需执行一个命令即可自动处理成离线包,且处理了私有包子包依赖也存在私有包的场景,私有包子包依赖的私有包版本不一致处理,无需其他操作,以处理私服的场景

  • 无私服的场景,提供privatify register create命令快速搭建私服,privatify register install命令在业务工程将npm包缓存到私服仓库

npm-package-privatify

5.1 介绍

一个将私有源npm包处理为离线包的自动化工具

github地址:github.com/zxyue25/npm…

5.2 使用

安装

npm install -g npm-package-privatify

命令

1、 privatify package <package> [scope]

将所声明的npm包package处理为离线包,并查找离线包package子包依赖是否包含scope下的子包,如包含也处理为离线包

scope支持通配符, scope匹配规则:www.npmjs.com/package/min…

参数<package>:npm包名

// 将packageName处理为私包
privatify package packageName

执行成功后,有两处变化:

a、本工程package.json

// 修改前
...
"dependencies|| devDependencies": {
    "packageName": "^0.3.29",
},
// 修改后
...
"dependencies|| devDependencies": {
    "packageName": "file:private/packageName-${version}.tar.gz",
},

b、路径private下新增文件packageName-${version}.tar.gz

参数[scope]:查找包是否存在scope下的依赖,有则会处理子包私有包

privatify package @scope1/packageName @scope1

执行成功后,除了跟上述一样有两处变化,如果子包有依赖scope下的私有包,还有两处变化:

c、private/@scope1/packageName下的package.json文件

// 修改前
...
"dependencies|| devDependencies": {
    "@scope1/subPackageName": "^0.3.29",
},
// 修改后
...
"dependencies|| devDependencies": {
    "@scope1/subPackageName": "file:../../private/@scope1/packageName-${version}.tar.gz",
},

d、路径private/@scope1下新增文件subPackageName-${version}.tar.g

2、privatify scope <scope>

提供快捷操作,将所声明作用域下的包统一处理为离线包,查找包是否存在scope下的依赖,有则会处理子包私有包

scope支持通配符, scope匹配规则:www.npmjs.com/package/min…

参数1<scope>:要查找的作用域

// 将在@scope1下的包处理为私包
privatify scope @scope1
3、privatify create <registry-name>

创建一个npm私服仓库

参数:

<registry-name>:私服仓库名称


5.3 实现原理图

npm包处理为离线包原理图: 未命名文件 (1).png

搭建私服原理图: 待补充

六、总结

其实是业务中真实遇到私有源npm包需处理为离线包的场景,本方案从业务出发进行场景扩展,并抽离为通用方案~