温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何搭建react+ts组件库

发布时间:2022-05-30 10:22:03 来源:亿速云 阅读:234 作者:zzz 栏目:开发技术

本篇内容介绍了“如何搭建react+ts组件库”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

整体需求

  • react组件库,取名r-ui,能够导出r-ui.umd.js和r-ui.umd.css。

  • 代码使用typescript进行开发。

  • 样式使用less进行开发。

  • 引入antd组件库作为底层原子组件库,并且r-ui.umd.js和r-ui.umd.css包含antd组件代码和样式代码。

  • 依赖的react、react-dom模块以外部引用方式。

开发与打包工具选型

使用webpack作为打包工具

老牌而又经典的打包工具,广泛的使用,丰富的插件生态以及各种易得的样例。

使用babel来处理typescript代码

由于 TypeScript 和 Babel 团队官方合作了一年的项目:TypeScript plugin for Babel(@babel/preset-typescript),TypeScript 的使用变得比以往任何时候都容易。 —— 摘自《TypeScript With Babel: A Beautiful Marriage (TypeScript 和 Babel:美丽的结合)》

建议各位读者可以先阅读一下上面的文章(有中文翻译文章)。

使用less-loader、css-loader等处理样式代码

使用MiniCssExtractPlugin分离CSS

项目搭建思路

整体结构

- r-ui
  |- src
     |- components
        |- button
           |- index.tsx
  |- index.tsx

方案思路

编写webpack.config.js配置文件,添加核心loader:

  • babel-loader。接收ts文件,交给babel-core以及相关babel插件进行处理,得到js代码。

  • less-loader。接收less样式文件,处理得到css样式代码。

  • css-loader+MiniCssExtractPlugin.loader。接收css样式代码进行处理,并分离导出组件库样式文件。

项目搭建实施

初始化

初始化r-ui项目

mkdir r-ui && cd r-ui && npm init
# 配置项目基本信息(name、version......)

初始化git仓库,添加gitignore文件(后续所有命令非特殊情况,均相对于项目根目录)

git init
# .gitignore文件内容请直接查看项目内文件
# 完成后,初始提交:
# git add . && git commit -m "init"

安装webpack(包管理器使用yarn)

yarn add -D webpack webpack-cli webpack-dev-server
# 安装webpack-dev-server是为后续构建样例页面做准备,前期可以不安装。
diff --git a/package.json b/package.json
index e01c1b1..53dd9a3 100644
--- a/package.json
+++ b/package.json
@@ -8,5 +8,9 @@
   },
   "author": "",
   "license": "MIT",
-  "devDependencies": {}
+  "devDependencies": {
+    "webpack": "^5.72.1",
+    "webpack-cli": "^4.9.2",
+    "webpack-dev-server": "^4.9.0"
+  }
 }

项目根目录添加webpack.config.js并进行初始配置

// webpack.config.js
const {resolve} = require("path");
module.exports = {
  // 组件库的起点入口
  entry: './src/index.tsx',
  output: {
    filename: "r-ui.umd.js", // 打包后的文件名
    path: resolve(__dirname, 'dist'), // 打包后的文件目录:根目录/dist/
    library: 'rui', // 导出的UMD js会在window挂rui,即可以访问window.rui
    libraryTarget: 'umd' // 导出库为UMD形式
  },
  resolve: {
    // webpack 默认只处理js、jsx等js代码
    extensions: ['.js', '.jsx', '.ts', '.tsx']
  },
  externals: {},
  // 模块
  module: {
    // 规则
    rules: []
  }
};

Babel引入

引入babel-loader以及相关babel库

