一文带你搞懂搭建企业级的 npm 私有仓库
作者:小飞猫
转发链接:
https://mp.weixin.qq.com/s/Q9JSBXx7eiq3GuAvg1itIw
前言
npm 作为一种包管理工具,无论你是泛前端还是大前端都已经离不开它。它的出现方便了万千少年。让我们跨过了 Ctrl+C、Ctrl+V,通过 npm install x 的方式将别人的优秀代码模块引入到自己的项目中。这些优秀的模块能被共享的原因,一方面是有 npm 这么一个包管理工具,另外就是 npm 仓库。
对于 npm 仓库,如果你还停留在使用 npm 或者 cnpm 这类官方源的情况下。那么你有必要想想如何搭建一个私有的 npm 仓库。下面从三个方面讲解企业级 npm 私有仓库搭建那些事儿,分分钟教会你。
- 为什么需要搭建公司的私有 npm 仓库
- 如何搭建私有仓库
- 搭建私有仓库扩展篇
为什么需要搭建公司的私有 npm 仓库
照惯例,先讲讲为啥要搭建私有仓库。目前已经有很多成熟的 npm 源可以使用,比如:
- npm : https://registry.npmjs.org/
- cnpm : http://r.cnpmjs.org/
- taobao: https://registry.npm.taobao.org/
- nj:https://registry.nodejitsu.com/
- npmMirror https://skimdb.npmjs.com/registry/
- ….
在已经有如此多公共仓库的情况下,是否有必要搞一套私有 仓库?重复"造轮子"?还是自嗨?如果从下面几方面来考虑的话,或许能打消心中的疑问。
1. 稳定性
首先是网络访问稳定性,私有仓库因为是自己公司在维护,有什么问题能第一时间处理,比如服务宕机…其次资源的稳定性,试想一下,如果哪天你依赖的某个很重要的模块突然被作者删了,那是不是完犊子了,毕竟我们很多时候都奉行的是“拿来主义”,一旦遇见这种情况,基本上全抓瞎。如果有私有仓库,上面的问题可以从容面对,有效的保障了业务稳定。
2. 私密性
每个公司都有和自己业务强相关的模块,或者对某些开源模块进行个性化的改造,改造后的模块只满足本公司的业务场景,这些模块我们并不希望发布到公共的仓库中去,这时就可以发布到自己的私有仓库在公司内部共享。
3. 安全性
有了私有仓库后,可以在 npm 模块的质量和安全上做文章,能够有效的防止恶意代码攻击。
综上,搭建自己公司的私有仓库完全有必要,这并不是秀。当然,如果你所在的公司比较 mini,对于上面的几点需求并不是那么迫切,使用公共仓库也挺好。但当公司发展到一定规模,在未来可预见的情况下,那就是时候准备搭建自己的私有仓库了。
如何搭建私有 npm 仓库
目前已经有许多成熟开源方案,选择站在巨人的肩膀上不失为一种良策。这里选择 cnpmjs.org (
https://www.npmjs.com/package/cnpmjs.org) 方案,原因有三:
- 目前国内像淘宝这样的大厂内部也是选择的它,足以证明它的可靠性和稳定性
- 扩展性强
- 配置多样化
当然缺点也不是没有,就是部署有那么一丢丢复杂。
准备工作
兵马未动,粮草先行,既然是搭建企业级的应用,基础环境得备好。
- Linux 服务器
- node 环境
- 数据库( Mysql )
- nginx
大概就这些,如果你只是熟悉一下,搭着玩儿。也不一定得用 Linux 服务器,Windows 也行,但是如果是作为线上应用,建议还是使用 Linux 服务器,毕竟 Linux 的稳定性在那里摆着。
下面的示例是在云服务器(Ubuntu)上完成的。
安装
- 安装 cnpmjs.org
建议通过 git 将 cnpmjs.org 的项目源码克隆到服务器本地某个目录下。或者将代码 fork 到自己 git 仓库后,再基于内部仓库进行部署,这样方便以后对源码进行个性化的改造。
git?clone?https://github.com/cnpm/cnpmjs.org.git
安装项目依赖:
npm?i
安装完成后找到项目根目录下的配置文件 config/index.js ,这里配置文件非常多,刚开始可以只关注下面几项即可,详细配置(
https://gitee.com/199253/cnpmjs/blob/master/config/index.js) 戳这里。
- 服务访问端口
registryPort:?7001,?????????//仓库服务访问端口
webPort:?7002,??????????????//web站点访问端口
bindingHost:?'',???//监听绑定的 Host,默认127.0.0.1,外网访问注释掉此项即可,一般我们不会把我们内部端口暴露出去,可以在nginx层做一个转发,所以这个配置可以注释掉。如果直接外网访问,配置为?0.0.0.0
- 数据库配置
database:?{
??db:?'npm',数据库名称
??username:?'admin',//用户
??password:?'admin123',//密码
??//?数据库类型
??//?-?目前支持?'mysql',?'sqlite',?'postgres',?'mariadb'
??dialect:?'mysql',//默认是sqlite,我选择的mysql
??host:?'127.0.0.1',?//数据库服务地址
??port:?3306,????//?端口
??//?数据库连接池使用默认配置就好
??//?目前只支持??mysql?和?postgresql?(since?v1.5.0)
??pool:?{
????maxConnections:?10,
????minConnections:?0,
????maxIdleTime:?30000
??},
??...//其他的暂时不用关注
},
- 是否启用私有模式
enablePrivate:?false,//默认不启用
私有模式下,只有管理员才能发布模块。非管理员发布模块式命名必须以 scopes 字段开头例如:@catfly/packagename。
- 发布前缀
scopes:?['@catfly'],
这个和启用非私有模式配套使用,非私有模式要发布必须配置该项。
- 管理员账号配置
admins:?{
??fengmk2:?'fengmk2@gmail.com',
??admin:?'admin@cnpmjs.org',
??dead_horse:?'dead_horse@qq.com',
}
如果启用私有模式,只有该配置项中的用户可以发布私有包。至于其他的配置项暂时不用关注,后面根据需要再逐渐配置起来。
- 同步模式
//?同步模式选项
//?none:?不进行同步,只管理用户上传的私有模块,公共模块直接从上游获取
//?exist:?只同步已经存在于数据库的模块
//?all:?定时同步所有源registry的模块
syncModel:'exist'
- 数据库我选择的 mysql ,这里不介绍怎么安装 mysql 了,有需要请戳这里(https://www.runoob.com/mysql/mysql-install.html)。当然你也可以选择其他数据库,目前支持 mysql、 sqlite、 postgres、 mariadb,默认是 sqlite。先检查一下数据库服务状态,确保数据库服务没毛病:
- 登录数据库
mysql?-u?root?-p?test123456
- 创建数据库
create?database?npm;
查看数据库列表:
- 创建数据库表cnpmjs.org 项目 docs 目录下已经给我们备好了创建数据库的脚本 db.sql。执行:
source?docs/db.sql;
默认当前操作路径就在 cnpmjs.org 项目下,如果不是,请用 db.sql 的绝对路径。
查看结果:
上面两步完成后,就可以将项目跑起来一睹芳容了。因为我们通过 git 克隆的,所以需要进入到项目目录下执行启动服务的命令
npm?run?start
启动成功后,访问 web 页面,发现之前配置文件中的 web 端口 7002 访问不了。
这是因为服务器防火墙的原因,可以选择关闭防火墙,但是这种方式不推荐;另外一种就是开放指定端口。
iptables?-A?INPUT?-p?tcp?--drop?-j?7002?DROP
如果你是使用的云服务器,需要去云服务控制台,新增安全组,将暴露的端口放开。
端口开放后,访问 web 页面:xxx.xxx.xxx.xx:7002,就可以看见熟悉的部署在本地的 cnpm 页面了。
在上面这张的访问地址可以看到,用了域名,并不是用的 IP+ 端口的形式,因为作为一个企业级的应用,IP+ 端口的方式就如同裸奔一样,建议采用域名的方式。我在自己的云服务域名管理下新增了一个子域名。
然后配置 nginx 将 IP 和域名进行绑定,统一使用默认的 80 端口,尽量不要将私有仓库服务的真实端口和 IP 暴露出来。这里顺便把 nginx 配置也说一下,如果你能接受 IP+ 端口访问的方式,可以跳过下面这一步。
- nginx配置
如果没有安装 nginx,戳这里 (
https://www.runoob.com/linux/nginx-install-setup.html)。找到 nginx 配置文件,在 conf.d 文件夹信息新增 npm.conf 配置文件,这样功能清楚明白,因为很多时候 nginx 不是只代理这个一个服务。
server?{
??listen??80;
??server_name?www.mirrors.catfly.vip;
??#charset?koi8-r;
??#access_log??logs/host.access.log??main;
??location?/?{?
????proxy_pass?http://127.0.0.1:7002/;?#代理到cnpmjs.org提供的web服务
????proxy_set_header????????X-Real-IP?$remote_addr;
??}
??location?/registry/?{
????proxy_pass?http://127.0.0.1:7001/;?#?代理到cnpmjs.org提供的注册服务
????proxy_set_header?X-Real-IP?$remote_addr;
????proxy_set_header?Host?$host;
????proxy_set_header?X-Forwarded-For?$proxy_add_x_forwarded_for;
??}
??#error_page??404??????????????/404.html;
??#?redirect?server?error?pages?to?the?static?page?/50x.html
??#?error_page???500?502?503?504??/50x.html;
??location?=?/50x.html?{
????root???html;
??}
}
重启 nginx:
service?nginx?restart
检查 nginx 状态,防止配置错误,导致重启失败。
通过上面的环境搭建和部署,基本工作就完成了,下面开始验证功能。
功能验证
在验证之前推荐安装一个 npm 源管理模块 nrm ,有了它我们可以在各种源之间自由切换。
npm?i?nrm?-g
安装成功后新增我们自己的私有源到 nrm 源列表中。
nrm?add?catfly?http://www.mirrors.catfly.vip/registry
切换到私有源:
nrm?use?catfly
这个时候本地执行 npm 操作的时候就会去找到我们自己的私有地址。
- 私有包发布注册用户:npm adduser登录私有仓库:npm login登录成功后,发布 npm,在已准备好的模块目录执行:npm publish这个时候可能会出现各种错误,主要是403权限问题,因为私有仓库在不同模式下需要满足不同的条件,例如:在非私有模式(enablePrivate: false)下,当用户不用具备管理员权限,模块命名前缀必须带有配置中规定的 scope,如果不存在或者 scopes 中不包含该 scope 就会报错。在私有模式(enablePrivate: true)下,如果用户不再配置文件的 admins 中,则不允许执行发布操作,反过来如果在,那么它的权限就非常大了,不仅能发布还能删除。所以真实场景下不要让管理员账号泛滥。
- 包下载安装发布成功后,尝试安装发布的私有包:
npm?i?xxxxx
没毛病。
- Web工作台访问私有仓库的web站点通过这个 站点可以对私有包的发布、删除以及下载进行统计,还可以私有包搜索功能。也可以对这个 web站点进行个性化改造。代码、数据都在我们这边,想怎么造就怎么造。
私有 npm 仓库搭建扩展篇
在真实的企业级应用中,在上面的基础上还可以进行扩展,下面介绍一下可以扩展的几个方面:
进程管理
推荐使用 pm2 进行进程管理,虽然项目本身提供了 npm run start 和 npm run stop 的能力,但是这对于一个企业级的应用来说还是太弱了,使用 pm2 的好处如下:
- 随时随地多进程管理
- 完善的监控机制,我们可以清晰地看见整个集群的模式、状态,CPU 利用率甚至是内存大小
- 负责均衡
- 进程守护
- ...
- 全局安装 pm2
npm?i?pm2?-g
- 启动项目
pm2?start?./dispatch.js?//dispatch.js在cnpmjs.org项目的根目录下
执行完后,可以看见该服务的基本信息,简洁明了。
- 查看服务进程信息
pm2?monit?dispatch?//diapatch为当前进程?name
这里可以实时查看进程运行的详细信息,方便平时项目的维护。pm2 还有好多强大的功能,这里就不一一介绍了,有兴趣的戳这里(
https://zhuanlan.zhihu.com/p/58787876)。
私有包存储上云
cnpmjs.org 项目配置项里面有一个 nfs 配置,这里定义了一个 npm 文件系统(NFS)。私有仓库在同步和上传的时候,会交给 NFS 对象相应的函数去处理,NFS 对象返回处理结束之后再返回下载链接,所以通过自定义 NFS 模块可以实现 npm 包的各种定制存储。目前官方默认使用 fs-cnpm,该模块会将上传或者同步的包保存在服务器本地的
/root/.cnpmjs.org/doenloads/ 目录下。这种方式比较传统,一方面随着私有包数量的不断增加,存储资源会是一个瓶颈。另一方面需要定时的备份资源,不然哪天磁盘坏了,那就只有
这个时候将私有包或者同步的资源放到云上就是一个非常好的方案。cnpmjs.org 官方早就为我们想到了这点,给出了下面几种 NFS 模块:
- upyun-cnpm (https://link.jianshu.com?t=https://github.com/cnpm/upyun-cnpm):又拍云存储插件
- fs-cnpm (https://link.jianshu.com?t=https://github.com/cnpm/fs-cnpm):本地存储的插件
- sfs-client (https://link.jianshu.com?t=https://github.com/cnpm/sfs-client):SFS (https://link.jianshu.com?t=https://github.com/cnpm/sfs)(Simple FIle Store)存储插件
- qn-cnpm (https://link.jianshu.com?t=https://github.com/cnpm/qn-cnpm):七牛云存储插件
- oss-cnpm (https://link.jianshu.com?t=https://github.com/cnpm/oss-cnpm):阿里云 OSS 存储插件
这些模块已经能够满足我们绝大部分的场景,如果你有特殊的需求,可以参看nfs模块规范 (
https://www.v2ex.com/t/294255) 进行定制化开发。这里拿阿里云 oss 存储作为示例。
首先在 cnpmjs.org 项目目录下安装 oss-cnpm 模块
cnpm?i?oss-cnpm
然后在云服务控制台 oss 管理中新增了一个 bucket 来存储 npm 包,也可以通过上传路径区分来复用其他 bucket,毕竟在公司中 bucket 资源一般还是比较紧张的。然后修改项目配置文件,将默认的fs-cnpm模块替换成oss-cnpm。
var?oss?=?require("oss-cnpm");
var?nfs?=?oss.create({
??accessKeyId:?'xxxx',
??accessKeySecret:?'xxx',
??endpoint:?'oss-cn-beijing.aliyuncs.com',
??bucket:?'catfly-xxx',
??mode:?'private',
})
var?config?=?{
??...,
??nfs:nfs,
??...
}
重启项目,这个时候再发布或者同步资源的时候,服务器本地目录不会有新发布或同步的包了,在 oss 对应的 bucket 里面能找到刚刚发布或者同步的资源。
希望这篇文章对你有所帮助。
推荐JavaScript经典实例学习资料文章
《教你如何使用内联框架元素 IFrames 的沙箱属性提高安全性?》
《细说DOM API中append和appendChild的三个不同点》
《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》
《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》
《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》
《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》
《图解 Promise 实现原理(二):Promise 链式调用》
《图解 Promise 实现原理(三):Promise 原型方法实现》
《图解 Promise 实现原理(四):Promise 静态方法实现》
《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》
《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》
《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》
《手把手教你7个有趣的JavaScript 项目-上「附源码」》
《手把手教你7个有趣的JavaScript 项目-下「附源码」》
《JavaScript 使用 mediaDevices API 访问摄像头自拍》
《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》
《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》
《可视化的 js:动态图演示 Promises & Async/Await 的过程》
《Pug 3.0.0正式发布,不再支持 Node.js 6/8》
《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》
《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》
《JavaScript 已进入第三个时代,未来将何去何从?》
《前端上传前预览文件 image、text、json、video、audio「实践」》
《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》
《推荐13个有用的JavaScript数组技巧「值得收藏」》
《36个工作中常用的JavaScript函数片段「值得收藏」》
《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》
《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》
《JavaScript正则深入以及10个非常有意思的正则实战》
《前端开发规范:命名规范、html规范、css规范、js规范》
《100个原生JavaScript代码片段知识点详细汇总【实践】》
《手把手教你深入巩固JavaScript知识体系【思维导图】》
《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》
《身份证号码的正则表达式及验证详解(JavaScript,Regex)》
《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》
《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《面试中教你绕过关于 JavaScript 作用域的 5 个坑》
作者:小飞猫
转发链接:
https://mp.weixin.qq.com/s/Q9JSBXx7eiq3GuAvg1itIw