封装一个react组件发布到npm

为什么要将封装的组件发布到npm上?

  1. 在日常开发中,我们会基于项目对一些通用的组件进行封装,并在项目中进行调用。当一个组件被多个项目调用,需要升级维护时,就会出多次修改的情况,这种维护成本无疑是比较高的。所以,我们此时需要将组件发布到npm上,进行统一版本管理。项目中直接从npm上加载最新的包就可以了。
  2. 搭建一个自己或企业的私人组件仓库。

第一部分:封装一个react组件

1. 初始化一个package.json
yarn init -y
2. 安装相关插件包

package.json

  "devDependencies": {
    "@babel/core": "^7.10.4",
    "@hot-loader/react-dom": "^16.13.0",
    "@babel/preset-env": "^7.10.4",
    "@babel/preset-react": "^7.10.4",
    "autoprefixer": "^9.8.4",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.6.0",
    "html-webpack-plugin": "^4.3.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "style-loader": "^1.2.1",
    "terser-webpack-plugin": "^3.0.6",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^5.0.9",
    "webpack-node-externals": "^2.5.0"
  },
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  }
3. 准备文件夹目录

封装一个react组件发布到npm_第1张图片

4. 编写组件内容

4.1 src/components/change_button.js

import React, { useState, Component } from 'react';
import './change_button.css';

class ChangeButton extends Component{
    constructor(props){
      super(props);
      this.state={
        btnTxt:'Login'
      }
    }

    render(){
      const {btnTxt}=this.state;
      return(
        <div className='button-container' onClick={()=>{ this.setState({btnTxt:btnTxt==='Login'?'Logout':'Login'})}}>
          <span>{btnTxt}</span>
        </div>
      )
    }
}

export default ChangeButton;

注意:此处的组件内容不可以用hooks的形式去写,否则会编译报错,因为hooks只能在依赖于body,不能在独立的组件中使用。

4.2 src/components/change_button.css

.button-container{
  width: 100px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: aquamarine;
  border-radius: 5px;
}

.button-container:hover{
  cursor:pointer;
}

4.3 src/components/index.js

import ChangeButton from './change_button';

export default ChangeButton;

4.4 src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import ChangeButton from './components/change_button';

const App = () => {
  return (
    <div>
      <ChangeButton />
    </div>
  )
}

//要实现局部热更新,必须要添加此句
if (module.hot) {module.hot.accept()}

ReactDOM.render(<App />, document.getElementById('root'));

4.5 public/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React</title>
  </head>
  <body>
    <div id="root" class="root"></div>
  </body>
</html>
5. 编写webpack内容

5.1 webpack.base.config.js

const webpackConfigBase = {

  //module此处为loader区域,一般文件内容解析,处理放在此处,如babel,less,postcss转换等
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-react'],
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: (loader) => [
                require('autoprefixer')()
              ],
            }
          }
        ]
      }
    ]
  }
}
module.exports = webpackConfigBase

5.2 webpack.dev.config.js

