介绍

如何写一个react组件,发布到npm,让大家伙儿都能用你写的组件呢?

自从萌生了这个念头儿,我就开始查各种资料,奈何资料搜了一堆,

都是比较零碎的,或者不符合我自身的需求,就只能自己耐着性子

去从头开始摸索。

我的需求是:

  • 必须是react组件
  • 必须是自己搭建的脚手架,因为react原生脚手架太大,又不方便
    自定义
  • 必须是最新的webpack5打包,因为webpack最流行
  • 必须发布到npm能使用,体积还不能太大

问题

这个过程中,遇到不少问题:

  • 需要哪些技术?
  • react脚手架怎么搭建?
  • package.json怎么配置?
  • npm怎么发包?
  • 组件我是用react写的,打包时还不能把react打包进来,要怎么处理?
  • 发布之后,组件使用一堆报错,是什么原因,怎么处理?

这中间确实经常懵逼,毫无头绪。

于是,就硬着头皮,从搭建脚手架开始,复习webpack5的配置,

找资料,看文档,搜视频。边学边练。最终,搞懂了整个流程,

发布了自己第一个比较满意的npm包。

下面就开始说明整个发布流程。


准备

先解决第一个问题,需要哪些技术?

对于最基础的组件发布来说,一般需要这些:

  • react相关知识
  • package.json配置项的理解
  • webpack5各种配置项要非常了解
  • babel系列配置,比如预设,核心模块,插件等
  • eslint

配置.gitignore

在项目根目录下创建文件.gitignore,然后写一些忽略项:

node_modules
dist
lib

这一步是为下面安装包做准备,如果已经有了该文件,可忽略这一步。


搭建react脚手架

先初始化生成package.json文件:

npm init -y

然后安装一些包:

// react react-dom在写react组件时需要引用
npm i react react-dom -D

// webpack webpack-cli做webpack配置时使用
npm i webpack webpack-cli -D

// 安装开发服务器,在开发模式下启动本地服务
npm i webpack-dev-server -D

// babel-loader用于转译js文件 
// @babel-core是babel的核心库,必须安装
// @babel-preset-env是一个只能预设,用于编译js高级语法
npm i babel-loader @babel-core @babel/preset-env -D

// 为了编译react中的语法
npm i @babel/preset-react -D

// 开发模式下,我们需要在html模板里查看组件样式
// 它会把html文件打包,自动引入css样式和js代码
npm i html-webpack-plugin -D

// 生产模式下,需要把css文件独立打包
npm i mini-css-extract-plugin -D

// 压缩css代码
npm i css-minimizer-webpack-plugin -D

// style-loader会把css样式插入到html中
// css-loader会把import引入的css文件编译成css代码
npm i style-loader css-loader -D

开始搭建脚手架,为了方便理解,这里把开发模式和生产模式分开配置:

开发模式webpack.dev.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      // 处理js和jsx
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      // 开发模式下打包html的时候,以index.html为模板
      template: path.resolve(__dirname, 'public/index.html')
    })
  ],
  // 配置开发模式的source-map,便于我们调试代码,定位bug
  devtool: 'cheap-module-source-map',
  // 配置开发服务器
  devServer: {
    port: 3001,
    open: true
  },
  resolve: {
    // 自动补充扩展名,引入的时候就可以省略文件后缀
    // 支持.js .jsx .json文件引入时隐藏后缀
    extensions: ['.js', '.jsx', '.json']
  },
  // externals用来定义哪些通过import引入的包不要被打包到boundle中
  // 也就是说,我们在写组件时引入的react和react-dom,不要打包
  externals: {
    react: {
      root: 'React',
      commonjs2: 'react',
      commonjs: 'react',
      amd: 'react',
    },
    'react-dom': {
      root: 'ReactDOM',
      commonjs2: 'react-dom',
      commonjs: 'react-dom',
      amd: 'react-dom',
    }
  }
}

生产模式webpack.prod.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizaerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    // 打包到lib目录下
    path: path.resolve(__dirname, 'lib'),
    filename: 'index.js',
    // 每次打包时,都自动清除原有的打包文件
    clean: true,
    // 发布到npm库的相关信息
    // name是发布到npm时的库名,别人安装就是安装它
    // type是暴露库的形式,umd就表示别人可以在所有模块定义下引入这个库
    // 比如CommonJs AMD 和全局变量的形式
    // export用来指定哪一个导出应该被暴露为一个库
    // 'default'就是我们默认导出的库
    library: {
      name: 'publish-react-test01',
      type: 'umd',
      export: 'default'
    },
    
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      },
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      // 因为我们在文件中引入就是publicTest.css
      // 所以在打包后,也用这个名字,以免引入失败
      filename: 'publicTest.css'
    })
  ],
  resolve: {
    // 支持.js .jsx .json文件引入时隐藏后缀
    extensions: ['.js', '.jsx', '.json']
  },
  // 压缩和优化的相关配置都写在optimization里
  optimization: {
    minimizer: [
      new CssMinimizaerPlugin()
    ]
  },
  externals: {
    react: {
      root: 'React',
      commonjs2: 'react',
      commonjs: 'react',
      amd: 'react',
    },
    'react-dom': {
      root: 'ReactDOM',
      commonjs2: 'react-dom',
      commonjs: 'react-dom',
      amd: 'react-dom',
    }
  }
}

配置babel

在上一步中,已经配置了babel-loader了,但是还需要给js语法做兼容,

也就是使用@babel/preset-env

而且,我们还需要去编译jsx语法,这里需要用到@babel/preset-react

二者在上一步中已经安装过了,这里就直接配置。

在根目录下创建文件.babelrc.js,并写入配置:

