webpack+react 搭建简易npm包

1,372

前言

大家好,我是Jade。这也是我第一次写文章,也是想记录一下这个过程,我相信还有很多小伙伴还没有接触过或者自己从0到1搭建过npm组件,所以我就想记录一下我的搭建过程。过程中还是有很多不清楚和不了解的地方,文章中有错误以及问题还望帮忙指出(手动抱拳)。

相信大家都用过npm吧,里面有各种各样的包。在工作当中,我们有很多代码都是重复的CV,我们能否自己写一个发布上去呢?答案当然是可以的,让我们用webpack及react来搭建一个简易版的~

说明:webpack比较灵活,可以配置自己想要的功能或者编写符合公司业务逻辑的组件库,也比较容易集成第三方库,如antd。如果你只想开发一个类似lodash这种工具包可以使用rollup打包工具。 偷偷告诉你,这也是个很重要的面试题哦~

起步

1、创建文件夹,并初始化

npm init or yarn init

这里会生成package.json文件,文件中会有一些配置项,在此处列举几个常见的

{
  "name": "@panyushan/study", // npm包名,此处有个坑 下文会指出
  "version": "1.0.0", // 版本号
  "main": "lib/index.js",  // 入口文件
  "license": "ISC",  //许可
  "description":"此包用于学习", //包的描述信息
  "author":"panyushan", // 作者
  "files": ["lib/*.js"],  // 项目包含的文件名数组
  "keywords": ["study","webpack","react"], // 关键词,用于搜索
  "repository": {  // 项目仓库地址
    "type" : "git",
    "url" : "https://github.com/panyushan2019/-panyushan-study"
  },
  "scripts": { // 执行脚本
    "start": "webpack serve --open --config ./config/webpack.dev.js",
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config ./config/webpack.prod.js"
  },
  "devDependencies": {
  },
  "dependencies": {
  }
}

2、创建src并在src下创建index.js

index.js
import React from "react";
import { Button } from 'antd'

const MyButton = () => {
    return <Button>测试button</Button>
}

export default MyButton;

此处使用的antd第三方组件库
npm install antd react react-dom webpack webpack-cli
or
yarn add antd react react-dom webpack webpack-cli 注意:webpack版本是v5+,react版本是v17+。不同版本的配置及用法有所不同,如有版本问题请参考官方网站

webpack配置

首先我们肯定要在本地调试我们的代码,确认无误才能打包发布。其次要把公共配置提取出来(使用webpack-merge进行合并),以便我们统一配置及修改,所以我们需要创建三个不同的配置文件。

webpack.dev.js  //开发环境
webpack.prod.js //生产环境
webpack.base.js //公共配置

1、安装babel、loader等插件

npm install @babel/cli @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader html-webpack-plugin style-loader webpack-dev-server less less-loader
or
yarn add @babel/cli @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader html-webpack-plugin style-loader webpack-dev-server less less-loader

在项目根目录下配置.babelrc文件

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

如有用到less、sass、ts等安装相应loader配置即可,请参考官方网站

2、webpack.base.js配置

const path = require("path");
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: "babel-loader",
        exclude: /node_modules/
      },
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/
      },
      {
        test: /\.less$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              modules: {
                mode: "local",
                localIdentName: "[local]__[hash:base64:5]",
              },
             }
          },
          {
            loader: "less-loader"
          }
        ]
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"]
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource"
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: "asset/resource"
      }
    ]
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "../src/")
    },
    extensions: [".js", ".jsx", ".ts", ".tsx",'css','less']
  }
};

3、webpack.dev.js

const path = require("path");
const { merge } = require("webpack-merge");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const base = require("./webpack.base.js");

module.exports = merge(base, {
  mode: "development",
  entry: {
    index: "./example/app.js"
  },
  devtool: "inline-source-map",
  devServer: {
    static: path.resolve(__dirname, "../example")
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Development",
      template: path.join(__dirname, "../example/index.html"),
      filename: "index.html"
    })
  ],
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "../example")
  }
});

4、webpack.prod.js

const path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.base.js");

