【第三期】使用lerna管理經常使用工具庫

在工做中咱們有時會寫一些經常使用的庫,好比包含數據類型判斷、cookie存儲模塊的工具庫等,但可能在某些業務場景中,並不須要用到全部的模塊。前端

咱們一般會將這個庫拆分紅多個,分別建立git倉庫,分別打包上傳到npm,這樣作看起來並無什麼問題。node

但當多個庫之間產生依賴的時候,問題就就會顯露出來;你須要打包發佈修改後的庫,還須要修改全部依賴庫的版本號,從新發包。react

可想而知,當庫多起來後,這個過程將會變得多麼繁瑣。webpack

那麼有什麼好的方式來解決呢?Lerna正適合這樣的應用場景。git

lerna是什麼

Lerna是一個用於管理具備多個包的JavaScript項目的工具,它採用monorepo(單代碼倉庫)的管理方式。es6

將全部相關module都放到一個repo裏,每一個module獨立發佈,(例如BabelReactjest等),issue和PR都集中到該repo中。github

你不須要手動去維護每一個包的依賴關係,當發佈時,會自動更新相關包的版本號,並自動發佈。web

Lerna項目文件結構:npm

├── lerna.json
├── package.json
└── packages
    ├── package-a
    │   ├── index.js
    │   └── package.json
    └── package-b
        ├── index.js
        └── package.json
複製代碼

lerna主要作了什麼

  • 經過lerna bootstrap 命令安裝依賴並將代碼庫進行npm link
  • 經過lerna publish發佈最新改動的庫

如何使用

安裝

npm install --global lerna
#or
yarn global add lerna
複製代碼

初始化一個項目

mkdir demo 
cd demo
lerna init  
複製代碼

執行後將生成如下目錄:json

├── lerna.json # lerna配置文件
├── package.json
└── packages # 包存放文件夾
複製代碼

Lerna有兩種管理項目的模式:固定模式或獨立模式

固定模式

固定模式是默認的模式,版本號使用lerna.json文件中的version屬性。執行lerna publish時,若是代碼有更新,會自動更新此版本號的值。

獨立模式

獨立模式,容許維護人員獨立的增長修改每一個包的版本,每次發佈,全部更改的包都會提示輸入指定版本號。

使用方式:

lerna init --independent
複製代碼

修改lerna.json中的version值爲independent,可將固定模式改成獨立模式運行。

lerna配置解析

{
  "npmClient": "yarn", // 執行命令所用的客戶端,默認爲npm
  "command": { // 命令相關配置
    "publish": { // 發佈時配置
      "ignoreChanges": ["ignored-file", "*.md"], // 發佈時忽略的文件
      "message": "chore(release): publish" // 發佈時的自定義提示消息
    },
    "bootstrap": { // 安裝依賴配置
      "ignore": "component-*", // 忽略項
      "npmClientArgs": ["--no-package-lock"] // 執行 lerna bootstrap命令時傳的參數
    }
  },
  "packages": [ // 指定存放包的位置
    "packages/*"
  ],
  "version": "0.0.0" // 當前版本號
}
複製代碼

共用devDependencies

開發過程當中,不少模塊都會依賴babeleslint等模塊,這些大多都是能夠共用的,

咱們能夠經過lerna link convert命令,將它們自動放到根目錄的package.json文件中去。

這樣作便可以保證每一個依賴的版本統一,也能夠減小存儲空間,減小依賴安裝的速度。

注意: 一些npm可執行的包,仍然須要安裝到使用模塊的包中,才能正常執行,例如jest

使用yarn Workspaces

工做區是設置軟件包體系結構的一種新方式,只須要運行一次 yarn install 即可將指定工做區中全部依賴包所有安裝。

優點

  • 依賴包能夠連接在一塊兒,這意味着你的工做區能夠相互依賴,同時始終使用最新的可用代碼。 這也是一個比 yarn link 更好的機制,由於它隻影響你工做區的依賴樹,而不會影響整個系統。
  • 全部的項目依賴將被安裝在一塊兒,這樣可讓 Yarn 來更好地優化它們。
  • Yarn 將使用一個單一的 lock 文件,而不是每一個包都有一個,這意味着擁有更少的衝突和更容易的進行代碼檢查。