const path = require('path');
const webpack = require('webpack');
const webpackConfigBase = require('./webpack.base.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { merge } = require('webpack-merge');

function resolve(relatedPath) {
  return path.join(__dirname, relatedPath)
}

const webpackConfigDev = {
  mode: 'development',

  entry: {
    app: [resolve('../src/index.js')],
  },

  output: {
    path: resolve('../lib'), 
    filename: 'change-button.js',
  },

  devtool: 'cheap-module-eval-source-map',   

  devServer: {
    contentBase: resolve('../lib'), 
    hot: true,
    open: true,   
    host: 'localhost',
    port: 8080,
  },

  plugins: [
    new HtmlWebpackPlugin({template: './public/index.html', }),
    new webpack.NamedModulesPlugin(),  
    new webpack.HotModuleReplacementPlugin()
  ]
}

module.exports = merge(webpackConfigBase, webpackConfigDev)

5.3 webpack.prod.config.js

const path = require('path');
const webpack = require('webpack');
const nodeExternals  = require('webpack-node-externals');
const webpackConfigBase = require('./webpack.base.config');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { merge } = require('webpack-merge');

function resolve(relatedPath) {
  return path.join(__dirname, relatedPath)
}

const webpackConfigProd = {
  mode: 'production',

  entry: {
    app: [resolve('../src/components/index.js')], 
  },

  output: {
    filename: 'change-button.js',
    path: resolve('../lib'), 
    libraryTarget:'commonjs2'
  },

  devtool: 'source-map',  //或使用'cheap-module-source-map'、'none'
  optimization: {
    minimizer: [
      // 压缩js代码
      new TerserJSPlugin({// 多进程压缩
        parallel: 4,// 开启多进程压缩
        terserOptions: {
          compress: {
            drop_console: true,   // 删除所有的 `console` 语句
          },
        },
      }),
      //压缩css代码
      new OptimizeCSSAssetsPlugin()
    ],
  },
  externals: [nodeExternals()],

  plugins:[
    new CleanWebpackPlugin() //每次执行都将清空一下./dist目录
  ]
}
module.exports = merge(webpackConfigBase, webpackConfigProd)

注意:
1.entry的入口文件位置,由开发环境的src/index.js改成了组件的出口src/components/index.js,表示此处只负责输出组件。
2.output的libraryTarget需要为commonjs2。
3.通过nodeExternals()将打包组件内的react等依赖给去除了,减小了包的体积,在引用该包时,只要其环境下有相关包,就可以正常使用。

5.4 在package.json中添加如下scripts,用来启动webpack

  "scripts": {
    "build": "webpack --config ./scripts/webpack.prod.config.js",
    "dev": "webpack-dev-server --config ./scripts/webpack.dev.config.js"
  },
6. 配置组件发布的相关信息

package.json

  "name": "@yangin/change-button",
  "version": "1.0.0",
  "main": "lib/change-button.js",
  "author":"yangin",
  "license": "MIT",

属性说明
name: 包名,当建自己的私有仓库时,建议在包名前加一个scoped,如@yangin/change-button,而不是change-button,因为 npm 包特别的多,很容易重复。这样这个包就会是私有的,可以通过 npm publish --access=public 将这个包变为共有的包。
version: 包的版本,每次发布包的版本不能和上次一样。详细规范可见这里
description:包的简介。
repository:适合写 Github 地址,建议写成::username/:repository。
license:认证。不知道该用什么的,就写MIT 吧。
main:包的入口文件。就是引入这个包的时候去加载的入口文件。
keywords:添加一些关键词更容易使你的包被搜索到。

更详细的package.json配置可见官网。

至此,完成了组件的相关配置

7. 调试并生成组件包文件

7.1 通过在根目录下运行

yarn dev

来启动项目,并对组件内的代码进行调试修改(因为已配置了热更新,所以可以实时看到修改效果)。

7.2 打包组件

yarn build

根据webpack的配置,在lib目录下会生成一个change-button.js文件,而此文件,正是我们的插件文件。
也是我们将要发布的文件。
封装一个react组件发布到npm_第2张图片
至此,我们完成了组件的开发与打包环节。

第二部分:调试验证

第1步:将组件映射到本地库

在组件项目的根目录下运行

yarn link

封装一个react组件发布到npm_第3张图片
运行后,在yarn的link文件夹下会有一个文件的快捷键映射
封装一个react组件发布到npm_第4张图片
如图,则表示映射成功。

第2步:新建一个项目pblog
create-react-app pblog
第3步:在pblog项目中引入@yangin/change-button组件,并调用
yarn link @yangin/change-button

则在pblog的node_modules文件夹下新增一个@yangin/change-button文件夹
调用封装一个react组件发布到npm_第5张图片

第4步:在app.js中调用,最终运行结果如下

封装一个react组件发布到npm_第6张图片
至此,则表示组件调试成功。

第三部分:将组件发布到npm

1.将源码发布到github上

1.1 在自己的github上新建一个respository,名称为change-button,并生成README.md

1.2 克隆仓库到本地,并将前面编写的项目文件内容拉到目录下。

git clone [仓库地址]

1.3 添加.gitignore文件
.gitignore

/node_modules
/yarn-err.log

1.4 提交项目至github。
封装一个react组件发布到npm_第7张图片
至此,完成将项目源码发布至github上。

2.将组件包发布到npm上

2.1 准备npm账号,npm官网地址:https://www.npmjs.com/

2.2 在组件的项目根目录下登录npm

npm login

按照提示输入username、password、email
登录后,可以通过npm whoami来查看登录用户信息

npm whoami

2.3 发布组件到npm上

npm publish --access=public

因为我们在给组件命名时,用的是@yangin/change-button,此种为私有库命名方式,但此处需要将组将公开给大家共用,所以需要添加–access=public。
封装一个react组件发布到npm_第8张图片
至此,完成了将组件发布至npm上。

2.4 验证
在新项目中通过引入@yangin/change-button组件,并调用来进行验证。

yarn add @yangin/change-button

编写README,可参考资料:https://blog.csdn.net/weixin_33721344/article/details/88679068

你可能感兴趣的:(React,react,npm,components)