module.exports = merge(common, {
  entry: {
    index: "./index.js"
  },
  mode: "production",
  externals: {
    react: 'react', //打包时候排除react
    ReactDOM:'react-dom',
    antd:'antd'
  },
  output: {
    filename: "[name].js",
    library: {
      name: "MyButton",
      type: "umd",
      export: 'default',
    },
    path: path.resolve(__dirname, "../lib"),
    clean: true
  }
});

相关使用语法可参考官方网站此处不做过多叙述(本文主要讲如何搭建,而不是api的使用)

注意:开发环境和生产环境打包的出入口不一样也就是entry和output,请注意区分

开发环境

1、在根目录创建example文件夹并创建index.html和app.js并将自己的组件以样式导入并挂载

import React from "react";
import ReactDOM from "react-dom";
import MyButton from '../index.js';

function App() {
    return <MyButton />;
}

ReactDOM.render(<App />, document.getElementById("app"));

2、创建在package.json中创建script脚本(每次在终端输入--config --open等命令不累吗)

"scripts": {
    "start": "webpack serve --open --config ./config/webpack.dev.js"
  },

3、yarn start
至此我们开发环境就ok了,你可以调试自己的代码,并封装出可复用的高质量组件

生产环境

在package.json中创建生产环境script脚本

"build": "webpack --config ./config/webpack.prod.js"

你以为这就完了?

首先你不可预测你的此生产包是否通过npm install 能够正常使用,万一直接提交发布,用户一用发现有bug那岂不是啪啪打脸~

生产环境测试

1、jest编写测试脚本(此处不做过多叙述,请自行学习了解)
2、使用npm link 或 yarn link

  • 首先在自己组件项目目录下的终端输入 npm link(可以帮助我们模拟包安装后的状态,它会在系统中做一个快捷方式映射,让本地的包就好像 install 过一样,可以直接使用)
  • 然后在另一个空项目根目录终端中(可以使用脚手架来创建)使用npm link [package-name]yarn link [package-name]
  • 上面的[package-name]就是package.json中的name,也就是包的名称
  • 最后在测试项目中(通过脚手架创建的项目)运行start 这样子我们的组件库才算完整的开发完成

发布到npm

这里对于初次接触的小伙伴会有一些坑等着你~

  • 首先你要有npm账号,如果没有去官方网站注册一个
  • 在终端输入npm login,然后输入账号密码以及邮箱,可以使用npm whoami来查看是否处于登录
  • 在终端执行npm publish,大功告成~

相信你一定有以下问题,来让我们看一下:
1、在测试项目start运行报错

Error : Invalid hook call . HoOkS can only be called inside of the bOdy ofa function cOmponent . This COuld happen for one of the following reasons :
1. You might have mismatching versions of React and the renderer ( such as React DOM )2. You might be breaking the Rules of HoOKS 
3. You might have more than one copy of React in the same app 
 See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem .

根据react官方给的提示是 存在多个不同版本的react,这是因为link的时候A库调用B,B使用的是npm link调试,会导致react的依赖出现两次,所以需要在B中link到A下面的react,重启项目。说白了就是将组件库的react link到调试库。还有个简单暴力的方法,直接将组件库node_modules中的react库删除即可

2、npm publish报错(可能会有以下情况,请自行根据错误信息排查)

  • 没有使用 npm login 登录
  • 没有切换镜像源(如果你的是淘宝镜像源,需要切换回来)
  • 已有重复的包名(修改package.json里的name或者使用scope)
如果你使用@[package-name]/[package-name] 作为包名
则你需要使用 npm publish --access=public来进行发布
  • 没有验证npm邮箱(我碰到了这个问题,因为npm注册时会发送邮件让你验证,我当时没有验证,回头再找的时候已经失效了,登录上npm重新验证一下就行)
  • 无权限,使用npm adduser添加就行了 暂时想到这些,后续有再补充~

总结

至此一个简单的npm组件就搭建完成了,建议小伙伴们手动搭建一次,收获其实很多的。一开始我也是一头雾水通过查文档,一遍又一遍的创建、编译、调试、删除。过程挺繁琐枯燥的,还是要坚持下去,多看文档。 而且我也是第一次写文章,排版、布局反反复复改,对我来说简直双重打击,如果有做的不好的地方,还请各位见谅~(手动抱拳)