Vue发布自定义组件库

889 阅读2分钟

NPM源的切换

  • 我们自定义的包只能发布到NPM官方源,国内镜像只是官方源的副本
  • 因此我们的用户注册和包的发布,都必须先切换到官方源
  • 切换方式请参见 NPM注册源的配置

注册NPM用户

  • 进入 npm 官网注册账号
  • 注册地址:www.npmjs.com/
  • 注册过程中需要安装一个一次性验证器,以生成一个6位数动态校验码(OTC),笔者使用的是Google的Authenticator手机APP;
  • 身份验证器下载完毕后,在自己的NPM账号中生成【身份验证器APP】所需的二维码,用自己的【身份验证器APP】扫描该二维码,就能动态生成6位数的OTC(one-time-code),登录NPM账号时需要用户名+邮箱+密码+OTC;

微信图片_20221129101618.jpg

微信图片_20221129101631.jpg

image.png

登录NPM账号

npm login

后续需要输入用户名、密码、以及一次性校验码,请使用上一步中的校验器生成;

创建工程

手动或者是使用命令创建均可

#手动创建库工程,本例名曰jdzb-ui
cd jdzb-ui

// 如果将来 npm i jdzb-ui
npm init -y

// 如果将来 npm i @steveouyang/jdzb-ui
npm init --scope=steveouyang

配置package.json,请根据自己的项目细节自行配置

{
  "name": "jdzb-ui",
  "version": "1.0.0",
  "description": "京东正版UI",
  "main": "src/main.js",
  "author": "steve ouyang",
  "scripts": {
    "dev": "npx rollup -wc rollup.config.dev.js",
    "build": "npx rollup -c rollup.config.js"
  },
  "license": "ISC",
  "devDependencies": {
    "@rollup/plugin-babel": "^5.3.1",
    "@rollup/plugin-commonjs": "^22.0.0",
    "@rollup/plugin-json": "^4.1.0",
    "@rollup/plugin-node-resolve": "^13.3.0",
    "rollup-plugin-scss": "^3.0.0",
    "rollup-plugin-vue": "^6.0.0"
  }
}

自定义组件库案例

项目地址

项目结构

── jdzb-ui
    |—— node_modules
    |—— lib
    |—— src
         |—— main.js
         |—— components
                 |—— Elevator
                         |—— index.vue
                         |—— index.css
                         |—— index.js
         |—— css
    |── package.json
    |—— rollup.config.js
    |—— README.md

组件定义

index.vue

