Vite?+?React從零開(kāi)始搭建一個(gè)開(kāi)源組件庫(kù)
前言
日常使用開(kāi)源的組件庫(kù)時(shí)我們或多或少的都會(huì)做一些自定義的配置來(lái)符合實(shí)際的設(shè)計(jì),當(dāng)這些設(shè)計(jì)形成一定規(guī)模時(shí),設(shè)計(jì)獅們就會(huì)形成一套規(guī)范,實(shí)施到前端這里就變成了組件庫(kù)。
本文的目標(biāo)是從0開(kāi)始搭建一個(gè)面向組件庫(kù)的基礎(chǔ)設(shè)施,一起來(lái)探索下吧~。
??目標(biāo)
- 開(kāi)發(fā)環(huán)境
- 組件庫(kù)編譯,需要生成umd和esm模塊的組件代碼
- 支持按需導(dǎo)入與全量導(dǎo)入
- 組件文檔/預(yù)覽
- 代碼格式化和規(guī)范檢測(cè)工具
??搭建開(kāi)發(fā)環(huán)境
現(xiàn)在的時(shí)間點(diǎn)Vue或者React都可以用Vite來(lái)進(jìn)行開(kāi)發(fā)打包,這里有老前輩Vant的嘗試我們可以放心使用~。
???生成模板
yarn create vite my-components --template react-ts
這里我們創(chuàng)建生成一套react-ts的應(yīng)用模板,可以僅保留main.tsx
用于組件庫(kù)的開(kāi)發(fā)調(diào)試。
??CSS預(yù)處理器
CSS預(yù)處理器Sass與Less都可以選擇,這里用了Sass:
yarn add sass
不需要配置直接用就可以,與它搭配的規(guī)則檢查可以安裝stylelint:
yarn add stylelint stylelint-config-standard stylelint-config-prettier-scss stylelint-config-standard-scss stylelint-declaration-block-no-ignored-properties
同時(shí)根目錄下新建.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 } }
具體的規(guī)則可以查看文檔,或者直接用ant-design/vant的規(guī)范,總之制定一個(gè)用起來(lái)舒服的即可。
??eslint
eslint與stylelint基本一個(gè)套路,這里不再重復(fù),可以直接用開(kāi)源組件庫(kù)的規(guī)范。
??組件庫(kù)編譯
組件庫(kù)的編譯和默認(rèn)的應(yīng)用編譯有一些不同,Vite有預(yù)設(shè)的打包組件庫(kù)的選項(xiàng)可以幫我們省去大部分自定義的時(shí)間。
??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
,組件庫(kù)的打包入口需要是一個(gè)包含了所有組件的索引文件,大概可以長(zhǎng)這樣:
import Button from "./button/index"; export { Button };
默認(rèn)情況下Vite的配置會(huì)打包umd和esm兩種模式,只需要寫一下名字即可。
同時(shí)在打包時(shí)我們也不希望外部的庫(kù)打包進(jìn)去,像必然存在的react
,vue
,二次封裝的組件庫(kù)antd
,vant
這些都需要剔除出去。
現(xiàn)在直接build可以看到生成了es
和umd
兩份不同版本的文件,里面僅存在我們的代碼:
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.
??自動(dòng)生成ts類型文件
打包進(jìn)行到上面已經(jīng)初步可用,還不具備Ts的類型定義,用在Ts項(xiàng)目里會(huì)報(bào)錯(cuò),這里我們可以用Ts的rollup插件來(lái)生成對(duì)應(yīng)的類型:
yarn add @rollup/plugin-typescript tslib
Vite中的rollupOptions擴(kuò)展一下plugins:
{ ..., rollupOptions: { ..., plugins: [ typescript({ target: "es2015", // 這里指定編譯到的版本, rootDir: resolvePath("packages/"), declaration: true, declarationDir: resolvePath("dist"), exclude: resolvePath("node_modules/**"), allowSyntheticDefaultImports: true, }), ], } }
重新打包會(huì)發(fā)現(xiàn)所有packages
目錄下的文件都生成了一份d.ts
的類型定義。
??樣式懶加載與全量加載
日常應(yīng)用的開(kāi)發(fā)時(shí)我們會(huì)在組件里導(dǎo)入樣式,這樣打包時(shí)構(gòu)建工具會(huì)自動(dòng)處理。
在構(gòu)建組件庫(kù)時(shí)為了支持更多的環(huán)境考慮,組件內(nèi)不會(huì)導(dǎo)入樣式,樣式需要單獨(dú)處理。
可以選擇配置多入口或者用插件,Vite沒(méi)有找到如何配置多入口,所以這里選擇了用插件的方式。
開(kāi)發(fā)時(shí)由于我們的組件單獨(dú)組件內(nèi)不會(huì)導(dǎo)入具體的樣式,可以在開(kāi)發(fā)的入口處導(dǎo)入全量樣式省去手工導(dǎo)入的麻煩:
const req = import.meta.globEager("./*/style/index.scss"); export default req;
插件沒(méi)有找到可以直接用的插件,這里自己寫了一個(gè):
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, }); }); // 上半部分編譯單獨(dú)的css,下半部分會(huì)把所有css編譯為一整個(gè)。 const wholeCss = await postcss([autoprefixer, postcssImport]).process( allRawCss.join("\n") ); this.emitFile({ type: "asset", fileName: "styles.css", source: wholeCss.css, }); }, }; }
generateBundle
是rollup的插件運(yùn)行鉤子,更多信息可以在這里找到。
再次打包可以看到生成了單個(gè)的樣式與全量的樣式,全量的可以走CDN導(dǎo)入,按需加載的可以用如vite-plugin-imp的構(gòu)建工具進(jìn)行按需加載。
??文檔
文檔我們需要同時(shí)兼顧到預(yù)覽,這里我們可以選擇storybook:
npx storybook init
之后不需要配置,直接用即可。
內(nèi)置的mdx文件可以讓我們同時(shí)寫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 語(yǔ)法**
??npm 發(fā)布與發(fā)布前的測(cè)試
npm的發(fā)布流程比較簡(jiǎn)單,直接
npm login npm version patch npm publish
就可以了,對(duì)于私有的npm倉(cāng)庫(kù)地址我們可以在package.json中定義:
{ "publishConfig": { "registry": "https://npm.private.com" } }
,除此之外package.json中我們最好還要定義一下此組件庫(kù)的基礎(chǔ)入口信息:
{ "main": "./dist/componentsName.umd.js", "module": "./dist/componentsName.es.js", "typings": "./dist/index.d.ts", }
??測(cè)試
發(fā)布前的測(cè)試不同于單元測(cè)試(本文沒(méi)有折騰單元測(cè)試),我們需要將打包好的庫(kù)給實(shí)際的項(xiàng)目去使用,模擬安裝發(fā)布后的包:
在組件庫(kù)目錄運(yùn)行:
npm link
這樣會(huì)基于當(dāng)前目錄的名字創(chuàng)建一個(gè)符號(hào)鏈接,之后在實(shí)際的項(xiàng)目中再次運(yùn)行:
npm link componentsName
此時(shí)node_modules中對(duì)應(yīng)的包會(huì)鏈接到你的組件庫(kù)中,在組件庫(kù)的任何修改都可以及時(shí)反饋。
當(dāng)然不僅僅用于測(cè)試,開(kāi)發(fā)時(shí)也可以用這種方式。
到此這篇關(guān)于Vite + React 如何從0到1搭建一個(gè)開(kāi)源組件庫(kù)的文章就介紹到這了,更多相關(guān)React 搭建組件庫(kù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決React報(bào)錯(cuò)You provided a `checked` prop&n
這篇文章主要為大家介紹了React報(bào)錯(cuò)You provided a `checked` prop to a form field的解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12React引入antd-mobile+postcss搭建移動(dòng)端
本文給大家分享React引入antd-mobile+postcss搭建移動(dòng)端的詳細(xì)流程,文末給大家分享我的一些經(jīng)驗(yàn)記錄使用antd-mobile時(shí)發(fā)現(xiàn)我之前配置的postcss失效了,防止大家踩坑,特此把解決方案分享到腳本之家平臺(tái),需要的朋友參考下吧2021-06-06react中實(shí)現(xiàn)拖拽排序react-dnd功能
這篇文章主要介紹了react中實(shí)現(xiàn)拖拽排序react-dnd功能,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02在React頁(yè)面重新加載時(shí)保留數(shù)據(jù)的實(shí)現(xiàn)方法總結(jié)
在React頁(yè)面重新加載時(shí)保留數(shù)據(jù),可以通過(guò)多種方法來(lái)實(shí)現(xiàn),常見(jiàn)的方法包括使用瀏覽器的本地存儲(chǔ)(Local Storage 或 Session Storage)、URL參數(shù)、以及服務(wù)器端存儲(chǔ)等,本文給大家總結(jié)了一些具體實(shí)現(xiàn)方法,需要的朋友可以參考下2024-06-06使用?React?Hooks?重構(gòu)類組件的示例詳解
這篇文章主要介紹了如何使用?React?Hooks?重構(gòu)類組件,本文就來(lái)通過(guò)一些常見(jiàn)示例看看如何使用 React Hooks 來(lái)重構(gòu)類組件,需要的朋友可以參考下2022-07-07React實(shí)現(xiàn)前端選區(qū)的示例代碼
本文主要介紹了React實(shí)現(xiàn)前端選區(qū)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05