您好,登錄后才能下訂單哦!
本篇內容介紹了“如何搭建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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。