module.exports = {
  presets: [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

配置package.json

这里配置和发布相关的参数。

参考文档:

https://juejin.cn/post/7145001740696289317

下面做些简单配置:

{
  // 这里默认的是我们项目的名称
  // 发布前,这里最好和我们在webpack中配置的库名保持一致
  "name": "publish-react-test01",
  // 发布时改下版本号,只能升,不能降,也不能重复
  // 不然会报错403
  "version": "0.0.9",
  "description": "练习发布react组件到npm",
  // 模块的出口文件
  "main": "lib/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    // 开发模式下启动 npm start
    "start": "webpack serve --config webpack.dev.js",
    // 生产模式下启动,打包项目 npm run build
    "build": "webpack --config webpack.prod.js",
    // 在已经登陆npm后,执行npm run pub就会直接打包并发布到npm
    "pub": "npm run build && npm publish"
  },
  "repository": {
    "type": "git",
    "url": "https://gitee.com/guozia007/pulish-react-test01.git"
  },
  "keywords": [
    "react",
    "react component",
    "practive"
  ],
  // 我们只把打包后的lib目录发到npm
  "files": [
    "lib"
  ],
  "author": "guozi007a",
  "license": "MIT",
  // 这里可以看到,我们所有的包都是安装到开发模式下的
  "devDependencies": {
    "@babel/core": "^7.20.12",
    "@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",
    "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"
  },
  // 用户要使用我们的库,就必须保证自己已经安装了
  // react和react-dom的16.9.0以上版本
  "peerDependencies": {
    "react": ">=16.9.0",
    "react-dom": ">=16.9.0"
  },
  // 浏览器兼容范围
  "browserslist": [
    "> 0.25%",
    "last 2 versions",
    "not dead"
  ]
}


配置.npmignore

这个文件是告诉npm,哪些文件不要发布到npm。

node_modules
.DS_Store
*.log

因为我们在package.json中已经配置了files参数,所以这里就随便写下

就可以了。


开发组件

配置基本完成,现在我们开始写组件吧。

写这样一个例子,初始状态下这个大号字颜色是黑色,
在这里插入图片描述
点击按钮后,字体颜色变成粉色,或者用户传入的颜色值。


首先,在根目录下创建src目录,

src下创建三个文件:

src
 - index.js // 入口文件
 - PublicTest.jsx // 我们的组件
 - publicTest.css // 组件样式

代码如下:

// index.js

import React from "react";
import { createRoot } from 'react-dom/client';
import PublicTest from './PublicTest';

const root = createRoot(document.getElementById("root"));
root.render(
  <PlulicTest />
);
// PublicTest.jsx

import React, { useState } from "react";
import './publicTest.css';

const PublicTest = (props) => {

  const { color } = props;
  const [hcolor, setHcolor] = useState('#000');

  return <div className="public-test">
    <h1 style={{color: `${hcolor}`}}>hello react!</h1>
    <button className="btn"
      onClick={() => {setHcolor(color || 'pink')}}
    >click me</button>
  </div>
}

export default PublicTest;
/* publicTest.css */

.public-test .btn {
  width: 80px;
  height: 30px;
  cursor: pointer;
}

启动项目,npm start,可以看到我们预期的效果:
在这里插入图片描述
点击按钮后:
在这里插入图片描述
很棒,组件完成!


准备发布

发布之前我们还需做些准备工作:

  • 改造index.js代码。毕竟我们是要让别人用,就要把组件导出去,
    而不是在index中自己加入html中使用。
  • 注册npm账号
  • 熟悉npm发布流程

改造index.js代码如下:

import PublicTest from './PublicTest';

export default PublicTest;

然后到https://www.npmjs.com/中注册账号。

然后来到项目终端:

// 检查npm当前使用的镜像是否为原始镜像
npm config get registry

// 如果不是,就切换npm源到原始镜像 否则报错E404
npm config set registry https://registry.npmjs.org/

// 登陆 如果曾经登陆过,就不需要登陆了
// 然后会要求输入username password email等
npm login

// 检查package.json的name和库名是否一致,版本号是否需要更换

以上都没问题了,就开始编写README.md文档,

该文档会自动跟着发布到npm库,目的是介绍自己的库,

告诉用户,他该如何使用这个库。

// 安装
npm i publish-react-test01

// 引入
import PublicTest from 'publish-react-test01';

// 使用示例1 无传参
const Demo = () => {
  return <PublicTest />
}

// 使用示例2 有传参
const Demo = () => {
  return <PublicTest color="deeppink" />
}

发布和检验

以上准备工作做好之后,开始发布:

npm run pub

发布后,可以到自己的npm库里面查看。比如:

https://www.npmjs.com/package/publish-react-test01

启动一个react项目,来测试一下,发布的包是否可用。

按照我们写的文档来操作:

安装:

npm i publish-react-test01

引入:

import PublicTest from 'publish-react-test01';

在组件中使用:

import React from 'react';
import PublicTest from 'publish-react-test01';

const App = () => {

  return <PublicTest />
}
export default App;

npm start启动项目,可以在浏览器中看到效果:
在这里插入图片描述
点击按钮:
在这里插入图片描述

完美!


思考和改进

虽然完成了,但毕竟还只是初级,还有很多地方是可以改进的,

比如:

  • 在该库中,是否可以一次发布多个组件?
  • 库中如何添加标准化操作?如eslint, husky, prettier等
  • 是否可以先检验,后发布?比如使用storybook
  • 更新库应该如何做,是否可以实现自动化?
  • 打包体积仍然有几kb,是否还可以减小包体积?

总之,后续将继续学习和改进。


Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