Vite + React从零开始搭建一个开源组件库


Posted in Javascript onJune 25, 2022

前言

日常使用开源的组件库时我们或多或少的都会做一些自定义的配置来符合实际的设计,当这些设计形成一定规模时,设计狮们就会形成一套规范,实施到前端这里就变成了组件库。

本文的目标是从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的构建工具进行按需加载。

Vite + React从零开始搭建一个开源组件库

?文档

文档我们需要同时兼顾到预览,这里我们可以选择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 搭建组件库内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

Javascript 相关文章推荐
如何将一个String和多个String值进行比较思路分析
Apr 22 Javascript
JS、CSS加载中的小问题探讨
Nov 26 Javascript
js调用后台、后台调用前台等方法总结
Apr 17 Javascript
字符串反转_JavaScript
Apr 28 Javascript
JavaScript导航脚本判断当前导航
Jul 12 Javascript
详解vue中使用express+fetch获取本地json文件
Oct 10 Javascript
JS实现的抛物线运动效果示例
Jan 30 Javascript
JS中创建自定义类型的常用模式总结【工厂模式,构造函数模式,原型模式,动态原型模式等】
Jan 19 Javascript
利用原生JavaScript实现造日历轮子实例代码
May 08 Javascript
使用vue实现各类弹出框组件
Jul 03 Javascript
js实现单元格拖拽效果
Feb 10 Javascript
基于jquery实现彩色投票进度条代码解析
Aug 26 jQuery
React自定义hook的方法
Jun 25 #Javascript
小程序实现侧滑删除功能
Jun 25 #Javascript
小程序自定义轮播图圆点组件
Jun 25 #Javascript
微信小程序实现轮播图指示器
Jun 25 #Javascript
create-react-app开发常用配置教程
Jun 25 #Javascript
JavaScript架构搭建前端监控如何采集异常数据
Jun 25 #Javascript
webpack介绍使用配置教程详解webpack介绍和使用
Jun 25 #Javascript
You might like
PHP 引用是个坏习惯
2010/03/12 PHP
php使用imagick模块实现图片缩放、裁剪、压缩示例
2014/04/17 PHP
PHP函数实现从一个文本字符串中提取关键字的方法
2015/07/01 PHP
php解决安全问题的方法实例
2019/09/19 PHP
thinkphp框架类库扩展操作示例
2019/11/26 PHP
laravel框架中控制器的创建和使用方法分析
2019/11/23 PHP
thinkphp5 框架结合plupload实现图片批量上传功能示例
2020/04/04 PHP
JavaScript 节点操作 以及DOMDocument属性和方法
2007/12/06 Javascript
JavaScript自定义日期格式化函数详细解析
2014/01/14 Javascript
Chrome扩展页面动态绑定JS事件提示错误
2014/02/11 Javascript
JQuery EasyUI 加载两次url的原因分析及解决方案
2014/08/18 Javascript
js使用for循环与innerHTML获取选中tr下td值
2014/09/26 Javascript
jquery插件pagination实现无刷新ajax分页
2015/09/30 Javascript
CascadeView级联组件实现思路详解(分离思想和单链表)
2016/04/12 Javascript
深入理解jQuery之事件移除
2016/06/02 Javascript
jquery ajax结合thinkphp的getjson实现跨域的方法
2016/06/06 Javascript
概述如何实现一个简单的浏览器端js模块加载器
2016/12/07 Javascript
js实现简单的获取验证码按钮效果
2017/03/03 Javascript
vue.js实现三级菜单效果
2019/10/19 Javascript
Vue watch响应数据实现方法解析
2020/07/10 Javascript
python解决方案:WindowsError: [Error 2]
2016/08/28 Python
利用Pandas 创建空的DataFrame方法
2018/04/08 Python
python3 requests中使用ip代理池随机生成ip的实例
2018/05/07 Python
Python多进程池 multiprocessing Pool用法示例
2018/09/07 Python
python多任务及返回值的处理方法
2019/01/22 Python
在ipython notebook中使用argparse方式
2020/04/20 Python
详解python UDP 编程
2020/08/24 Python
Django数据统计功能count()的使用
2020/11/30 Python
Does C# support multiple inheritance? (C#支持多重继承吗)
2012/01/04 面试题
高分子材料与工程专业推荐信
2013/12/01 职场文书
舞蹈兴趣小组活动总结
2014/07/07 职场文书
“九一八事变纪念日”国旗下讲话稿
2014/09/14 职场文书
小学教师2014年度工作总结
2014/12/03 职场文书
nginx里的rewrite跳转的实现
2021/03/31 Servers
Redis命令处理过程源码解析
2022/02/12 Redis
MySQL解决Navicat设置默认字符串时的报错问题
2022/06/16 MySQL