【React npm】从零搭建react脚手架,发布组件库到npm,并实现按需加载(二)
【React npm】从零搭建react脚手架,发布组件库到npm,并实现按需加载(二)
前情回顾
前面写过一篇,发布单个组件到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/55644953
https://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.js
import 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.jsx
import 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.jsx
import 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"
]
]
}
项目还有很多可以优化的地方,后续继续优化。
更多推荐
所有评论(0)