使用Typescript和Rollup從零開發一個工具庫, 並使用Github Actions進行CI操作

lxnxbnq發表於2022-12-22

在日常開發中,經常會遇到一些通用的邏輯,導致每次都需要複製貼上。而我們作為coder,可以將一些常用業務邏輯封裝成通用的函式庫,併發布到npm中。
這樣,每次遇到新的專案時,只需要 install一下即可

這裡我們已經有了一個fe-utils 前端日常開發工具庫,也是本文最後的產物,並且後續也會持續更新。如果你有一個開源的心,但是沒信心去為大的專案提pr,不妨從這個封裝日常通用邏輯的專案做起,我們一起進步!!!

接下來我們就嘗試自己動手實現一個工具庫

建立專案

首先在自己的github上建立一個專案,然後拉取。進入到該倉庫中,執行yarn init。一路enter下去。

主要目錄結構

- .github
    - workflows
        - xx.yml
- .husky
- src
    - __test__
    - index.ts
    - sum.ts
- .editorconfig
- .gitignore
- .npmignore
- commitlint.config.js
- jest.config.mjs
- package.json
- rollup.config.js
- tsconfig.json

配置專案

1. 安裝rollup和ts

yarn add rollup typescript -D

2. 配置typescript配置檔案

yarn tsc --init生成一個預設的配置檔案,然後根據我們的專案,改成如下:

{
  "compilerOptions": {
    "target": "es5" /* 編譯目標 */,
    "module": "commonjs" /* 專案模組型別 */,
    "lib": ["ES2018", "DOM"],
    "allowJs": true /* 是否允許js程式碼 */,
    "checkJs": true /* 檢查js程式碼錯誤 */,
    "declaration": true /* 自動建立宣告檔案(.d.ts) */,
    "declarationDir": "./lib" /* 宣告檔案目錄 */,
    "sourceMap": true /* 自動生成sourcemap檔案 */,
    "outDir": "lib" /* 編譯輸出目錄 */,
    "rootDir": "./src" /* 專案原始碼根目錄,用來控制編譯輸出的目錄結構 */,
    "strict": true /* 啟用嚴格模式 */
  },
  "include": ["src/index.ts"],
  "exclude": ["node_modules", "lib"]
}

3. 配置rollup.config.js

在配置之前,我們需要安裝幾個rollup外掛
yarn add @rollup/plugin-node-resolve @rollup/plugin-typescript @rollup/plugin-commonjs rollup-plugin-terser -D

這幾個分別是如下作用

  • @rollup/plugin-node-resolve 處理路徑
  • @rollup/plugin-typescript 支援ts
  • @rollup/plugin-commonjs 處理commonjs
  • rollup-plugin-terser 壓縮umd規範的輸出檔案
const resolve = require('@rollup/plugin-node-resolve');
const typescript = require('@rollup/plugin-typescript');
const commonjs = require('@rollup/plugin-commonjs');
const { terser } = require('rollup-plugin-terser')

module.exports = [

  {
    input: './src/index.ts',
    output: [
      {
        dir: 'lib',
        format: 'cjs',
        entryFileNames: '[name].cjs.js',
        sourcemap: false, // 是否輸出sourcemap
      },
      {
        dir: 'lib',
        format: 'esm',
        entryFileNames: '[name].esm.js',
        sourcemap: false, // 是否輸出sourcemap
      },
      {
        dir: 'lib',
        format: 'umd',
        entryFileNames: '[name].umd.js',
        name: 'FE_utils', // umd模組名稱,相當於一個名稱空間,會自動掛載到window下面
        sourcemap: false,
        plugins: [terser()],
      },
    ],
    plugins: [resolve(), commonjs(), typescript({ module: "ESNext"})],
  }
]

4. 修改package.json

我們直接看完整的package.json

