记 node_modules 版本锁定方案的落地

背景

半年前开始关注 npm shrinkwrap ,因为项目里每次 npm install 都会出现因依赖库版本不一致导致的构建问题。

当时的默认安装工具是 tnpm,由于 tnpm 从 cnpm 来,cnpm 又通过 npminstall 实现,而 npminstall 又不支持 shrinkwrap ,无奈只能考虑通过 npm 方式进行安装。

官方 npm 没有 @ali 依赖,必须使用 --registry=http://registry.npm.alibaba-inc.com 指定资源仓库。

对于一般的项目,设置一个别名即可解决这个问题:

alias alinpm='npm --registry=http://registry.npm.alibaba-inc.com'
# 或 cnpm
alias alinpm='npm --registry=https://registry.npm.taobao.org'

但问题又来了,本地项目中有一模块 @ali/imagemin 依赖以下这些模块:

  • advpng-bin
  • jpeg-recompress-bin
  • jpegtran-bin
  • optipng-bin
  • pngcrush-bin
  • pngquant-bin

这些模块安装时会从 github 下载执行文件,具体流程如下:

因为有两处逻辑涉及下载,而下载地址又是 github cdn。国内环境不出意外的话一定被墙,所以 npm 安装方式行不通,安装到 pngquant 时会报 pngquant pre-build test failed。之后走源码构建逻辑又从 github 下载,继续报 pngquant failed to build

但奇怪的是 tnpm 安装却一切正常:

为什么

非常有意思,扒扒 npminstall 源码跟踪安装流程,发现 bin/install.js 脚本中有一段:

// if in china, will automatic using chines registry and mirros.
const inChina = argv.china || !!process.env.npm_china;
// if exists, override default china mirror url
const customChinaMirrorUrl = argv['custom-china-mirror-url'];

顺着 customChinaMirrorUrl 找到了:

这段代码表示从这几处资源仓库里找 binary-mirror-config 模块的最新版本,下载后返回mirrors.china。搜一下,发现 npminstall 用了一个比较鸡贼的办法:

case by case 的把所有需要下载的二进制全做了一次镜像!

顺藤摸瓜

抑制不住兴奋继续往下扒,看看在哪里做了处理:

yield installLocal(config);
 + require('./local_install')
  + _install()
   + installOne()
    + install()
     + _install()
      + download()
       + npm()
        + download()

终于在 lib/download/npm.js 的第 238 行看到了 pngquant-bin

// use mirror url instead
// e.g.: pngquant-bin
const indexFilepath = path.join(ungzipDir, 'lib/index.js');
yield replaceHostInFile(pkg, indexFilepath, binaryMirror, options);
const installFilepath = path.join(ungzipDir, 'lib/install.js');
yield replaceHostInFile(pkg, installFilepath, binaryMirror, options);

npminstall 在下载流程里单独处理了所有需要镜像的二进制执行文件,找到一处匹配就 replace 其 binary host,起到镜像的效果。

解决它

找到关键点就好办了,目前有两种方案:
1. 单独为每个模块的 package.json 添加 postinstall 脚本
2. 通过 npm 钩子 hooks script 对所有模块单独进行处理

修改别人的模块显然不可能,那就只能用方案2了

编写脚本

