本篇内容介绍了“如何搭建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模块以外部引用方式。
老牌而又经典的打包工具,广泛的使用,丰富的插件生态以及各种易得的样例。
由于 TypeScript 和 Babel 团队官方合作了一年的项目:TypeScript plugin for Babel(
@babel/preset-typescript
),TypeScript 的使用变得比以往任何时候都容易。 —— 摘自《TypeScript With Babel: A Beautiful Marriage (TypeScript 和 Babel:美丽的结合)》
建议各位读者可以先阅读一下上面的文章(有中文翻译文章)。
使用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-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 - 知乎 (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相关组件处理代码的呢?还记得我们安装过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可以按照如下思路进行:
xxx.ts(x)代码交给webpack打包;
webpack遇到ts(x)结尾的代码文件,根据webpack.config.js配置,交给babel-loader;
babel-loader交给@babel/core;
@babel/core根据.babelrc配置交给相关的插件处理代码,转为js代码;
webpack进行后续的打包操作。
还记得我们的需求吗?
依赖的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查看效果:
根据上述内容,我们已经搭建了基础的项目结构,但是目前来说我们还需要处理我们的less样式,并且能够支持导出r-ui.umd.css样式文件。基于此考虑,我们需要引入:
less-loader。处理less样式代码,转为css;
less。由于less-loader内部是调用了less模块进行less代码编译,故还需要引入less(模式和babel-loader内部使用@babel/core一样);
css-loader。处理css样式代码,进行适当加工;
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"
根据上述依赖,我们可以知道需要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>
刷新页面后,可以看到按钮的文字颜色已经生效
根据我们的需求,我们希望将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" } }
src/components/button/index.less
-@color: #006fde; - -.my-button { - color: @color; -} +@import "~antd/lib/button/style/index.css";
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;
通过上述的代码修改以后,我们直接进行编译,然后检查效果即可:
“如何搭建react+ts组件库”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。