從 1 到完美,寫一個 js 庫、node 庫、前端組件庫

從 1 到完美,寫一個 js 庫、node 庫、前端組件庫

之前講了很多關於項目工程化、前端架構、前端構建等方面的技術,這次說說怎麼寫一個完美的第三方庫。

1. 選擇合適的規範來寫代碼

js 模塊化的發展大致有這樣一個過程 iife => commonjs/amd => es6,而在這幾個規範中:

  • iife: js 原生支持,但一般不會直接使用這種規範寫代碼
  • amd: requirejs 定義的加載規範,但隨着構建工具的出現,便一般不會用這種規範寫代碼
  • commonjs: node 的模塊加載規範,一般會用這種規範寫 node 程序
  • es6: ECMAScript2015 定義的模塊加載規範,但到目前爲止,幾乎所有的 js 運行環境都不支持,包括瀏覽器、node(包括 electronnw.js)、React Native

針對原生不支持任何規範的運行環境程序(如瀏覽器、React Native),建議使用 es6 規範來寫代碼,然後由工具轉換成原生 js 能夠運行的。

而針對 node 程序,可以直接用 commonjs 規範來寫,也可由 es6 規範來寫,然後用工具轉化成 commonjs 規範。

所以,總的來說,都可以使用 es6 規範來寫代碼,然後用工具轉換成其他規範,而且 es6 的代碼可以使用 tree-shaking 功能。

參考:

2. 選擇合適的構建工具

對於前端項目來說,因爲有靜態資源(如圖片、字體等)加載與按需加載的需求,所以使用 webpack 是不二選擇,但對於第三方庫來說,其實還有更好的選擇:rollup

可以查看 webpack 之外的另一種選擇:rollup 瞭解 webpackrollup 之間各自的差異與優勢。

webpack 在打包成第三方庫的時候只能導出 amd/commonjs/umd,而 rollup 能夠導出 amd/commonjs/umd/es6。使用 rollup 導出 es6 模塊,就可以在使用這個庫的項目中構建時使用 tree-shaking 功能。

對於有樣式文件(csslessscss)、靜態資源文件(圖片、字體)的前端組件來說,可以使用 rollup-plugin-postcss 插件配合 rollup 處理樣式文件與靜態資源文件。

參考:

3. 定好目錄結構

一般庫項目的目錄:

|-- /                   # 項目根目錄
    |-- src/            # 源代碼目錄
    |-- lib/(dist/)     # 發佈文件目錄
    
    |-- test/           # 測試文件目錄
    |-- ...             # 更多其他目錄

如果是多包項目(一個項目裏有多個 npm packages,比如 babel):

|-- /                           # 項目根目錄
    |-- packages/               # packages 目錄   
        |-- pkg1/               # package1 目錄
            |-- src/            # 源代碼目錄
            |-- lib/(dist/)     # 發佈文件目錄
        |-- pkg2/               # package2 目錄
            |-- src/            # 源代碼目錄
            |-- lib/(dist/)     # 發佈文件目錄
            
        |-- ...
                   

後面會詳細講解多包項目。

4. 搭建一個好的腳手架

不管是應用項目還是第三方庫項目,都需要搭建一個好的腳手架,來幫助我們更好的編寫代碼、構建項目等。

可以查看 搭建自己的前端腳手架 瞭解一些基本的腳手架文件與工具。

比如:

詳細的文件、工具與配置,參考 搭建自己的前端腳手架

另外,針對開源的第三方庫,還可以有:

  • 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 便應運而生,babelcreate-react-appjestlila 等都是用 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.jsoncommand.publish.ignoreChanges 除外),然後把 lerna.json 中的 version 與有更新的包中 package.jsonversion 字段更新到一個新的版本號上,最後把這些有更新的包都發布到遠程倉庫上。

lerna bootstrap: 啓動建立包相互之間的 node_modules 鏈接

這個命令會根據各個包下 package.json 裏面的 dependenciesdevDependencies 配置,使用 symlink 在各個包的 node_modules 下面建立引用關係。這樣就解決了相互之間有依賴而無法聯調的問題。

lerna changed: 查看哪些包有更新,可以發佈一個新的版本

lerna diff [package?]: 查看包都更新了些什麼

lerna run [script]: 使用 npm 運行每個包下面的 [script]

參考:

6. 示例

單個包的 node 項目可以參考我的項目:sclean

單個包的前端項目可以參考我的項目:see-fetch

多個包的項目可以參考我的項目:lila

後續

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章