要使 hooks script 起作用,得在 node_modules 目录里创建一个 .hooks 目录,里面存放着以「事件名称」命名的脚本文件(安装脚本请参见:npm scripts

project_dir
 + node_modules
  + .hooks
   + preinstall <---

在 preinstall 脚本里可以使用 process.env.npm_package_name 获得当前安装的模块名称,伪代码如下:

#!/usr/bin/env node
if('pngquant-bin' === process.env.npm_package_name){
    const PWD = process.env.PWD;
    replaceHostInFile(path.join(PWD, 'lib/index.js'));
    replaceHostInFile(path.join(PWD, 'lib/install.js'));
}
function replaceHostInFile(filepath) {
    const exists = fs.existsSync(filepath);
    if (!exists) return;
    let content = fs.readFileSync(filepath, 'utf8');
    content = content.replace(/\/\/raw\.github\.com/, '//raw.github.cnpmjs.org');
    content = content.replace(/\/\/github\.com/, '//github.com.cnpmjs.org');
    fs.writeFileSync(filepath, content);
}

执行结果:

到此,问题已完全解决,安装上最新的 npm 5.x ,轻松使用 package-lock.json 提供的版本锁定特性。

更进一步

实际使用中不可能每个项目都复制一份 hooks scripts,要脱离 case by case 必须自动化。

不细讲,以下是最终版本,欢迎大家试用:

hook-binary-mirror

用法

  1. 全局安装 hook-binary-mirror 模块

    # tnpm 方式
    tnpm --by=npm i hook-binary-mirror -g
    # npm 方式
    npm --registry=https://registry.npm.taobao.org i hook-binary-mirror -g
    
  2. 删除原有的 node_modules 目录
    cd project_dir
    rm -rf node_modules
    
  3. 为 package.json 增加一处 scripts
    "scripts": {
    "preinstall": "hook-binary-mirror"
    }
    

完成

结束

困扰了半年多的问题终于解决,此项任务终于可以放心的置为「已结束」。过程中翻阅了 npminstall 的源码,获益良多。

时间: 2025-01-20 13:38:05

记 node_modules 版本锁定方案的落地的相关文章

Android使用fitsSystemWindows属性实现–状态栏【status_bar】各版本适配方案

Android使用fitsSystemWindows属性实现–状态栏[status_bar]各版本适配方案  首先我们看下qq的status bar在各个android版本系统中适配: 1.Android5.0以上:半透明(APP 的内容不被上拉到状态)  2.Android4.4以上:全透明(APP 的内容不被上拉到状态)  3.Android4.4以下:不占据status bar  这里我们就按照qq在各个android的版本显示进行适配:  1.Android5.0以上:material

和力记易容灾备份方案为企业节省IT成本

 电脑问世前,企业之间的竞争主要集中在产品.规模,而当信息化出现并快速发展之后,企业之间的竞争很大程度上是信息资源的竞争.在产品信息方面,可以通过官网.微信.APP等多种方式进行展示与宣传;在业务渠道方面,可以借助专业的软件进行建设与管理;在客户关系方面,CRM客户关系管理系统日益普及,无一不是信息资源的集中.整合与发展. 信息是什么? 说白了就是数据. 是信息化时代企业赖以生存发展的根本之一. 据IDC研究机构近几年的调查表明,全球的数据量每两年将会翻一番.数据量越来越多,为企业的发展提供了更

CentOS下与Apache连接的PHP多版本共存方案实现详解_php实例

在apache下整合fastCGI模式运行的php-fpm,似乎网上很少相关材料,就连英文版材料也少.只要是php-fpm,基本上都是与nginx搭配.查了一大批相关资料,写本文总结一下. apache下有多个fastCGI的支持方案:至少有mod_fcgi.mod_fastcgi(git).mod_proxy_fcgi等.这两个模块都有点老,尤其mod_fastcgi自从2007年以来就没有更新,略掉不谈,事实上没用过用.mod_proxy_fcgi模块是httpd 2.4+的版本正式引入,通

WebGIS中自定义互联网地图局部注记的一种方案

文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.    前言 实际项目中我们经常会遇到这样一种场景:地图底图可能是互联网地图(百度.高德.天地图)等等,同时我们自己又有某个区域单独的一套POI数据,我们需要将互联网地图中这个区域的原有POI数据进行遮罩然后只显示我们自己的POI数据. 针对这样的需求,我们首先想到的是能否我们只使用不包含注记的底图瓦片,然后再叠加上我们的POI数据.事实上,实际需求中还需要考虑一点

一个中国方案的落地:马云的eWTP,如何让马来西亚第一个all in

郑加富没有想到马云会在发言中提到他. "我的同事告诉我,第一家在DFTZ通关的企业,是一个做橡胶的三代家族企业.现在这个年轻人只有26岁.他爷爷用木船把商品运到印尼,他爸爸用集装箱把货卖到几十个国家和地区,他现在通过eWTP要卖到全世界." 11月3日,在马来西亚数字自由贸易区(DFTZ),也是第一个eWTP海外试点,正式启用的庆典仪式上,阿里巴巴集团董事局主席马云在致辞中说.他提到的这个年轻人便是郑加富. "会感到比较意外."戴着黑框眼镜,略显腼腆的郑加富对<

Bluemix专属版本落地中国 开放物联网和认知计算能力

近日,IBM与世纪互联联合宣布:Bluemix专属版本(Dedicated)在中国正式投入使用.从去年10月双方在华正式推出Bluemix至今,IBM已经于去年12月1日推出Bluemix本地版本(Local),而此次落地的Bluemix专属版本(Dedicated)是IBM混合云能力在中国的又一大进展.而据记者了解,Bluemix公有云版本也会很快落地中国. Bluemix专属版本更亲民 此前采访IBM全球云平台服务部总经理Steve Robinson的时候他介绍过Bluemix三个版本的不同

SQL Server和Oracle防止数据锁定的比较

oracle|server|比较|数据 廖铮 2002-5-30 14:23:50 -------------------------------------------------------------------------------- 数据库并行访问,也就是两个或两以上用户同时访问同一数据,这也是数据库引擎如何设计和实现适度反应所面临的最大问题.设计优良.性能卓越的数据库引擎可以轻松地同时为成千上万的用户服务.而"底气不足"的数据库系统随着更多的用户同时访问系统将大大降低其性

SQL Server和Oracle数据锁定比较

数据库并行访问,也就是两个或两以上用户同时访问同一数据,这也是数据库引擎如何设计和实现适度反应所面临的最大问题.设计优良.性能卓越的数据库引擎可以轻松地同时为成千上万的用户服务.而"底气不足"的数据库系统随着更多的用户同时访问系统将大大降低其性能.最糟糕的情况下甚至可能导致系统的崩溃. 当然,并行访问是任何数据库解决方案都最为重视的问题了,为了解决并行访问方面的问题各类数据库系统提出了各种各样的方案. SQL Server和Oracle两大DBMS也分别采用了不同的并行处理方法.它们之

MySQL 4.1.0 中文参考手册 --- 6.7 MySQL 事务与锁定命令

mysql|参考|参考手册|中文 MySQL 4.1.0 中文参考手册 --- 犬犬(心帆)翻译 MySQL Reference Manual for version 4.1.0-alpha. 6.7 MySQL 事务与锁定命令6.7.1 BEGIN/COMMIT/ROLLBACK 句法 缺省的,MySQL 运行在 autocommit 模式.这就意味着,当你执行完一个更新时,MySQL 将立刻将更新存储到磁盘上. 如果你使用事务安全表 (例如 InnoDB.BDB),通过下面的命令,你可以设置