yarn add -D babel-loader @babel/core @babel/preset-env @babel/preset-typescript @babel/preset-react @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread
diff --git a/package.json b/package.json
index 53dd9a3..33c32b6 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,13 @@
   "author": "",
   "license": "MIT",
   "devDependencies": {
+    "@babel/core": "^7.18.2",
+    "@babel/plugin-proposal-class-properties": "^7.17.12",
+    "@babel/plugin-proposal-object-rest-spread": "^7.18.0",
+    "@babel/preset-env": "^7.18.2",
+    "@babel/preset-react": "^7.17.12",
+    "@babel/preset-typescript": "^7.17.12",
+    "babel-loader": "^8.2.5",
     "webpack": "^5.72.1",
     "webpack-cli": "^4.9.2",
     "webpack-dev-server": "^4.9.0"
(END)

了解Babel

如果对于babel不太熟悉,可能对这一堆的依赖感到恐惧,这里如果读者有时间,我推荐这篇深入了解babel的文章:一口(很长的)气了解 babel - 知乎 (zhihu.com)。当然,如果这口气憋不住(哈哈),我做一个简单摘抄:

babel 总共分为三个阶段:解析,转换,生成。

babel 本身不具有任何转化功能,它把转化的功能都分解到一个个 plugin 里面。因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的。

插件总共分为两种:

当我们添加 语法插件 之后,在解析这一步就使得 babel 能够解析更多的语法。(顺带一提,babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发)

举个简单的例子,当我们定义或者调用方法时,最后一个参数之后是不允许增加逗号的,如 callFoo(param1, param2,) 就是非法的。如果源码是这种写法,经过 babel 之后就会提示语法错误。

但最近的 JS 提案中已经允许了这种新的写法(让代码 diff 更加清晰)。为了避免 babel 报错,就需要增加语法插件 babel-plugin-syntax-trailing-function-commas

当我们添加 转译插件 之后,在转换这一步把源码转换并输出。这也是我们使用 babel 最本质的需求。

比起语法插件,转译插件其实更好理解,比如箭头函数 (a) => a 就会转化为 function (a) {return a}。完成这个工作的插件叫做 babel-plugin-transform-es2015-arrow-functions

同一类语法可能同时存在语法插件版本和转译插件版本。如果我们使用了转译插件,就不用再使用语法插件了。

简单来讲,使用babel就像如下流程:

源代码 =babel=> 目标代码

如果没有使用任何插件,源代码和目标代码就没有任何差异。当我们引入各种插件的时候,就像如下流程一样:

源代码
|
进入babel
|
babel插件1处理代码:移除某些符号
|
babel插件2处理代码:将形如() => {}的箭头函数,转换成function xxx() {}
|
目标代码

因为babel的插件处理的力度很细,我们代码的语法、语义内容规范有很多,如果我们要处理这些语法,可能需要配置一大堆的插件,所以babel提出,将一堆插件组合成一个preset(预置插件包),这样,我们只需要引入一个插件组合包,就能处理代码的各种语法、语义。

所以,回到我们上述的那些@babel开头的npm包,再回首可能不会那么迷茫:

@babel/core
@babel/preset-env
@babel/preset-typescript
@babel/preset-react
@babel/plugin-proposal-class-properties
@babel/plugin-proposal-object-rest-spread

@babel/core毋庸置疑,babel的核心模块,实现了上述的流程运转以及代码语法、语义分析的功能。

以plugin开头的就是插件,这里我们引入了两个:@babel/plugin-proposal-class-properties(允许类具有属性)和@babel/plugin-proposal-object-rest-spread(对象展开)。

以preset开头的就是预置组件包合集,其中@babel/preset-env表示使用了可以根据实际的浏览器运行环境,会选择相关的转义插件包:

env 的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。

如果不写任何配置项,env 等价于 latest,也等价于 es2015 + es2016 + es2017 三个相加(不包含 stage-x 中的插件)。

{
 "presets": [
   ["env", {
     "targets": {
       "browsers": ["last 2 versions", "safari >= 7"]
     }
   }]
 ]
}

如上配置将考虑所有浏览器的最新2个版本(safari大于等于7.0的版本)的特性,将必要的代码进行转换。而这些版本已有的功能就不进行转化了。

—— 摘自《一口(很长的)气了解 babel - 知乎 (zhihu.com)》

@babel/preset-typescript会处理所有ts的代码的语法和语义规则,并转换为js代码;@babel/preset-react

故名思义,可以帮助处理使用React相关特性,例如JSX标签语法等。

webpack的基于babel-loader的处理流程

讲了这么多,我们的打包工具webpack如何使用babel相关组件处理代码的呢?还记得我们安装过babel-loader吗?

实际上,我们通过配置webpack.config.js,使用babel-loader建立起webpack处理代码与babel处理代码的连接:

diff --git a/webpack.config.js b/webpack.config.js
index 8bfbb63..6767fd8 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -17,6 +17,11 @@ module.exports = {
   // 模块
   module: {
     // 规则
-    rules: []
+    rules: [
+      {
+        test: /\.tsx?$/,
+        use: 'babel-loader'
+      }
+    ]
   }
 };
(END)

这一步的配置,就是让webpack遇到ts或tsx的时候,将这些代码交给babel-loader,babel-loader作为桥接把代码交给内部引用的@babel/core相关API进行处理。

那么,@babel/core如何知道要使用我们安装的各种plugin插件和preset预置插件包的呢?通过.babelrc文件(注:实际上还有其他配置方式,但个人倾向于.babelrc)。这里,我们在项目根目录创建.babelrc文件,并添加一下内容:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-typescript",
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-object-rest-spread"
  ]
}

这里的配置不难理解,plugins字段存放要使用的插件,presets字段存放预置插件包名称,具体的配置可以查阅官方文档。