<template>
  <div class="elevator elevator_recommend" :class="{ elevator_fix: fix }">
    <svg
      class="svgcont"
      version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
      style="display: none"
    >
      <defs>
        <symbol id="icon_timline" viewBox="0 0 16 16">
          <path
            d="M12.986 5.582a.505.505 0 0 0 .25-.063c.34-.188.364-.738.056-1.252-.235-.391-.59-.643-.905-.643a.511.511 0 0 0-.251.063c-.338.188-.363.738-.055 1.252.234.391.59.643.905.643m-9.975 0c.317 0 .674-.252.91-.643.31-.514.286-1.064-.056-1.253a.52.52 0 0 0-.252-.062c-.317 0-.674.252-.91.643-.31.514-.285 1.064.056 1.252.076.042.16.063.252.063m10.779.956a.372.372 0 0 0-.295-.024.387.387 0 0 0-.225.201c-.013.025-.315.609-1.062 1.191-.738.573-2.062 1.26-4.185 1.279h-.075c-2.076 0-3.384-.653-4.116-1.2-.779-.581-1.094-1.177-1.097-1.182a.395.395 0 0 0-.23-.199.371.371 0 0 0-.293.031.42.42 0 0 0-.161.549c.039.077.413.772 1.316 1.45.826.622 2.29 1.363 4.573 1.363l.089-.001c2.331-.019 3.81-.793 4.641-1.439.862-.672 1.225-1.345 1.292-1.475a.419.419 0 0 0-.172-.544m-5.848-.124c1.092 0 2.268-.218 2.268-.697S9.034 5.02 7.942 5.02c-1.071 0-2.152.216-2.152.697s1.081.697 2.152.697m4.086 4.647c-1.004.574-1.052 1.597-1.015 2.223a.098.098 0 0 1-.113.104c-.693-.096-1.161-.407-1.757-.943a1.343 1.343 0 0 0-.933-.351l-.21.003c-3.809 0-6.897-2.458-6.897-5.489 0-3.032 3.088-5.489 6.897-5.489 3.808 0 6.896 2.457 6.896 5.489 0 1.834-1.129 3.461-2.868 4.453M8 0C3.589 0 0 2.931 0 6.533c0 3.603 3.589 6.533 8 6.533l.224-.002c.102 0 .204.033.272.099 1.239 1.205 2.568 1.323 3.303 1.336a.3.3 0 0 0 .303-.342c-.087-.613-.318-1.813.477-2.263C14.722 10.684 16 8.677 16 6.533 16 2.931 12.411 0 8 0"
          ></path>
        </symbol>
        <symbol id="icon_feedback" viewBox="0 0 16 16">
          <path
            d="M1.4,15l0-1l14,0v1H1.4z M2.5,13H2l0-0.5c0-0.1,0-0.2,0-0.3c0-0.3,0.1-0.8,0.2-1.3c0.1-0.7,0.4-1.7,0.4-2 C2.7,8.7,2.8,8.4,3,8.3l8-8c0.3-0.3,0.8-0.3,1.1,0l2.7,2.7c0.3,0.3,0.3,0.8,0,1.1l0,0l-8,8c-0.1,0.1-0.4,0.3-0.6,0.4 c-0.7,0.2-1.4,0.3-2.1,0.4c-0.5,0.1-1,0.2-1.3,0.2C2.7,13,2.6,13,2.5,13z M11.6,1.1L3.7,9c0,0.1-0.1,0.1-0.1,0.2 c-0.1,0.2-0.3,1.1-0.4,1.9c-0.1,0.3-0.1,0.6-0.1,0.9c0.3,0,0.6-0.1,0.9-0.1c0.7-0.1,1.3-0.2,2-0.4c0,0,0.1-0.1,0.2-0.1L14,3.5 L11.6,1.1z M14.1,3.3C14.1,3.3,14.1,3.3,14.1,3.3L14.1,3.3z M11.4,0.9C11.4,0.9,11.4,0.9,11.4,0.9L11.4,0.9z"
          ></path>
        </symbol>
      </defs>
    </svg>

    <ul class="elevator_list">
      <template
        v-for="({ name, imgSrc, link, icon, floorRef }, index) in floors"
        :key="index"
      >
        <li
          v-if="name && !link"
          class="elevator_item"
          @click="navToFloor(floorRef)"
        >
          <a
            class="elevator_lk"
            href="javascript:void(0);"
            clstag="h|keycount|core|elvt_01"
            tabindex="-1"
            aria-hidden="true"
          >
            <div>
              <span class="elevator_lk_bg"></span>
              <span class="elevator_lk_txt">{{ name }}</span>
            </div>
          </a>
        </li>

        <li
          v-else-if="imgSrc"
          class="elevator_item"
          @click="navToFloor(floorRef)"
        >
          <a
            class="elevator_lk elevator_promotional"
            href="javascript:void(0);"
            clstag="h|keycount|core|elvt_08"
            tabindex="-1"
            aria-hidden="true"
            @mouseover="imgHovered = true"
            @mouseout="imgHovered = false"
          >
            <img v-if="imgHovered" :src="imgSrc[1]" alt="" />
            <img v-else :src="imgSrc[0]" alt="" />
          </a>
        </li>

        <li v-else class="elevator_item">
          <a
            class="elevator_lk elevator_lk2"
            :href="link"
            target="_blank"
            clstag="h|keycount|core|elvt_06"
            ><span class="elevator_lk_bg"></span>
            <svg>
              <use :xlink:href="icon"></use>
            </svg>
            <span class="elevator_lk_txt">{{ name }}</span>
          </a>
        </li>
      </template>
    </ul>
    <a
      class="elevator_totop"
      href="javascript: void(0);"
      clstag="h|keycount|core|elvt_07"
      tabindex="-1"
      aria-hidden="true"
      @click="toTop"
    >
      <span class="elevator_totop_icon"></span>
      <span class="elevator_totop_txt">顶部</span>
    </a>
  </div>