如何使用

package.json 文件中添加如下內容:

package.json

{
  "private": true,
  "workspaces": ["packages/*"]
}
複製代碼

注意:private: true 是必需的!工做區自己不該當被髮布出去,因此咱們添加了這個安全措施以確保它不會被意外暴露。

lerna中使用

須要在lerna.json文件中增長如下配置來啓用yarn workspaces:

{
  "useWorkspaces": true
}
複製代碼

建立模塊

lerna create package-a
複製代碼

執行上面的命令,會在package文件夾下建立模塊,並根據交互提示生成對應的package.json

生成目錄結構以下:

├── lerna.json
├── package.json
└── packages
    └── package-a
    		├── __tests__
    		│    └── name.test.js
    		├── lib
    		│    └── name.js
        ├── package.json
        └── README.md
複製代碼

添加依賴

將模塊package-a添加到package-b模塊依賴中

larna add package-a --scope=package-b
複製代碼

添加完成後會在package-bpackage.json中增長如下依賴項

{
  "dependencies": {
    "package-a": "file:../package-a"
  }
}
複製代碼

包依賴使用file:來指定本地路徑文件

發佈

發佈時,須要先提交commit代碼,而後執行lerna publish命令,提示選擇版本號:

這裏選擇Patch,而後會提示,哪些包會升級到1.0.1

接着根據提示選擇確認便可發佈成功。

也可使用lerna publish -y默認選項所有選擇Yes,並根據commit信息自動升級版本號。

Lerna Changelog

lerna自帶生成Changelog的功能,只須要經過簡單的配置就能夠生成CHANGELOG.md文件。

配置以下:

{
  "command": {
    "publish": {
      "allowBranch": "master", // 只在master分支執行publish
      "conventionalCommits": true, // 生成changelog文件
      "exact": true // 準確的依賴項
    }
  }
}
複製代碼

配置後,當咱們執行lerna publish後會在項目根目錄以及每一個packages包下,生成CHANGELOG.md

注意: 只有符合約定commit提交才能正確生成CHANGELOG.md文件。

若是提交的commitfix會自動升級版本的修訂號;

若是爲feat則自動更新次版本號;

若是有破壞性的更改,則會修改主版本號

Lerna與Jest集成

在包發佈以前,爲了保證代碼的質量,都須要來編寫單元測試,爲了提升效率並方便測試運行,咱們想要作到如下功能:

  • 全部包只維護一份公共的jest配置文件
  • 能夠總體運行全部單元測試
  • 能夠只對某個包執行單元測試

jest配置

在項目根目錄配置jest.config.js文件以下:

const path = require('path')
module.exports = {
  collectCoverage: true, // 收集測試時的覆蓋率信息
  coverageDirectory: path.resolve(__dirname, './coverage'), // 指定輸出覆蓋信息文件的目錄
  collectCoverageFrom: [ // 指定收集覆蓋率的目錄文件,只收集每一個包的lib目錄,不收集打包後的dist目錄
    '**/lib/**',
    '!**/dist/**'
  ],
  testURL: 'https://www.shuidichou.com/jd', // 設置jsdom環境的URL
  testMatch: [ // 測試文件匹配規則
    '**/__tests__/**/*.test.js'
  ],
  testPathIgnorePatterns: [ // 忽略測試路徑
    '/node_modules/'
  ],
  coverageThreshold: { // 配置測試最低閾值
    global: {
      branches: 100,
      functions: 100,
      lines: 100,
      statements: 100
    }
  }
}

複製代碼

編寫測試腳本

新增scripts文件夾,添加test.js文件:

const minimist = require('minimist')
const rawArgs = process.argv.slice(2)
const args = minimist(rawArgs)
const path = require('path')
let rootDir = path.resolve(__dirname, '../')
// 指定包測試
if (args.p) {
  rootDir = rootDir + '/packages/' + args.p
}
const jestArgs = [
  '--runInBand',
  '--rootDir', rootDir
]

console.log(`\n===> running: jest ${jestArgs.join(' ')}`)

require('jest').run(jestArgs)

複製代碼

該腳本經過解析命令行參數-p來決定執行指定包的測試用例,若是沒有指定-p參數,則執行所有測試用例。

