lerna — JS package 管理工具

DEBUG
LT Lab
Published in
13 min readSep 6, 2019

是不是覺得發布套件到 NPM 上距離很遙遠,程式碼總是 Copy Paste 來 Copy Paste 去,最後搞到加班發大財,總覺得自己是草包離大神們好遠,就讓 lerna 帶你跳上巨人肩膀…

開始之前,需要先了解以下知識

  • 何謂 mono-repo 及 multi-repo
  • git 與 npm 基本操作及流程
  • typescript 基本設定

會選擇 mono-repo 的原因,在於每個 package 都是簡單的小元件,首先應用在前端 UI 元件及功能上的實現,由於只有一個 repo 也易於管理及維護這些 package。

已知管理 mono-repo 的工具有幾套 bolt、lerna、rush…,最終選擇 lerna, 主要原因在於旁邊有大神(有普渡有感應)可以指引我一盞明燈,工具本身都有其優缺點,在開發過程中能駕馭,並順利解決問題及完成專案就是好工具。以下附上這幾個工具的趨勢數據。

接下來以 lerna 的使用操作為主,lerna 本身結合 git 及 npm,指令雖然不多,但過程卻非常飄渺,整個專案會以 ts 方式撰寫,套件的安裝使用 yarn 為主。

首先需要安裝 lerna 於全域環境。

yarn global add lerna

建立資料夾並做 lerna 的初始化動作。

lerna init 預設的模式為 Fixed,Fixed 模式下所有 package 固定一個版本號。另一個模式為 independent,每個 package 都有自己的版本號。意思是在專案下會有 package-01、package-02、package-03 …,當前版本均為 1.0.0,突然有使用者反映 package-01 有 bug,於是 F2E 立馬修正,此時在 Fixed 模式下,所有 package 版本均升級到了 1.0.1,沒更新的也升級了,是不是覺得怪怪的。但在 independent 下,只有 package-01 升級到1.0.1,其他能維持在 1.0.0。初始化完後,如需要更改模式,可以透過 lerna.json 做修改。

mkdir lerna-repo
cd lerna-repo
lerna init --independent

初始完會產生以下結構,packages 是一個空目錄,放置的內容為要開發的 package。lerna.json 就是透過 lerna 來管理旗下 packages 的設定檔。

- packages/
- lerna.json
- package.json
- .git

以下針對 lerna.json 及 package.json 做基本設定,如果使用 yarn,需要注意useWorkspaces 參數,攸關是否將 node_modules 統一管理。

接下來建立一個 package,packages/ 下會多一個 package-01 的資料結構。

lerna create package-01 -y

可以不透過指令,直接在 packages 下按右鍵,建立資料夾,需手動做 yarn init -y,原因是專案下認的是 package.json。

package.json 有個很重要的欄位 name,name 的使用時機是專案在引用時,會依此名稱。

整個 packages 下的 package,建議 name 可以使用一個前綴詞(@taiwan),諸如@taiwan/package-01,用意在於在引用這些 package 時,node_modules 會統一歸類在一個資料夾下。可以參考如下 babel 的 package( babel 也使用 lerna !!)。

基本的環境都設定好後,開始安裝相依的套件,諸如 react 及 react-dom。我們之後要做的 package 都是基於 react 下,所以我們在專案的根目錄下執行以下指令。

yarn add react react-dom

上述會報出一個錯誤

指令需要 -W 參數

yarn add react react-dom -W

每個 packages 下都依賴 react 及 react-dom,執行以下指令,每個 package.json 的 dependencies 下會有 react 及 react-dom。當然也可以手動方式加入到 package.json。

lerna add react react-dom

也可以指定相依套件,要安裝在哪個 packages 下

lerna add react --scope packages/package-01

補充:package.json 中 peerDependencies

一般專案開發下,很常見dependenciesdevDependencies ,差別在於是否在開發測試的狀態與否。如果是開發 package 讓多個專案共用的話,可能要思考是否使用 peerDependencies 取代 dependencies ,諸如 react 版本,在使用此 package 時,先確認要引入專案的 react 版本,如果達不到套件需求版本,就終止安裝。

dependencies 其他種類可參考如下
https://classic.yarnpkg.com/en/docs/dependency-types/

當相依套件加入 package 後會產生 node_modules,如果每個node_module 下都有 react 及 react-dom 的套件,整個專案會變得非常肥,可以加入 hoist 參數,讓套件統一根目錄管理,以下指令同於 yarn 或 npm install。

lerna bootstrap --hoist

如果是使用 yarn 的會報錯,會建議使用 workspace 替代,上述的設定以使用 workspace 方式,因此不需要加 hoist 參數。

如果要清除所有專案下的 node_modules

lerna clean

這時可以嘗試先 build 出一個版本,在 build 一個新版本時,需要有一個遠端的 git repo 並確定連結及提交所有變更的 commit,如果沒有將無法進行後續。

lerna version

如果模式為 independent,會逐一詢問版本的狀態,bug (patch) 修正可能 0.0.1 開始往上加,新功能 (minor) 加入可能 0.1.0 往上加,大改版 (major)可能就是 1.0.0 往上加,也可以自訂版本等。設定完後,他會自動 push 到遠端 git repo。

到目前為止只是 build 出個版本號過過癮,別人還是無法用你的 package。接下來會從如何開發 package 開始,及開發完後如何在本地引入,最後開啟自建 npm server 引入自己做的 package 到專案中。

在開發 package 前,還需要前置作業及環境的設定,首先需要安裝 storybook,展示我們開發 package 的成果以及加入一點開發難度全部使用 ts 開發,便於我們後續程式開發的統一及維護性。

webpack 的編譯是使用 node,如要使用 ts 需安裝 ts-node
https://medium.com/webpack/unambiguous-webpack-config-with-typescript-8519def2cac7

storybook 及 typescript 的安裝,可參考官網,這邊不一一列出安裝方式。設定檔(webpack.config.js)副檔名 js 直接轉成 ts,會有問題應該都是缺型態,將型態暫時設為 any。

記得安裝的套件,後面要加 -W

yarn add @storybook/react -D -W

補充:

storybook notes 插件,用途是加入備註及說明,讓開發者能透過說明了解其使用方法,這邊也可以引入 markdown,引入 markdown,會有 typescript 的錯誤,會找不到 .md 的定義檔案。

import markdownNotes from '../readme.md';

建議在根目錄下創建 typings 目錄,加入以下檔案 (還加入了 scss 的定義檔,主要在做 css modules 會使用到)。

加入後有問題,可能需要確認 tsconfig.json 的 include 是否包含 typings 的目錄。

接下來,我們來創建第一個 package 名為 Footer

cd packages
mkdir Footer
yarn init -y

查看是否有此 package

lerna list 

可以開始建立裡面的內容,storybook 上如何呈現就不加贅述,可以定義在每個 package 下 xxx. stories.tsx,也可以獨立開一個資料夾來放 package 元件,差別可能可以戰 TDD 的流程是否好壞(暈…)

mkdir src
cd src
touch Module.tsx

Module.tsx 的內容,顯示 Footer 字樣

import * as React from 'react';const Module = () => {
return (<div>Footer</div>)
}
export default Module;

在 Footer 目錄下會建立一個入口點 index.ts,內容只是引入剛剛建立的Module.tsx

import Module from './src/Module';export default Module;

可以試著查看目前成果

yarn storybook

但離發布套件給自己用,還有一小段距離,有點敏感度的人應該知道要將 ts 、tsx、scss 等,編譯成瀏覽器能夠懂的語言 js 及 css。這邊就以 tsc 方式實作,先排除 css 的部分,如需要可以使用 webpack 方式做編譯。

tsc 就是 typescript cli ,可以將 typescript 裝在全域。之後著重在兩隻檔案的操作上,tsconfig.json 及 package.json。

可以將建立在根目錄的 tsconfig.json 複製過來,調整一下,編譯完之後的位置(這邊放置於 lib),需要編譯的檔案( src 下及 index.ts )

{
"compilerOptions": {
"outDir": "lib",
},
"include": [
"../../typings/**/*",
"src/**/*",
"index.ts"
]
}

看似沒用的 package.json 才是關鍵角色

{
// 套件的使用名稱這邊加入前綴,可以下 lerna list,查看套件名稱變化
"name": "@taiwan/footer",
"version": "1.0.0",

// 程式入口點,須為編譯後的入口
"main": "lib/index.js",

// 告知定義檔
"types": "lib/index.d.ts",
"license": "MIT",

// 編譯指令
"scripts": {
"build-tsc": "tsc"
},
// 需要上 npm server 的檔案列表,須為編譯後結果(package.json很重要,別忘了),跟 Git 上原始碼有別
"files": [
"lib",
"package.json"
]
}

在 Footer 目錄下執行

yarn build-tsc如果要 build 整個專案
lerna run build-tsc

接下來就是享用自建的 package 的時間。

接下來偷吃步一下,直接使用 create-react-app,來引用 @taiwan/footer,引用的方式有幾種,一種是透過 yarn link (捷徑的概念,檔案會在~/.config/yarn/link),另一種是採相對或絕對路徑的方式,這邊以相對路徑實作。

到你的 create-react-app 下,安裝你自建的 Footer 的資料夾位置

yarn add ../lerna-repo/packages/Footer

這時到 App.js 引入,要使用 package name @taiwan/footer

import Footer from '@taiwan/footer';

Footer package 本地測試成功。

接下來就是部署到自己私有的 npm server,這邊使用的是 verdaccio

需要裝在全域環境

yarn global add verdaccio

並執行 verdaccio 服務,會產生一個 localhost 的連結

verdaccio

首先需要註冊一個帳號,需要輸入帳號密碼及 email

npm adduser --registry http://localhost:4873

補充:

現在的 node 安裝,都建議使用 nvm 來管理與安裝,這邊就來註記幾個基本指令

nvm ls-remote 遠端版本
nvm ls 本地版本
nvm use [version] or nvm use node 使用特定版本

這時先別急著做 publish 的動作,原因是有可能你本機已設定官方 npm public 的連結,這時 publish 就會發布出去,要刪除需要填申請單。這邊會使用 nrm 管理我們的私有 npm server。

yarn global add nrm

列出可用管理工具

nrm ls 

加入私有 npm server 連結,並使用

nrm add yarn http://localhost:4873
nrm use yarn

可以針對單一 package 進行發布

yarn publish

對所有已更新 package 發布

lerna publish

發布成功後,即可看見 @taiwan/footer

在本機執行 yarn add @taiwan/footer ,如果找不到,就會到對外的管理套件做下載。

這篇介紹忽略了需多步驟,整個操作下來會有許多懵懵懂懂的地方,很多細節需要花時間踩過才知道。

--

--

DEBUG
LT Lab
Editor for

軟體工程的可怕是沒有最優,只有更優的代碼,每天被放大鏡檢視的錯誤(BUG),已鍛鍊內心的強大,有時可能厚者臉皮裝傻帶過,更多時候是不願服輸,比昨日的自己還強大。