Vite + React從零開始搭建一個開源組件庫

前言

日常使用開源的組件庫時我們或多或少的都會做一些自定義的配置來符合實際的設計,當這些設計形成一定規模時,設計獅們就會形成一套規范,實施到前端這裡就變成瞭組件庫。

本文的目標是從0開始搭建一個面向組件庫的基礎設施,一起來探索下吧~。

👣目標

  • 開發環境
  • 組件庫編譯,需要生成umd和esm模塊的組件代碼
  • 支持按需導入與全量導入
  • 組件文檔/預覽
  • 代碼格式化和規范檢測工具

🌈搭建開發環境

現在的時間點Vue或者React都可以用Vite來進行開發打包,這裡有老前輩Vant的嘗試我們可以放心使用~。

🛠️生成模板

yarn create vite my-components --template react-ts

這裡我們創建生成一套react-ts的應用模板,可以僅保留main.tsx用於組件庫的開發調試。

🔩CSS預處理器

CSS預處理器Sass與Less都可以選擇,這裡用瞭Sass:

yarn add sass

不需要配置直接用就可以,與它搭配的規則檢查可以安裝stylelint:

yarn add stylelint stylelint-config-standard stylelint-config-prettier-scss stylelint-config-standard-scss stylelint-declaration-block-no-ignored-properties

同時根目錄下新建.stylelintrc:

{
  "extends": [
    "stylelint-config-standard",
    "stylelint-config-prettier-scss",
    "stylelint-config-standard-scss"
  ],
  "plugins": [
    "stylelint-declaration-block-no-ignored-properties"
  ],
  "rules": {
    "no-descending-specificity": null,
    "no-invalid-position-at-import-rule": null,
    "declaration-empty-line-before": null,
    "keyframes-name-pattern": null,
    "custom-property-pattern": null,
    "number-max-precision": 8,
    "alpha-value-notation": "number",
    "color-function-notation": "legacy",
    "selector-class-pattern": null,
    "selector-id-pattern": null,
    "selector-not-notation": null
  }
}

具體的規則可以查看文檔,或者直接用ant-design/vant的規范,總之制定一個用起來舒服的即可。

🔩eslint

eslint與stylelint基本一個套路,這裡不再重復,可以直接用開源組件庫的規范。

💪組件庫編譯

組件庫的編譯和默認的應用編譯有一些不同,Vite有預設的打包組件庫的選項可以幫我們省去大部分自定義的時間。

⚡️vite的打包配置

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

const path = require("path");

const resolvePath = (str: string) => path.resolve(__dirname, str);

export default defineConfig({
  plugins: [react()],
  build: {
    lib: {
      entry: resolvePath("packages/index.ts"),
      name: "componentsName",
      fileName: format => `componentsName.${format}.js`,
    },
    rollupOptions: {
        external: ["react", "react-dom", "antd"],
        output: {
          globals: {
            react: "react",
            antd: "antd",
            "react-dom": "react-dom",
          },
        },      
    },
  },
})

這裡我們的入口不是上面的main.tsx,組件庫的打包入口需要是一個包含瞭所有組件的索引文件,大概可以長這樣:

import Button from "./button/index";
export { Button };

默認情況下Vite的配置會打包umd和esm兩種模式,隻需要寫一下名字即可。

同時在打包時我們也不希望外部的庫打包進去,像必然存在的reactvue,二次封裝的組件庫antdvant這些都需要剔除出去。

現在直接build可以看到生成瞭esumd兩份不同版本的文件,裡面僅存在我們的代碼:

yarn build
$ tsc && vite build
vite v2.9.12 building for production...
✓ 10 modules transformed.
dist/componentsName.es.js   2.27 KiB / gzip: 0.97 KiB
dist/componentsName.umd.js   1.81 KiB / gzip: 0.96 KiB
✨  Done in 3.12s.

⚡️自動生成ts類型文件

打包進行到上面已經初步可用,還不具備Ts的類型定義,用在Ts項目裡會報錯,這裡我們可以用Ts的rollup插件來生成對應的類型:

yarn add @rollup/plugin-typescript tslib

Vite中的rollupOptions擴展一下plugins:

