從 1 到完美,寫一個 js 庫、node 庫、前端元件庫
之前講了很多關於專案工程化、前端架構、前端構建等方面的技術,這次說說怎麼寫一個完美的第三方庫。
1. 選擇合適的規範來寫程式碼
js
模組化的發展大致有這樣一個過程 iife => commonjs/amd => es6
,而在這幾個規範中:
-
iife
:js
原生支援,但一般不會直接使用這種規範寫程式碼 -
amd
:requirejs
定義的載入規範,但隨著構建工具的出現,便一般不會用這種規範寫程式碼 -
commonjs
:node
的模組載入規範,一般會用這種規範寫node
程式 -
es6
:ECMAScript2015
定義的模組載入規範,但到目前為止,幾乎所有的js
執行環境都不支援,包括瀏覽器、node
(包括electron
、nw.js
)、React Native
等
針對原生不支援任何規範的執行環境程式(如瀏覽器、React Native
),建議使用 es6
規範來寫程式碼,然後由工具轉換成原生 js
能夠執行的。
而針對 node
程式,可以直接用 commonjs
規範來寫,也可由 es6
規範來寫,然後用工具轉化成 commonjs
規範。
所以,總的來說,都可以使用 es6
規範來寫程式碼,然後用工具轉換成其他規範,而且 es6
的程式碼可以使用 tree-shaking
功能。
參考:
2. 選擇合適的構建工具
對於前端專案來說,因為有靜態資源(如圖片、字型等)載入與按需載入的需求,所以使用 webpack
是不二選擇,但對於第三方庫來說,其實還有更好的選擇:rollup
。
可以檢視 webpack 之外的另一種選擇:rollup 瞭解 webpack
與 rollup
之間各自的差異與優勢。
webpack
在打包成第三方庫的時候只能匯出 amd/commonjs/umd
,而 rollup
能夠匯出 amd/commonjs/umd/es6
。使用 rollup
匯出 es6
模組,就可以在使用這個庫的專案中構建時使用 tree-shaking
功能。
對於有樣式檔案(css
、less
、scss
)、靜態資原始檔(圖片、字型)的前端元件來說,可以使用 rollup-plugin-postcss 外掛配合 rollup
處理樣式檔案與靜態資原始檔。
參考:
- webpack、rollup、rollup-plugin-postcss
- webpack 之外的另一種選擇:rollup
- UMD (Universal Module Definition)
- tree-shaking
- webpack 如何優雅的使用tree-shaking(搖樹優化)
3. 定好目錄結構
一般庫專案的目錄:
|-- / # 專案根目錄
|-- src/ # 原始碼目錄
|-- lib/(dist/) # 釋出檔案目錄
|-- test/ # 測試檔案目錄
|-- ... # 更多其他目錄
如果是多包專案(一個專案裡有多個 npm packages,比如 babel):
|-- / # 專案根目錄
|-- packages/ # packages 目錄
|-- pkg1/ # package1 目錄
|-- src/ # 原始碼目錄
|-- lib/(dist/) # 釋出檔案目錄
|-- pkg2/ # package2 目錄
|-- src/ # 原始碼目錄
|-- lib/(dist/) # 釋出檔案目錄
|-- ...
後面會詳細講解多包專案。
4. 搭建一個好的腳手架
不管是應用專案還是第三方庫專案,都需要搭建一個好的腳手架,來幫助我們更好的編寫程式碼、構建專案等。
可以檢視 搭建自己的前端腳手架 瞭解一些基本的腳手架檔案與工具。
比如:
-
.editorconfig
: 用這個檔案來統一不同編輯器的一些配置,比如tab
轉 2 個空格、自動插入空尾行、去掉行尾的空格等,http://editorconfig.org - eslint、stylelint、prettier: 規範化程式碼風格、優化程式碼格式等
-
husky、lint-staged: 在
git
提交之前對程式碼進行審查,否則不予提交 -
.travis.yml
: 一個很棒的持續整合服務,https://www.travis-ci.org/
詳細的檔案、工具與配置,參考 搭建自己的前端腳手架。
另外,針對開源的第三方庫,還可以有:
-
LICENSE
: 協議檔案 -
CONTRIBUTING.md
: 專案程式碼參與者 -
codecov.yml
: 測試覆蓋率配置檔案 -
.github
:github
上的一些自定義配置,比如issue
模板、pr
模板等 -
/docs
: 文件目錄 -
/examples
: 使用示例目錄 -
/scripts
: 指令碼目錄
加上 rollup
的配置檔案 rollup.config.js
:
rollup.config.js
如果是 node
程式,把 es6
規範轉化成 commonjs
規範:
export default {
input: `src/index.js`,
output: {
file: `lib/index.js`,
format: `cjs`,
},
};
如果是前端庫,還需要轉 es6+
到 es5
、匯出不同規範的檔案(es6/commonjs/amd/umd
):
import babel from `rollup-plugin-babel`;
import postcss from `rollup-plugin-postcss`;
export default [
{
file: `lib/cjs.js`,
format: `cjs`,
},
{
file: `lib/m.js`,
format: `esm`,
},
{
file: `lib/umd.js`,
format: `umd`,
name: `Name`,
},
{
file: `lib/amd.js`,
format: `amd`,
},
].map(output => ({
input: `src/index.js`,
output,
plugins: [
babel({
presets: [`@babel/preset-env`],
}),
postcss({ extract: !0 }), // 構建樣式檔案時需要這個外掛
],
}));
.gitignore
一般來說,我們並不希望把釋出檔案放到 git
的版本控制之中,而只是釋出到倉庫而已,所以:
# .gitignore
.DS_Store
node_modules
bower_components
/coverage
*.log
.idea
.vscode
.eslintcache
package-lock.json
/lib # 把 lib 排除在外
/packages/*/lib # 多包專案
package.json
{
...
# node 專案
"main": "lib/index.js",
# 前端專案
"main": "lib/cjs.js", # commonjs 規範檔案
"module": "lib/m.js", # es6 規範檔案
"umd:main": "lib/umd.js", # umd 規範檔案
"amd:main": "lib/amd.js", # amd 規範檔案
"files": [ # 釋出時只發布 lib 目錄下檔案
"lib"
],
"scripts": {
...
"build": "rollup -c", # 構建釋出檔案
"prepublishOnly": "npm run build", # npm publish 之前先 npm run build
"pretest": "npm run build", # npm run test 之前先 npm run build
},
...
}
在實際專案中,構建工具(如 webpack
)會首先找這個包中的 module
欄位對應的 es6
規範檔案,並使用 tree-shaking
;如果不存在,然後找 main
欄位對應的檔案。
有些構建工具可能也會用 amd
規範檔案與 umd
規範檔案。
參考:
5. 構建多包專案
如果一個專案很大,需要分割成多個 npm
包進行管理,但這些包仍然在一個專案裡,並且這些包可能有相互依賴關係,這個時候就比較難以管理和開發了。
為了方便的管理多包專案,lerna 便應運而生,babel、create-react-app、jest、lila 等都是用 lerna
來管理多個包的。
英文不好的童鞋,可以參考 使用lerna管理大型前端專案,瞭解 lerna
的一些基本用法。
lerna 一般目錄檔案結構
my-lerna-repo/
package.json
packages/
package-1/
package.json
package-2/
package.json
安裝 lerna,初始化專案
# 安裝
npm i -g lerna
# 初始化
git init lerna-repo && cd lerna-repo
lerna init
# 初始化後的目錄及檔案
lerna-repo/
packages/
package.json
lerna.json
配置檔案 lerna.json
{
"version": "0.5.2", # 當前版本號
"packages": [
"packages/*"
],
"command": {
"publish": { # 釋出配置
"ignoreChanges": [ # 哪些檔案變動不會引發釋出新版本
"*.md",
"*.json",
"*.txt",
"test/**",
"example/**",
"package.json"
]
},
"bootstrap": {
"npmClient": "cnpm" # lerna bootstrap 時使用哪個 npm 客戶端
}
},
"npmClientArgs": [ # npm 客戶端 執行時的引數
"--no-package-lock"
]
}
常用命令
lerna publish
: 釋出所有有更新的包
在預設的固定模式(Fixed mode)下,這個命令會檢查 packages
目錄下哪些包的檔案有更新(lerna.json
中 command.publish.ignoreChanges
除外),然後把 lerna.json
中的 version
與有更新的包中 package.json
的 version
欄位更新到一個新的版本號上,最後把這些有更新的包都發布到遠端倉庫上。
lerna bootstrap
: 啟動建立包相互之間的 node_modules
連結
這個命令會根據各個包下 package.json
裡面的 dependencies
和 devDependencies
配置,使用 symlink
在各個包的 node_modules
下面建立引用關係。這樣就解決了相互之間有依賴而無法聯調的問題。
lerna changed
: 檢視哪些包有更新,可以釋出一個新的版本
lerna diff [package?]
: 檢視包都更新了些什麼
lerna run [script]
: 使用 npm
執行每個包下面的 [script]
參考:
6. 示例
單個包的 node
專案可以參考我的專案:sclean
單個包的前端專案可以參考我的專案:see-fetch
多個包的專案可以參考我的專案:lila
後續
更多部落格,檢視 https://github.com/senntyou/blogs
版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證)