修改根目錄下package.jsonscript增長以下命令:

{
  "scripts": {
    "ut": "node scripts/test.js"
  }
}
複製代碼

運行測試腳本:

# 執行所有測試
yarn ut

# 執行某個包測試
yarn ut -p package-a
複製代碼

Lerna與webpack集成

發包時,會須要使用webpack進行es6轉碼或壓縮打包,若是每一個都維護一份配置文件,就會很繁瑣;咱們有與jest相同的需求:

  • 只用一份webpack配置文件
  • 能夠一次性將全部的模塊分別打包
  • 也能夠單獨對指定模塊打包

webpack配置

在根目錄建立webpack.config.js文件,以下:

var path = require('path')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = (opt) => {
  return {
    mode: 'production',
    entry: path.resolve(opt.path, './lib/index.js'),
    output: {
      path: path.resolve(opt.path, './dist'),
      filename: `${opt.name}.min.js`,
      library: opt.name,
      libraryTarget: 'umd',
      umdNamedDefine: true
    },
    externals: opt.externals,
    plugins: [
      new CleanWebpackPlugin()
    ],
    module: {
      rules: [
        {
          test: /\.js$/,
          loader: 'babel-loader',
          include: [path.resolve(opt.path, './lib')],
          options: {
            // 指定babel配置文件
            configFile: path.resolve(__dirname, '.babelrc')
          }
        }
      ]
    },
    optimization: {
      minimize: true
    }
  }
}

複製代碼

這個配置文件是一個函數,經過接受一個參數對象,來返回最終的配置內容,

編寫build腳本

思路:

  1. 讀取packages目錄下的全部模塊,獲取模塊的路徑
  2. 讀取模塊下的package.json,獲取name及依賴項
  3. 經過模塊路徑、包名和package.json中的dependencies參數來獲取webpack配置
  4. 經過webpackNode API執行配置編譯打包
  5. 根據命令行參數,判斷執行須要打包的配置文件(單獨打包)

具體實現以下:

/scripts/build.js

const minimist = require('minimist')
const rawArgs = process.argv.slice(2)
const args = minimist(rawArgs)
const webpack = require('webpack')
const webpackConfig = require('../webpack.config')
const fs = require('fs')
const path = require('path')
const packages = fs.readdirSync(path.resolve(__dirname, '../packages/'))

// 獲取外部依賴配置
function getExternals (dependencies) {
  let externals = {}
  if (dependencies) {
    Object.keys(dependencies).forEach(p => {
      externals[p] = `commonjs ${p}`
    })
    return externals
  }
}
const packageWebpackConfig = {}

// 遍歷全部的包生成配置參數
packages.forEach(item => {
  let packagePath = path.resolve(__dirname, '../packages/', item)
  const { name, dependencies } = require(path.resolve(packagePath, 'package.json'))
  packageWebpackConfig[item] = {
    path: packagePath,
    name,
    externals: getExternals(dependencies)
  }
})

function build (configs) {
  // 遍歷執行配置項
  configs.forEach(config => {
    webpack(webpackConfig(config), (err, stats) => {
      if (err) {
        console.error(err)
        return
      }

      console.log(stats.toString({
        chunks: false, // 使構建過程更靜默無輸出
        colors: true // 在控制檯展現顏色
      }))
      if (stats.hasErrors()) {
        return
      }
      console.log(`${config.name} build successed!`)
    })
  })
}

console.log('\n===> running build')

// 根據 -p 參數獲取執行對應的webpack配置項
if (args.p) {
  if (packageWebpackConfig[args.p]) {
    build([packageWebpackConfig[args.p]])
  } else {
    console.error(`${args.p} package is not find!`)
  }
} else {
  // 執行全部配置
  build(Object.values(packageWebpackConfig))
}

複製代碼

而後在根目錄下package.jsonscript增長以下命令:

{
  "scripts": {
    "build": "node scripts/build.js"
  }
}
複製代碼

運行構建腳本:

# 所有打包
yarn build

# 指定打包
yarn build -p package-a
複製代碼

至此,Lerna的使用方法就介紹完成了。


水滴前端團隊招募夥伴,歡迎投遞簡歷到郵箱:fed@shuidihuzhu.com

相關文章
相關標籤/搜索