{
  ...,
  rollupOptions: {
    ...,
    plugins: [
      typescript({
        target: "es2015", // 這裡指定編譯到的版本,
        rootDir: resolvePath("packages/"),
        declaration: true,
        declarationDir: resolvePath("dist"),
        exclude: resolvePath("node_modules/**"),
        allowSyntheticDefaultImports: true,
      }),
    ],
  }
}

重新打包會發現所有packages目錄下的文件都生成瞭一份d.ts的類型定義。

⚡️樣式懶加載與全量加載

日常應用的開發時我們會在組件裡導入樣式,這樣打包時構建工具會自動處理。

在構建組件庫時為瞭支持更多的環境考慮,組件內不會導入樣式,樣式需要單獨處理。

可以選擇配置多入口或者用插件,Vite沒有找到如何配置多入口,所以這裡選擇瞭用插件的方式。

開發時由於我們的組件單獨組件內不會導入具體的樣式,可以在開發的入口處導入全量樣式省去手工導入的麻煩:

const req = import.meta.globEager("./*/style/index.scss");

export default req;

插件沒有找到可以直接用的插件,這裡自己寫瞭一個:

import { compile } from "sass";
import postcss from "postcss";
import postcssImport from "postcss-import";

const autoprefixer = require("autoprefixer");

const path = require("path");

const resolvePath = str => path.resolve(__dirname, str);

const glob = require("glob");

function generateCssPlugin() {
  return {
    name: "generate-css",
    async generateBundle() {
      const files = glob.sync(resolvePath("packages/**/style/*.scss"));

      const allProcess = [];
      const allRawCss = [];

      files.forEach(file => {
        const { css } = compile(file);
        allRawCss.push(css);
        const result = postcss([autoprefixer, postcssImport]).process(css, {
          from: file,
          to: file.replace(resolvePath("packages"), "dist"),
        });

        allProcess.push(result);
      });

      const results = await Promise.all(allProcess);

      results.forEach(result => {
        this.emitFile({
          type: "asset",
          fileName: result.opts.from
            .replace(resolvePath("packages"), "dist")
            .replace("dist/", "")
            .replace("scss", "css"),
          source: result.css,
        });
      });

      // 上半部分編譯單獨的css,下半部分會把所有css編譯為一整個。

      const wholeCss = await postcss([autoprefixer, postcssImport]).process(
        allRawCss.join("\n")
      );

      this.emitFile({
        type: "asset",
        fileName: "styles.css",
        source: wholeCss.css,
      });
    },
  };
}

generateBundle是rollup的插件運行鉤子,更多信息可以在這裡找到。

再次打包可以看到生成瞭單個的樣式與全量的樣式,全量的可以走CDN導入,按需加載的可以用如vite-plugin-imp的構建工具進行按需加載。

📦文檔

文檔我們需要同時兼顧到預覽,這裡我們可以選擇storybook:

npx storybook init

之後不需要配置,直接用即可。

內置的mdx文件可以讓我們同時寫Markdown與jsx:

import { Meta, Story } from "@storybook/addon-docs";

import { Button } from "../packages";

<Meta title="Button" component={Button} />

<Canvas>
  <Story name="Button">
    <Button>這裡寫Jsx</Button>
  </Story>
</Canvas>

# 用法

**markdown 語法**

❤️npm 發佈與發佈前的測試

npm的發佈流程比較簡單,直接

npm login

npm version patch

npm publish

就可以瞭,對於私有的npm倉庫地址我們可以在package.json中定義:

{
  "publishConfig": {
    "registry": "https://npm.private.com"
  }
}

,除此之外package.json中我們最好還要定義一下此組件庫的基礎入口信息:

{
  "main": "./dist/componentsName.umd.js",
  "module": "./dist/componentsName.es.js",
  "typings": "./dist/index.d.ts",
}

💡測試

發佈前的測試不同於單元測試(本文沒有折騰單元測試),我們需要將打包好的庫給實際的項目去使用,模擬安裝發佈後的包:

在組件庫目錄運行:

npm link

這樣會基於當前目錄的名字創建一個符號鏈接,之後在實際的項目中再次運行:

npm link componentsName

此時node_modules中對應的包會鏈接到你的組件庫中,在組件庫的任何修改都可以及時反饋。

當然不僅僅用於測試,開發時也可以用這種方式。

到此這篇關於Vite + React 如何從0到1搭建一個開源組件庫的文章就介紹到這瞭,更多相關React 搭建組件庫內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: