閱讀958 返回首頁    go 阿裏雲 go 技術社區[雲棲]


記 node_modules 版本鎖定方案的落地

背景

半年前開始關注 npm shrinkwrap ,因為項目裏每次 npm install 都會出現因依賴庫版本不一致導致的構建問題。

當時的默認安裝工具是 tnpm,由於 tnpm 從 cnpm 來,cnpm 又通過 npminstall 實現,而 npminstall 又不支持 shrinkwrap ,無奈隻能考慮通過 npm 方式進行安裝。

官方 npm 沒有 @ali 依賴,必須使用 --registry=https://registry.npm.alibaba-inc.com 指定資源倉庫。

對於一般的項目,設置一個別名即可解決這個問題:

alias alinpm='npm --registry=https://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 下載執行文件,具體流程如下:

image.png

因為有兩處邏輯涉及下載,而下載地址又是 github cdn。國內環境不出意外的話一定被牆,所以 npm 安裝方式行不通,安裝到 pngquant 時會報 pngquant pre-build test failed。之後走源碼構建邏輯又從 github 下載,繼續報 pngquant failed to build

image.png

但奇怪的是 tnpm 安裝卻一切正常:

image.png

為什麼

非常有意思,扒扒 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 找到了:

image.png

這段代碼表示從這幾處資源倉庫裏找 binary-mirror-config 模塊的最新版本,下載後返回mirrors.china。搜一下,發現 npminstall 用了一個比較雞賊的辦法:

image.png

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);
}

執行結果:

image.png

到此,問題已完全解決,安裝上最新的 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 的源碼,獲益良多。

最後更新:2017-07-19 20:03:13

  上一篇:go  測試
  下一篇:go  Weex布局尺寸通用適配方案的研究