{
  "name": "@lxnxbnq/utils",
  "version": "0.0.2-alpha",
  "main": "lib/index.cjs.js",
  "module": "lib/index.esm.js",
  "jsnext:main": "lib/index.esm.js",
  "browser": "lib/index.umd.js",
  "types": "lib/index.d.ts",
  "files": [
    "lib"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/SaebaRyoo/fe-utils.git"
  },
  "author": "SaebaRyoo <yuanddmail@163.com>",
  "license": "MIT",
  "scripts": {
    "build": "rollup -c",
    "test": "jest"
  },
  "devDependencies": {
    "@rollup/plugin-babel": "^6.0.3",
    "@rollup/plugin-commonjs": "^23.0.4",
    "@rollup/plugin-node-resolve": "^15.0.1",
    "@rollup/plugin-typescript": "^10.0.1",
    "@types/jest": "^29.2.4",
    "jest": "^29.3.1",
    "rollup": "^3.7.2",
    "rollup-plugin-terser": "^7.0.2",
    "ts-jest": "^29.0.3",
    "tslib": "^2.4.1",
    "typescript": "^4.9.4"
  },
  "description": "前端業務程式碼工具庫",
  "bugs": {
    "url": "https://github.com/SaebaRyoo/fe-utils/issues"
  },
  "homepage": "https://github.com/SaebaRyoo/fe-utils#readme",
  "dependencies": {}
}

其中需要注意的有如下幾個欄位

告知使用者不同的規範引用哪個檔案

  • "main": "lib/index.cjs.js", // 當使用commonjs規範時會使用這個包
  • "module": "lib/index.esm.js", // 使用esm時,會使用這個包
  • "jsnext:main": "lib/index.esm.js", //這個同上,不過這個是社群規範,上面是官方規範
  • "browser": "lib/index.umd.js", // umd規範,當直接在瀏覽器中開發時,可以直接下載release包並在瀏覽器中使用script匯入

ts型別檔案

  • "types": "lib/index.d.ts",

使用yarn run build打包專案

  • "scripts": {
    "build": "rollup -c",
    },

files 欄位是用於約定在發包的時候NPM 會發布包含的檔案和資料夾。

"files": [

"lib"

],

5. 安裝jest + lint + prettier + husky + commit-msg對程式碼的質量進行約束

首先是安裝安裝測試框架jest,因為專案時基於ts寫的,所以需要配置jest來支援ts
yarn add -D jest ts-jest @types/jest

  1. 建立配置檔案
    yarn jest --init
  2. 修改配置檔案,完整如下

jest的預設環境是node,但是我們這個工具庫是面向前端的,肯定需要操作dom,所以需要安裝yarn add jest-environment-jsdom -D來支援DOM和BOM操作

然後就是在使用到DOM或者BOM物件的測試檔案的頂部加上這一行註釋即可執行

/**
 * @jest-environment jsdom
 */

或者在配置檔案中修改執行環境為testEnvironment: 'jsdom'

/*
 * For a detailed explanation regarding each configuration property, visit:
 * https://jestjs.io/docs/configuration
 */

export default {
  clearMocks: true,
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageProvider: 'v8',
  preset: 'ts-jest',
  testEnvironment: 'jsdom', // 支援測試環境訪問dom
  // 配置測試環境ua
  testEnvironmentOptions: {
    userAgent:
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
  },
};
  1. 在package.json的script中新增cli命令

    {
      "scripts": {
     "test": "jest",
     "coveralls": "jest --coverage",
      }
    }
  2. 最後按照這篇文章的步驟來配置規範程式碼的檔案

開發

我們先在src的目錄下寫一個簡單的sum方法以及一個單元測試

src/sum.ts

export function sum(...args: number[]): number {
  return args.reduce((prev, total) => total + prev, 0);
}

在入口中匯入並匯出

src/index.ts

export { sum } from './sum';

寫一個sum的單元測試

src/__test__/sum.test.ts

import {sum} from '../index'

describe('sum', () => {
  it('should work', () => {
    expect(sum()).toEqual(0)
  })
})

到這,基本上一個最簡單的npm包就開發完了,接下來就是需要釋出

npm包釋出

1. 手動釋出

首先需要準備一個賬號,然後進行登入,輸入你的npm賬號、密碼、郵箱
npm login

可以用npm logout退出當前賬號

npm who am i查詢當前登入的賬號

登入成功就可以透過npm publish將包推送到伺服器上

如果某版本的包有問題,可以使用npm unpublish [pkg]@[version]將其撤回