总结一下,配置babel可以按照如下思路进行:

  1. xxx.ts(x)代码交给webpack打包;

  2. webpack遇到ts(x)结尾的代码文件,根据webpack.config.js配置,交给babel-loader;

  3. babel-loader交给@babel/core;

  4. @babel/core根据.babelrc配置交给相关的插件处理代码,转为js代码;

  5. webpack进行后续的打包操作。

引入React相关库(externals方式)

还记得我们的需求吗?

依赖的react、react-dom模块以外部引用方式。

什么是外部引用方式?简单来讲,我希望react、react-dom等组件库的包,不会被打入到组件库中,而是在html中引入(Add React to a Website – React (reactjs.org)):

 <!-- ... other HTML ... -->
  <!-- Load React. -->
  <!-- Note: when deploying, replace "development.js" with "production.min.js". -->
  <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
  <!-- 组件库JS -->
  <script src="r-ui.js"></script>
</body>

要实现这样的效果,第一步是配置webapck.config.js:

diff --git a/webpack.config.js b/webpack.config.js
index 6767fd8..54fc0e5 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -13,7 +13,13 @@ module.exports = {
     // webpack 默认只处理js、jsx等js代码
     extensions: ['.js', '.jsx', '.ts', '.tsx']
   },
-  externals: {},
+  externals: {
+    // 打包过程遇到以下依赖导入,不会打包对应库代码,而是调用window上的React和ReactDOM
+    // import React from 'react'
+    // import ReactDOM from 'react-dom'
+    'react': 'React',
+    'react-dom': 'ReactDOM'
+  },
   // 模块
   module: {
     // 规则
(END)

第二部,在引入react相关库的时候,可以不用引入到dependencies运行时依赖,而只需要引入对应的类型定义到devDependencies开发依赖中:

yarn add -D @types/react@17.0.39 @types/react-dom@17.0.17
diff --git a/package.json b/package.json
index 33c32b6..bd17763 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,8 @@
     "@babel/preset-env": "^7.18.2",
     "@babel/preset-react": "^7.17.12",
     "@babel/preset-typescript": "^7.17.12",
+    "@types/react": "17.0.39",
+    "@types/react-dom": "17.0.17",
     "babel-loader": "^8.2.5",
     "webpack": "^5.72.1",
     "webpack-cli": "^4.9.2",

至此,我们已经完成了处理基于TypeScript的React项目的webpack配置,此时我们的项目结构如下:

- r-ui
  |- .babelrc
  |- package.json
  |- webpack.config.js

阶段演示1:基于TypeScript的React组件项目的webpack配置可行性

编写组件代码

新增src目录,在src目录下添加index.tsx(用于将所有的组件导出)

src目录下添加components/button目录,并创建index.tsx文件。具体结构与目录如下:

- r-ui
  |- src/components/button/index.tsx
  |- src/index.tsx
  |- ... ...

src/components/button/index.tsx

import * as React from 'react';
interface ButtonProps {
}
const Button: React.FC<ButtonProps> = (props) => {
    const {children, ...rest} = props;
    return <button {...rest} >{children}</button>
}
export default Button;

src/index.tsx

export {default as Button} from './components/button';

修改package.json

添加webpack处理脚本

diff --git a/package.json b/package.json
index bd17763..01565ad 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
   "description": "",
   "main": "index.js",
   "scripts": {
+    "build": "webpack --config webpack.config.js",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "author": "",
(END)

编译打包组件库

yarn run build

打包完成后,在项目根目录/dist目录下,会生成一个r-ui.umd.js文件。

效果演示

想要查看效果,可以在dist目录下添加如下的html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>r-ui example</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  	<!-- 注意r-ui.umd.js的路径 -->
    <script src="r-ui.umd.js"></script>
</head>
<body>
<div id="example"></div>
<script>
  const onClick = () => {
    alert('hello');
  };
  // window上存在rui,是因为我们将组件包导出为了umd包,取名为rui
  // 使用React原生方法创建Button的react组件实例
  // 等价于:
  // <Button onClick={onClick}>hello, world</Button>
  const button = React.createElement(rui.Button, {onClick}, 'hello, world');
  // 调用ReactDOM方法,将button组件实例挂载到example DOM节点上
  ReactDOM.render(button, document.getElementById('example'));
</script>
</body>
</html>
- r-ui
  |- dist
     |- index.html
     |- r-ui.umd.js
  |- ... ...

此时,可以直接使用浏览器打开index.html查看效果:

如何搭建react+ts组件库

处理样式(less编译与css导出)

依赖引入

根据上述内容,我们已经搭建了基础的项目结构,但是目前来说我们还需要处理我们的less样式,并且能够支持导出r-ui.umd.css样式文件。基于此考虑,我们需要引入:

  1. less-loader。处理less样式代码,转为css;

  2. less。由于less-loader内部是调用了less模块进行less代码编译,故还需要引入less(模式和babel-loader内部使用@babel/core一样);

  3. css-loader。处理css样式代码,进行适当加工;

  4. mini-css-extract-plugin。MiniCssExtractPlugin的loader用于进一步处理css,并且该插件用于导出独立样式文件。

yarn add -D less-loader less css-loader mini-css-extract-plugin
diff --git a/package.json b/package.json
index 01565ad..3070d07 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,10 @@
     "@types/react": "17.0.39",
     "@types/react-dom": "17.0.17",
     "babel-loader": "^8.2.5",
+    "css-loader": "^6.7.1",
+    "less": "^4.1.2",
+    "less-loader": "^11.0.0",
+    "mini-css-extract-plugin": "^2.6.0",
     "webpack": "^5.72.1",
     "webpack-cli": "^4.9.2",
     "webpack-dev-server": "^4.9.0"

配置webpack

根据上述依赖,我们可以知道需要less-loader、css-loader以及MiniCssExtractPlugin的内置loader来处理我们的样式代码。但是配置到webpack需要注意: webpack中的顺序是【从后向前】链式调用的,所以注意下面配置的代码中use数组的顺序:

diff --git a/webpack.config.js b/webpack.config.js
index 54fc0e5..9db43b8 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,5 +1,6 @@
 // webpack.config.js
 const {resolve} = require("path");
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 module.exports = {
   // 组件库的起点入口
   entry: './src/index.tsx',
@@ -27,7 +28,28 @@ module.exports = {
       {
         test: /\.tsx?$/,
         use: 'babel-loader'
+      },
+      {
+        test: /\.less$/,
+        use: [
+          // webpack中的顺序是【从后向前】链式调用的
+          // 所以对于less先交给less-loader处理,转为css
+          // 再交给css-loader
+          // 最后导出css(MiniCssExtractPlugin.loader)
+          // 所以注意loader的配置顺序
+          {
+            loader: MiniCssExtractPlugin.loader,
+          },
+          'css-loader',
+          'less-loader'
+        ]
       }
     ]
-  }
+  },
+  plugins: [
+    // 插件用于最终的导出独立的css的工作
+    new MiniCssExtractPlugin({
+      filename: 'r-ui.umd.css'
+    }),
+  ]
 };

