【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)
青葱年少的头像青葱年少普通用户
上一篇 2023年2月23日 上午11:52
下一篇 2023年2月23日 上午11:56

相关推荐