在工做中咱們有時會寫一些經常使用的庫,好比包含數據類型判斷、cookie
存儲模塊的工具庫等,但可能在某些業務場景中,並不須要用到全部的模塊。前端
咱們一般會將這個庫拆分紅多個,分別建立git
倉庫,分別打包上傳到npm
,這樣作看起來並無什麼問題。node
但當多個庫之間產生依賴的時候,問題就就會顯露出來;你須要打包發佈修改後的庫,還須要修改全部依賴庫的版本號,從新發包。react
可想而知,當庫多起來後,這個過程將會變得多麼繁瑣。webpack
那麼有什麼好的方式來解決呢?Lerna正適合這樣的應用場景。git
Lerna是一個用於管理具備多個包的JavaScript項目的工具,它採用monorepo
(單代碼倉庫)的管理方式。es6
將全部相關module
都放到一個repo
裏,每一個module
獨立發佈,(例如Babel、React和jest等),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 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
,可將固定模式改成獨立模式運行。
{
"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
開發過程當中,不少模塊都會依賴babel
、eslint
等模塊,這些大多都是能夠共用的,
咱們能夠經過lerna link convert
命令,將它們自動放到根目錄的package.json
文件中去。
這樣作便可以保證每一個依賴的版本統一,也能夠減小存儲空間,減小依賴安裝的速度。
注意: 一些npm
可執行的包,仍然須要安裝到使用模塊的包中,才能正常執行,例如jest
。
工做區是設置軟件包體系結構的一種新方式,只須要運行一次 yarn install
即可將指定工做區中全部依賴包所有安裝。
yarn link
更好的機制,由於它隻影響你工做區的依賴樹,而不會影響整個系統。在 package.json
文件中添加如下內容:
package.json
{
"private": true,
"workspaces": ["packages/*"]
}
複製代碼
注意:private: true
是必需的!工做區自己不該當被髮布出去,因此咱們添加了這個安全措施以確保它不會被意外暴露。
須要在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-b
的package.json
中增長如下依賴項
{
"dependencies": {
"package-a": "file:../package-a"
}
}
複製代碼
包依賴使用file:
來指定本地路徑文件
發佈時,須要先提交commit
代碼,而後執行lerna publish
命令,提示選擇版本號:
這裏選擇Patch
,而後會提示,哪些包會升級到1.0.1
。
接着根據提示選擇確認便可發佈成功。
也可使用lerna publish -y
默認選項所有選擇Yes
,並根據commit
信息自動升級版本號。
lerna
自帶生成Changelog
的功能,只須要經過簡單的配置就能夠生成CHANGELOG.md
文件。
配置以下:
{
"command": {
"publish": {
"allowBranch": "master", // 只在master分支執行publish
"conventionalCommits": true, // 生成changelog文件
"exact": true // 準確的依賴項
}
}
}
複製代碼
配置後,當咱們執行lerna publish
後會在項目根目錄以及每一個packages
包下,生成CHANGELOG.md
。
注意: 只有符合約定的commit
提交才能正確生成CHANGELOG.md
文件。
若是提交的commit
爲fix
會自動升級版本的修訂號;
若是爲feat
則自動更新次版本號;
若是有破壞性的更改,則會修改主版本號。
在包發佈以前,爲了保證代碼的質量,都須要來編寫單元測試,爲了提升效率並方便測試運行,咱們想要作到如下功能:
在項目根目錄配置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.json
的script
增長以下命令:
{
"scripts": {
"ut": "node scripts/test.js"
}
}
複製代碼
運行測試腳本:
# 執行所有測試
yarn ut
# 執行某個包測試
yarn ut -p package-a
複製代碼
發包時,會須要使用webpack
進行es6
轉碼或壓縮打包,若是每一個都維護一份配置文件,就會很繁瑣;咱們有與jest
相同的需求:
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
}
}
}
複製代碼
這個配置文件是一個函數,經過接受一個參數對象,來返回最終的配置內容,
思路:
packages
目錄下的全部模塊,獲取模塊的路徑package.json
,獲取name
及依賴項package.json
中的dependencies
參數來獲取webpack
配置webpack
的Node API
執行配置編譯打包具體實現以下:
/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.json
的script
增長以下命令:
{
"scripts": {
"build": "node scripts/build.js"
}
}
複製代碼
運行構建腳本:
# 所有打包
yarn build
# 指定打包
yarn build -p package-a
複製代碼
至此,Lerna
的使用方法就介紹完成了。
水滴前端團隊招募夥伴,歡迎投遞簡歷到郵箱:fed@shuidihuzhu.com