发布react组件库
- 前情回顾
- 介绍
- 搭建脚手架
- 配置babelrc
- 配置jsconfig
- 写入组件demo
- 修改主入口文件
- 配置生产环境webpack
- 配置package.json
- 发布
- 实现按需加载
前情回顾
前面写过一篇,发布单个组件到npm的:
https://blog.csdn.net/tuzi007a/article/details/129116273
实现了搭建react脚手架,并发布一个组件到npm。
情形较为简单。
介绍
本次实现发布一个包含多个组件的组件库,并实现组件和样式的按需加载。
实现目标:
- 发布多个组件
- 每个组件的内容和样式,单独存放在一个文件夹
- 每个组件单独打包成一个文件夹,同样包含内容和样式
- 用户可以通过
import { xxx } from 'ui库'
的形式引入组件 - 用户可以全局引入样式,也可以不引入样式,实现样式按需加载
npm包demo地址:
https://www.npmjs.com/package/pub-multily-react-test03
项目gitee地址:
https://gitee.com/guozia007/pub-multily-react-test03
搭建脚手架
在gitee
上创建示例项目pub-multily-react-test03
,并clone到本地,
在根目录下生成package.json
文件:
npm init -y
在根目录下创建.gitignore
文件
node_modules
dist
lib
开始安装一堆包:
// react18+相关
npm i react react-dom -D// webpack5+相关
npm i webpack webpack-cli webpack-dev-server -D// babel相关
npm i @babel/core babel-loader @babel/preset-env @babel/preset-react -D// 基础脚手架需要的其他loader和plugin
npm i style-loader css-loader -D
npm i html-webpack-plugin -D
npm i mini-css-extract-plugin -D
npm i css-minimizer-webpack-plugin -D
在根目录下创建webpack配置文件,
开发环境:webpack.dev.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {mode: "development",entry: './src/index.js',module: {rules: [{test: /\.css$/,use: ['style-loader','css-loader']},{test: /\.jsx?$/,exclude: /node_modules/,loader: 'babel-loader'}]},plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname, 'public/index.html')})],devtool: 'cheap-module-source-map',devServer: {port: 3001,open: true},resolve: {extensions: ['.js', '.jsx', '.json']}
}
开发环境的脚手架配置基本完成。
配置babelrc
在根目录下创建.babelrc.js
,如下配置:
module.exports = {presets: ["@babel/preset-env", // js环境预设"@babel/preset-react" // 解析react相关语法的预设,比如jsx className等]
}
配置jsconfig
在根目录下创建jsconfig.json
,
两篇和配置相关的文档:
https://zhuanlan.zhihu.com/p/55644953https://juejin.cn/post/7004748084374831117
配置内容:
{"compilerOptions": {"outDir": "./lib/", // 这里是实现import { xxx } from 'xxx'的关键"module": "ESNext", "target": "ES5","moduleResolution": "node","esModuleInterop": true,"jsx": "react","allowJs": true,"allowSyntheticDefaultImports":true},"exclude": ["node_modules", "lib"]
}
写入组件demo
在根目录下创建src
内容树结构如下:
+ src
+ index.js
+ MyButton
+ index.jsx
+ index.css
+ ShowImg
+ index.jsx
+ index.css
然后可看到项目结构如下:
内容如下:
// src/index.jsimport React from "react";
import { createRoot } from 'react-dom/client';
import ShowImg from "./showImg";
import MyButton from "./button";const root = createRoot(document.getElementById("root"));
root.render(// 要测试哪个组件,就在这里写哪个组件<MyButton bgc="pink" label="click me !" />
)
// src/MyButton/index.jsximport React from "react";
import './index.css';const MyButton = ({ label, bgc }) => {return (<div className="my-btn"style={{ backgroundColor: bgc || '#1EA7FD' }}>{ label || 'Button' }</div>)
}export default MyButton;
/* src/MyButton/index.css */.my-btn {min-width: 80px;display: inline-block;box-sizing: border-box;padding: 12px;border-radius: 4px;color: #fff;font-weight: 600;letter-spacing: 2px;user-select: none;cursor: pointer;
}
// src/ShowImg/index.jsximport React from "react";
import './index.css';const ShowImg = ({ url }) => {return (<div className="show-img"><p className="show-img-tip">以下是要展示的图片:</p><img src={url || ''} alt="" /></div>)
}export default ShowImg;
/* src/ShowImg/index.css */.show-img {width: 200px;
}.show-img .show-img-tip {margin: 20px auto;
}.show-img img {max-width: 200px;max-height: 200px;
}
保存文件。
然后在package.json
中添加开发环境的指令:
"scripts": {// ..."start": "webpack serve --config webpack.dev.js"
}
执行npm start
,可以在页面中看到测试效果。
这块比较简单,不具体说了,重点放在生产环境的处理。
修改主入口文件
主入口文件是src/index.js
,
在生产模式中,需要用它把开发的组件都批量导出,方便用户引入使用,如下:
// 批量导出export { default as MyButton } from './MyButton';
export { default as ShowImg } from './ShowImg';
配置生产环境webpack
要实现组件分开打包,就要使用多入口打包,这里需要3个入口:
- index: ‘./src/index.js’
- ‘MyButton/index’: ‘./src/MyButton/index.js’,
- ‘ShowImg/index’: ‘./src/ShowImg/index.js’,
如果还有其他组件,也是这种格式的入口,
所以这里的关键就是怎么动态的获取入口。
glob
就是用来做这个事情的。
它会生成一个数组,用来存储入口地址。
fileNames: ['./src/index.js','./src/MyButton/index.jsx','./src/ShowImg/index.jsx'
]
我们通过正则匹配的方式,用地址动态生成入口名字即可。
安装glob
:
npm i glob -D
使用glob
:
const glob = require('glob');/*** glob匹配规则* https://blog.csdn.net/feiying0canglang/article/details/125043362*/
// 创建入口对象
const entries = {};
// 通过glob获取到入口地址数组
const fileNames = glob.sync('./src/**/*.js?(x)');
console.log('fileNames: ', fileNames);
// 遍历入口地址,去掉前后内容,留下中间部分,作为入口名称
fileNames.forEach(file => {const filePath = file.replace(/^\.\/src\/(.+)\.jsx?$/, '$1');entries[filePath] = file;
})
console.log('entries: ', entries);
//entries: {
// index: './src/index.js',
// 'MyButton/index': './src/MyButton/index.jsx',
// 'ShowImg/index': './src/ShowImg/index.jsx'
//}
剩下内容就可以按部就班的配置了。
在根目录下创建webpack.prod.js
,
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const glob = require('glob');/*** glob匹配规则* https://blog.csdn.net/feiying0canglang/article/details/125043362*/
const entries = {};
const fileNames = glob.sync('./src/**/*.js?(x)');
// console.log('fileNames: ', fileNames);
fileNames.forEach(file => {const filePath = file.replace(/^\.\/src\/(.+)\.jsx?$/, '$1');entries[filePath] = file;
})
// console.log('entries: ', entries);module.exports = {mode: 'production',entry: entries,output: {path: path.resolve(__dirname, 'lib'),filename: '[name].js',library: {name: 'pub-multily-react-test03',type: 'umd'},clean: true},module: {rules: [{test: /\.css$/,use: [MiniCssExtractPlugin.loader,'css-loader']},{test: /\.jsx?$/,exclude: /node_modules/,loader: 'babel-loader'}]},plugins: [new MiniCssExtractPlugin({// 如果想自定义生成的css文件的filename,可以这样配置// filename: (filePath) => {// // console.log('filePath: ', filePath);// return `${filePath.chunk.name.replace('/', '/style/')}.css`;// }filename: '[name].css'})],optimization: {// 代码分隔splitChunks: {chunks: 'all',name: 'chunk'},minimizer: [new CssMinimizerPlugin() // 压缩css代码]},resolve: {// 支持.js .jsx .json自动补全,不要忘了.extensions: ['.js', '.jsx', '.json']},// 外部扩展,不需要安装的依赖externals: {react: {root: 'React',commonjs2: 'react',commonjs: 'react',amd: 'react',},'react-dom': {root: 'ReactDOM',commonjs2: 'react-dom',commonjs: 'react-dom',amd: 'react-dom',}}
}
在package.json
中添加打包指令:
"script": {"build": "webpack --config webpack.prod.js"
}
执行命令npm run build
可以看到打包结果:
配置package.json
{"name": "pub-multily-react-test03","version": "0.0.7", // 版本号,发布前需要修改"description": "发布react多组件库","main": "lib/index.js", // 库的入口文件"scripts": {"test": "echo \"Error: no test specified\" && exit 1","start": "webpack serve --config webpack.dev.js", // 开发环境启动项目"build": "webpack --config webpack.prod.js", // 生产环境打包项目"pub": "npm run build && npm publish" // 打包并发布项目到npm},"repository": { // 项目地址"type": "git","url": "https://gitee.com/guozia007/pub-multily-react-test03.git"},"keywords": [ // 关键字,告诉用户这个库是干嘛的,也利于用户搜索"react","react component","ui","framework","component"],"author": "guozi007a", // 作者"license": "MIT", // 证书格式"publishConfig": { // 发布到npm时,使用的npm源。配置之后,就不需要在发布时修改自己用的npm镜像了"registry": "https://registry.npmjs.org/" // npm原始镜像},"browserslist": [ // 支持的浏览器,做兼容用的">= 0.25%","last 1 version","not dead"],"files": [ // 要把哪些文件或目录发到npm"lib"],"peerDependencies": { // 用户要安装的依赖,如果没有,会给用户警告"react": ">= 16.9.0", // react版本不低于16.9.0"react-dom": ">= 16.9.0" // react-dom版本不低于16.9.0},"devDependencies": { // 开发环境依赖,开发组件,一般都是安装到开发环境"@babel/core": "^7.21.0","@babel/preset-env": "^7.20.2","@babel/preset-react": "^7.18.6","babel-loader": "^9.1.2","css-loader": "^6.7.3","css-minimizer-webpack-plugin": "^4.2.2","glob": "^8.1.0","html-webpack-plugin": "^5.5.0","mini-css-extract-plugin": "^2.7.2","react": "^18.2.0","react-dom": "^18.2.0","style-loader": "^3.3.1","webpack": "^5.75.0","webpack-cli": "^5.0.1","webpack-dev-server": "^4.11.1"}
}
发布
不重复说明,直接npm run pub
发布即可。
实现按需加载
打包后,可以看到在lib下多了一个index.css
,这个是全局样式,
所有组件的样式都在这里面。
用户在使用组件的时候,就有两种方式,
第一种是使用全局样式:
import { MyButton } from 'pub-multily-react-test03';
import 'pub-multily-react-test03/lib/index.css';
第二种是使用哪个组件,就单独引入哪个样式:
import { MyButton } from 'pub-multily-react-test03';
import 'pub-multily-react-test03/lib/MyButton/index.css';
两种方式均可,但是对用户并不友好。
我们需要实现样式的按需加载,即用户不需要再引入样式,而是
根据用户使用的组件,来自动实现样式的引入。
这就需要用户(组件库的使用者)去安装和配置插件babel-plugin-import
文档:
https://github.com/umijs/babel-plugin-import
安装:
npm i babel-plugin-import
如果用户的项目的package.json
中有babel
配置项,请先把配置项移植到babel的配置文件中。
项目根目录下创建babel配置文件.babelrc.js
,做如下配置:
module.exports = {"presets": [// ... 用户原有的preset],"plugins": [// ... 用户原有的plugin// 如下是babel-plugin-import插件的配置["import",{// 要实现按需加载的库名"libraryName": "pub-multily-react-test03",// 库的目录,默认是lib可自行更改"libraryDirectory": "lib",// 是否要把组件的目录名改成小写形式,即my-button,默认为true"camel2DashComponentName": false,// "style"是单个样式所在的相对路径,按需加载样式时会按照"style"的路径去找css样式文件// name是组件的目录名,如MyButton// "style": true,意思是路径为MyButton/style// "style": "css",意思是路径为MyButton/style/css// 还可以自定义如下,意思是要加载的样式文件是MyButton/index.css"style": (name) => `${name}/index.css`},// 如果你的@babel版本低于7,这句配置不用写"pub-multily-react-test03"]]
}
项目还有很多可以优化的地方,后续继续优化。