阶段演示2:less样式处理配置可行性

编写样式代码

新增src/components/button/index.less

@color: #006fde;
.my-button {
  color: @color;
}

修改src/components/button/index.tsx

import * as React from 'react';
+// 引入less样式
+import './index.less';
 
 interface ButtonProps {
 }
 const Button: React.FC<ButtonProps> = (props) => {
     const {children, ...rest} = props;
-    return <button {...rest} >{children}</button>
+    // 使用my-button样式
+    return <button {...rest} className='my-button'>{children}</button>
 }
 export default Button;

编译组件库

再次打包组件库以后,dist目录下会额外生成文件:r-ui.umd.css。所以,我们需要在index.html中添加样式文件的引入:

<head>
     <meta charset="UTF-8">
     <title>r-ui example</title>
     <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
     <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
     <script src="r-ui.umd.js"></script>
+    <link href="r-ui.umd.css" rel="external nofollow"  rel="stylesheet"/>
 </head>

效果演示

刷新页面后,可以看到按钮的文字颜色已经生效

如何搭建react+ts组件库

引入AntDesign

根据我们的需求,我们希望将antd组件代码引用到我们组件内部进行封装,所以需要以dependencies方式引入:

yarn add antd
diff --git a/package.json b/package.json
index 3070d07..09ca792 100644
--- a/package.json
+++ b/package.json
@@ -26,5 +26,8 @@
     "webpack": "^5.72.1",
     "webpack-cli": "^4.9.2",
     "webpack-dev-server": "^4.9.0"
+  },
+  "dependencies": {
+    "antd": "^4.20.6"
   }
 }

引用antd的button样式

src/components/button/index.less

-@color: #006fde;
-
-.my-button {
-  color: @color;
-}
+@import "~antd/lib/button/style/index.css";

引用antd的button组件

 import * as React from 'react';
+// 使用antd的Button和ButtonProps
+// 为了不和我们的Button冲突,需要改导出名
+import {Button as AntdButton, ButtonProps as AntdButtonProps} from 'antd';
 // 引入less样式
 import './index.less';
-interface ButtonProps {
+interface ButtonProps extends AntdButtonProps {
 }
 const Button: React.FC<ButtonProps> = (props) => {
     const {children, ...rest} = props;
-    // 使用my-button样式
-    return <button {...rest} className='my-button'>{children}</button>
+    // 使用AntdButton
+    return <AntdButton {...rest}>{children}</AntdButton>
 }
 export default Button;

阶段演示3:antd组件引入可行性

通过上述的代码修改以后,我们直接进行编译,然后检查效果即可:

如何搭建react+ts组件库

“如何搭建react+ts组件库”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI