之前講了很多關於項目工程化、前端架構、前端構建等方面的技術,這次說說怎么寫一個完美的第三方庫。
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許可證)