注意:如果使用@[scope]/package的命名形式,[scope]一定要寫你的賬號名,不然釋出的時候會提示404

2. 使用github action 釋出npm、建立release 以及處理一些工作流程

如果不瞭解github action的話, 建議先學習一下github actions的一些概念

在根目錄下建立.github/workflows/node.js.ymlCI配置檔案(這裡也可以在倉庫上的tab欄中找到Actions生成)

注意:如果你需要不同的actions,可以在Marketplace中查詢需要的action

目標

  1. 自動釋出npm包
  2. 建立release並上傳對應asset
  3. 跑單元測試,生成測試覆蓋率提交到coveralls

準備工作

  1. 在npm中生成token

npm_access_token

  1. 然後複製token到github對應倉庫的秘鑰中

截圖2022-12-20 20.00.26.png

  1. 設定一個變數名,我們這裡設定的是NPM_ACCESS_TOKEN,後面可以在CI中透過secrets.NPM_ACCESS_TOKEN 獲取到

整體程式碼

有了以上的思路來看下面的整體程式碼

# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Node.js CI

on:
  push:
    branches: [ "main" ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'yarn'
      - run: yarn
      # 測試,並生成測試覆蓋率檔案
      - run: yarn run coveralls
      - run: yarn run build
      # 上報
      - name: Coveralls
        uses: coverallsapp/github-action@master
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

  # publish-npm任務
  publish-npm:
    # 在ubuntu最新版本的虛擬機器執行
    runs-on: ubuntu-latest
    # 設定變數
    strategy:
      matrix:
        node-version: [ 18.x ]
    steps:
      # 檢查並切換到main分支
      - name: 檢查main分支
        # 使用actions/checkout外掛
        uses: actions/checkout@v3

      # 初始化快取
      - name:  快取
        uses: actions/cache@v3
        id: cache-dependencies
        with:
          path: node_modules
          key: ${{runner.OS}}-${{hashFiles('**/yarn.lock')}}

      # 安裝node
      - name: 設定Node.js
        # 使用actions/setup-node外掛
        uses: actions/setup-node@v3
        with:
          # node版本
          node-version: ${{ matrix.node-version }}
      - run: yarn
      - run: yarn run build

      # 讀取當前版本號
      - name: 讀取當前版本號
        id: version
        uses: notiz-dev/github-action-json-property@release
        with:
          # 讀取版本號
          path: './package.json'
          prop_path: 'version'

      - run: echo ${{steps.version.outputs.prop}}

      # 建立Release
      - name: release
        # 原本使用的 actions/create-release@latest來發版, actions/upload-release-asset@v1上傳release-asset
        # 不過這兩個action官方已經停止維護了,所以換成如下
        uses: softprops/action-gh-release@v1
        with:
          files: ./lib/index.umd.js
          name: v${{steps.version.outputs.prop}}
          tag_name: v${{steps.version.outputs.prop}}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # 釋出NPM包
      - name: 釋出NPM包
        # 執行釋出程式碼
        run: |
          npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
          npm publish
        env:
          # 配置 npm access token 環境變數
          NPM_TOKEN: ${{secrets.NPM_ACCESS_TOKEN}}

      # 重新整理快取
      - name: 重新整理快取
        run: |
          curl https://purge.jsdelivr.net/npm/iemotion-pic@latest/lib/name.json

為readme新增badge(徽章)

我們會發現在一個開源專案中,readme通常都會寫的很好,而且還有很多的badge,如ant-design的readme

那麼這一切都是怎麼完成的呢?一些簡單的badge可以直接在shields中輸入倉庫名即可生成。

比如:

workflow工作流狀態: GitHub Workflow Status

npm包版本: npm

license: GitHub

我們也可以根據自己需要來建立不同的badge

不過要新增測試覆蓋率的badge會稍稍有些麻煩。

  1. 首先進入coveralls官網,進去後需要透過github的授權
  2. 授權後點選左側側邊欄的 ADD REPOS 會進入如下頁面

image.png
然後我們將需要生成badge徽章的庫設定為on即可

  1. 後面的流程就是在CI中執行測試指令碼並生成測試覆蓋率的檔案然後上傳到coveralls就可以了

相關文章