React+webpack+ts实现组件库封装上传npm

1,699 阅读4分钟

在业务中多个项目需要用到公共组件库,所以决定封装组件库上传到npm私服管理,使用到的技术是react+webpack+ts+antd+less

讲在前面的话:这篇文章呢只是初稿的一些记录,代码只满足基本的功能,如果这篇文章对你有点作用的话,请在该基础上增加需要的内容,以及适当的优化,包括压缩、代码分割等功能。后面我会记录一些关于lerna管理多个包的相关文章,与大家分享

1.文件结构目录

|-- @lxy/demo
    |-- .babelrc                     //babel配置
    |-- .gitignore
    |-- README.md
    |-- package-lock.json
    |-- package.json
    |-- tsconfig.json                //ts编译配置
    |-- yarn.lock
    |-- config                       //webpack配置文件
    |   |-- webpack.dev.config.js
    |   |-- webpack.prod.config.js
    |-- example                      //测试demo,本地运行yarn start
    |   |-- app.js                   // start 的入口文件
    |   |-- index.html
    |   |-- TestDemo                 //组件的调试源码
    |-- lib                         //打包输出文件
    |-- src                         // 组件源码
        |-- index.tsx                // 组件入口
        |-- types
        |-- components
            |-- Tag
                |-- index.less
                |-- index.tsx

2.package.json

针对该文件重点说明一下以下重要字段:

  • name:上传到npm的包名

  • version:上传所需的版本号

  • main:打包之后输出的js路径

  • types:打包输出的d.ts路径

  • files:需要上传到npm的文件,此处lib是我打包输出的文件

  • peerDependencies:关于这个字段我就不做过多的叙述,可参考这篇文章

    {
      "name": "@tech/components",
      "version": "0.0.1",
      "author": "风控开发部",
      "description": "风控开发部组件",
      "main": "lib/bundle.js",
      "types": "lib/index.d.ts",
      "files": [
        "lib"
      ],
      "keywords": [],
      "license": "ISC",
      "scripts": {
        "start": "webpack-dev-server --config ./config/webpack.dev.config.js",
        "build": "webpack --config ./config/webpack.prod.config.js"
      },
      "dependencies": {
        "@ant-design/icons": "^4.6.3",
        "@ant-design/pro-form": "^1.42.0",
        "@ant-design/pro-layout": "^6.5.0",
        "@ant-design/pro-table": "^2.49.0"
      },
      "devDependencies": {
        "react": "^16.4.0",
        "react-dom": "^16.4.0",
        "antd": "^4.16.13",
        "webpack-dev-server": "^3.11.2",
        "html-webpack-plugin": "^4.5.1",
        "mini-css-extract-plugin": "^2.4.4",
        "webpack": "^5.18.0",
        "webpack-cli": "^3.3.12",
        "webpack-node-externals": "1.6.0",
        "@babel/core": "^7.2.2",
        "@babel/preset-env": "7.2.3",
        "@babel/preset-react": "7.0.0",
        "@types/react": "^17.0.0",
        "@types/react-dom": "^17.0.0",
        "babel-loader": "8.0.5",
        "ts-import-plugin": "^1.6.7",
        "css-loader": "0.28.9",
        "file-loader": "^6.2.0",
        "less": "^3.11.1",
        "less-loader": "^5.0.0",
        "style-loader": "0.19.1",
        "ts-loader": "~8.2.0",
        "typescript": "^4.4.4",
        "url-loader": "^4.1.1"
      },
      "peerDependencies": {
        "antd":"4.x",
        "react": ">=16.9.0",
        "react-dom": ">=16.9.0"
      }
    }
    

3. .babelrc文件配置

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

4.config文件配置

webpack.dev.config.js目的为了能够边预览边开发,及时响应效果

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const tsImportPluginFactory = require('ts-import-plugin')

module.exports = {
  mode: "development",
  entry: path.join(__dirname, "../example/app"), // 项目入口,处理资源文件的依赖关系
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "../lib"),
  },
  module: {
    rules: [
      {
        test: /\.(jsx|tsx|js|ts)$/,
        loader: 'ts-loader',
        options: {
          transpileOnly: true,
          getCustomTransformers: () => ({
            before: [tsImportPluginFactory(
              [{
                libraryName:'antd',
                style:true
            }])]
          }),
          compilerOptions: {
            module: 'es2015'
          }
        },
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "less-loader",
            options: {
              javascriptEnabled: true,
            },
          },
        ],
      },
      {
        test: /\.svg$/,
        use: ["file-loader"],
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: "index.html",
      template: path.join(__dirname, "../example/index.html"),
    }),
  ],
  devServer: {
    contentBase: path.join(__dirname, "../lib"),
    compress: true,
    port: 3002,
    open: true,
  },
  resolve: {
    //后缀名自动补全,引入时可不必写后缀名
    extensions: [".ts", ".tsx", ".js", ".jsx"],
  },
};