</template>

<script setup>
import { reactive, toRefs } from "vue";

/* 无法使用模块化 */
// import { yScrollTo } from "../utils/scrollUtil.js";
// import "../css/first-screen.chunk.css"
// import "../css/index.chunk.css"

let timer = null;
const yScrollTo = (y, timespan = 1000) => {
  if (timer) clearInterval(timer);

  let current = document.documentElement.scrollTop;
  let step = (y - current) / (timespan / 40);

  timer = setInterval(() => {
    if (Math.abs(y - current) >= Math.abs(step)) {
      current += step;
      window.scrollTo(0, current);
    } else {
      clearInterval(timer);
      timer = null;
      window.scrollTo(0, y);
    }
  }, 40);
};

const name = "Elevator";

const { fix, floors, scrollSpeed } = defineProps({
  fix: Boolean,
  floors: Array,
  scrollSpeed: Object,
});

const { imgHovered } = toRefs(
  reactive({
    imgHovered: false,
  })
);

const navToFloor = (ref) => {
  // console.log("navToFloor", ref);
  // window.scrollTo(0,document.documentElement.scrollTop + ref.$el.getBoundingClientRect().top - 50)
  yScrollTo(
    document.documentElement.scrollTop +
      ref.$el.getBoundingClientRect().top -
      50,
    scrollSpeed.toFloor || 1000
  );
};

const toTop = () => {
  yScrollTo(0, scrollSpeed.toTop || 500);
};
</script>

<style lang="scss" scoped>
.elevator_promotional:hover img {
  z-index: 2;
}
</style>

index.css内容较多,请移步源码地址自行查阅

index.js

import Elevator from "./index.vue";

Elevator.install = function (Vue) {
  Vue.component(Elevator.name, Elevator);
};

export default Elevator;

main.js

import Elevator from "./components/Elevator/index.js";

import "./components/Elevator/index.css";
// import "./css/first-screen.chunk.css"
// import "./css/index.chunk.css"

import { version } from "../package.json";

const components = [Elevator];

const install = function (Vue) {
  components.forEach((component) => {
    Vue.component(component.name, component);
  });
};
if (typeof window !== "undefined" && window.Vue) {
  install(window.Vue);
}
export { Elevator, install };
export default { version, install };

rollup.config.js

import resolve from "@rollup/plugin-node-resolve";
import vue from "rollup-plugin-vue";
import babel from "@rollup/plugin-babel";
import commonjs from "@rollup/plugin-commonjs";
import scss from "rollup-plugin-scss";
import json from "@rollup/plugin-json";

const formatName = "SuperEp";

const config = {
   input: "./src/main.js",
   output: [
      {
         file: "./lib/bundle.cjs.js",
         format: "cjs",
         name: formatName,
         exports: "auto",
      },
      {
         file: "./lib/bundle.js",
         format: "iife",
         name: formatName,
         exports: "auto",
      },
   ],
   plugins: [
      json(),
      resolve(),
      vue({
         css: true,
         compileTemplate: true,
      }),
      babel({
         exclude: "**/node_modules/**",
      }),
      commonjs(),
      scss(),
   ],
};

export default config;

执行打包

npm i
npm run build

打包成功后会在lib目录下生成bundle.jsbundle.cjs.js两个文件,即代码中真正使用的文件

发布包

npm publish

// 开源发布
npm publish --access=public

取消发布

npm unpublish jdzb-ui --force

一些可能的报错处理

在Vue项目中使用自定义组件库

npm i jdzb-ui

入口文件main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

/* 全局引入 main.js */
import JdzbUI from "jdzb_ui";

// 引入全局样式
// import "jdzb-ui/lib/first-screen.css";

createApp(App).use(store).use(router).use(JdzbUI).mount('#app')

在组件中使用自定义组件

import { Elevator } from "jdzb_ui";

在模板中部署

<!-- Elevator -->
<Elevator
:fix="fixTitleBar"
:floors="floors"
:scrollSpeed="{ toFloor: 200, toTop: 500 }"
></Elevator>