Appearance

封装一个自己的组件库

coderzhouyu2023/11/18

背景

平时开发会积累很多业务组件,但是在不同的项目中,我们需要重新复制粘贴这些组件,这样会导致组件的维护成本变得很高,所以我们需要将这些组件进行封装,然后发布到 npm 上,这样就可以在不同的项目中使用了。

技术栈

  • vue3
  • vite
  • ant-design-vue
  • vueuse

开发组件库

项目初始化

npm create vue@latest

安装依赖

因为真正发布的时候,我们不需要将 ant-design-vue 和 vueuse 一起发布,所以我们将这两个依赖安装到 devDependencies 中。

npm install vue ant-design-vue @vueuse/core --save-dev

配置组件库导出

组件库中单个组件导出

组件库中的组件一般都是单个导出,所以我们需要将组件库中的组件进行单个导出,这样才能在项目中按需引入组件。这个需要在每个组件目录下创建 index.js 文件,然后在 index.js 文件中导出组件

// src/components/button/index.js
import Button from "./src/button.vue";

Button.install = (app) => {
  // 如果用setup语法糖 Button.name 可以写对应的字符串
  app.component(Button.name, Button);
};

export default Button;

组件库中所有组件导出

组件库的统一导出可以配置一个src/bundle.js文件,然后在src/bundle.js文件中导出所有的组件。

// 打包 npm 包用
import "./style.scss";

import SjBusinessTable from "@/components/SjBusinessTable";

const components = {
  SjBusinessTable,
};

const install = function (app) {
  // 注册自定义组件到全局
  for (const key in components) {
    app.component(key, components[key]);
  }
};

export default {
  install,
};

export { SjBusinessTable };

配置组件库打包

通常组件库的打包有两种方式,一种是打包成 esm 模块,一种是打包成 umd 模块。

打包成 esm 模块

配置一个单独的vite.es.config.js文件

// 打包 npm 包
import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";

export default defineConfig({
  plugins: [vue(), vueJsx()],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
  //public 目录下的文件不会被打包
  publicDir: "empty",
  build: {
    // 打包后的文件夹
    outDir: "dist/es",
    lib: {
      // 入口文件
      entry: fileURLToPath(new URL("./src/bundle.js", import.meta.url)),
      // 项目名
      name: "SujuPlus",
      // 打包后的文件名
      fileName: "suju-plus",
      // 打包格式
      formats: ["es"],
    },
    rollupOptions: {
      // 不需要打包的模块
      external: ["vue"],
    },
  },
});

配置 package.json

{
  // 指定打包格式为 esm
  "type": "module",
  // 指定引用的入口文件
  "module": "dist/es/suju-plus.js",
  // 指定引用的别名
  "exports": {
    ".": {
      "import": "./dist/es/suju-plus.js"
    },
    "./style.css": {
      "import": "./dist/style.css"
    }
  },
  // 只发布 dist 目录下的文件到 npm
  "files": ["dist"],
  "scripts": {
    "build:es": "vite build --config vite.es.config.js"
  }
}

打包成 umd 模块

配置一个单独的vite.umd.config.js文件

// 打包 umd 包
import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";

export default defineConfig({
  plugins: [vue(), vueJsx()],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
  publicDir: "empty",
  build: {
    outDir: "dist/umd",
    lib: {
      entry: fileURLToPath(new URL("./src/bundle.js", import.meta.url)),
      name: "SujuPlus",
      fileName: "suju-plus",
      formats: ["umd"],
    },
    rollupOptions: {
      // 不需要打包的模块
      external: ["vue"],
      output: {
        exports: "named",
        globals: {
          vue: "Vue",
        },
      },
    },
  },
});

配置 package.json

{
  "main": "dist/umd/suju-plus.umd.cjs",
  "exports": {
    ".": {
      "require": "./dist/umd/suju-plus.umd.cjs"
    },
    "./style.css": {
      "require": "./dist/umd/style.css"
    }
  }
}

一个完整的 package.json 实例

{
  "name": "suju-plus",
  "version": "0.0.2",
  "description": "A Vue.js 3.0 UI Toolkit for Web",
  "license": "MIT",
  "keywords": ["vue3", "components", "library"],
  "type": "module",
  "module": "dist/es/suju-plus.js",
  "main": "dist/umd/suju-plus.umd.cjs",
  "exports": {
    ".": {
      "import": "./dist/es/suju-plus.js",
      "require": "./dist/umd/suju-plus.umd.cjs"
    },
    "./style.css": {
      "import": "./dist/style.css",
      "require": "./dist/umd/style.css"
    }
  },
  "files": ["dist"],
  "scripts": {
    "dev": "vite",
    "build": "pnpm run build:es && pnpm run build:umd",
    "build:es": "vite build -c ./vite.es.config.js && pnpm run move-style",
    "build:umd": "vite build -c ./vite.umd.config.js",
    "move-style": "move-file dist/es/style.css dist/style.css",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
    "format": "prettier --write src/"
  },
  "dependencies": {},
  "peerDependencies": {
    "@vueuse/core": "^10.2.1",
    "ant-design-vue": "^4.0.7",
    "vue": "^3.3.4"
  },
  "devDependencies": {
    "ant-design-vue": "^4.0.7",
    "@ant-design/icons-vue": "6.1.0",
    "@rushstack/eslint-patch": "^1.3.3",
    "@vitejs/plugin-vue": "^4.4.0",
    "@vitejs/plugin-vue-jsx": "^3.0.2",
    "@vue/eslint-config-prettier": "^8.0.0",
    "@vueuse/core": "^10.2.1",
    "autoprefixer": "^10.4.14",
    "eslint": "^8.49.0",
    "eslint-plugin-vue": "^9.17.0",
    "move-file-cli": "^3.0.0",
    "postcss": "^8.4.31",
    "prettier": "^3.0.3",
    "tailwindcss": "2.2.16",
    "vite": "^4.4.11",
    "vue": "^3.3.4",
    "vue-router": "^4.2.5"
  }
}

发布组件库

注册 npm 账号

切换 npm 源

npm config set registry https://registry.npmjs.org/

登录 npm 账号

npm login

发布 npm 包

npm publish
Last Updated 2023/11/18 20:07:33