webpack.prod.config.js

const path = require("path");
const nodeExternals = require("webpack-node-externals");
const tsImportPluginFactory = require('ts-import-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  mode: "production",
  entry: path.join(__dirname, "../src/index.tsx"),
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "../lib"),
    library: "laputarednerer",
    libraryTarget: "umd",
  },
  module: {
    rules: [
      {
        test: /\.(jsx|tsx|js|ts)$/,
        loader: 'ts-loader',
        options: {
          getCustomTransformers: () => ({
            before: [tsImportPluginFactory(
              [{
                libraryName:'antd',
                style:true
            }])]
          }),
          compilerOptions: {
            module: 'es2015'
          }
        },
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "less-loader",
            options: {
              javascriptEnabled: true,
            },
          },
        ],
      },
      {
        test: /\.svg$/,
        use: ["url-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin(),
  ],
  resolve: {
    //后缀名自动补全,引入时可不必写后缀名
    extensions: [".ts", ".tsx", ".js", ".jsx", ".less", ".css"],
  },
  externals: ["react", "react-dom", "antd", nodeExternals()],
};

5.tsconfig.js

{
  "compilerOptions": {
    "outDir": "./lib",
    "target": "ES2015",
    "module": "CommonJS",
    "esModuleInterop": true,
    "declaration": true,
    "jsx": "react",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
  },
  "include": [
    "src/**/*",
  ],
  "exclude": [
    "node_modules"
  ],
}

6.src正文内容

src下新建index.ts

import Tag from "./components/Tag";
export { Tag };

src/components下新建Tag文件:内容根据自己的业务实现,这里就不写了

src/types下新增externals.d.ts,处理less文件未定义问题

declare module '*.less'
declare module '*.png';
declare module '*.svg' {
  export function ReactComponent(
    props: React.SVGProps<SVGSVGElement>,
  ): React.ReactElement;
  const url: string;
  export default url;
}

以上基本的配置完成,需注意

  • import styles1 from './index.less' 以该方式引入less,样式文件如下:

    .aa{
      color:red
    }
    
  •  import './index.less'以该方式引入less,样式文件如下:

    :global{
      .aa{
        color:red
      }
    }
    

7.example预览文件配置

example/index.js

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { Tag } from '../src/index';

const Test = () => {

  return (
    <div>
      <h1>本地测试组件</h1>
      <Tag />
    </div>
  );
};

ReactDOM.render(<Test />, document.getElementById("root"));

example/index.html

<html>
<head>
    <title>My Component Demo</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>

<body>
    <noscript>
        You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
</body>

</html>

以上基本的组件库配置几经完毕,如果过程中觉得认为可以优化的电可以自行优化,或者有不懂的细节处可以请一定要弄明白

打包执行yarn build 会发现出现一个lib文件,为了减少上传npm的次数,我们可以先在本地进行测试。打完包执行npm link,在需要的使用组件库的项目下执行npm link @lxy/dmeo

使用组件的方法如下:

import {Tag} form "@lxy/demo"
import "@lxy/demo/lib/style/main.css"

然后之后正常使用组件,观察组件即可

8.npm上传

  • 切换镜像,可以学习一下nrm管理镜像

    npm set registry 你的镜像地址

  • 先申请npm账号,控制台输入,依次填入相关信息

    npm adduser --registry 你的镜像地址

可以通过npm whoami查看当前登录的账户

  • 上传npm

    npm publish

上传的时候注意每一个版本号都不能和之前的相同否则上传不上去

  • 删除已经上传的包,一定要在当前的npm内网所在的镜像哟~

删除整个包

npm unpublish @lxy/demo -force

删除某个版本

npm unpublish @lxy/demo -force

8.项目中怎么安装依赖中存在公网以及内网的依赖

解决方案一:

npm设置全局处理:npm config set @lxy:registry 你的镜像地址

解决方案二:

文件项目根目录添加.npmrc : @lxy:registry 你的镜像地址

上面的意思是遇到以@lxy开始的依赖都去你的镜像地址安装

好啦,基本的东西都写完了,从0到1基本的坑都过了一遍,有什么问题欢迎留言指教讨论