开发一个按需加载的vue组件库全流程记录

3,078 阅读6分钟

前言

几个月前写了一个数据大屏的项目,基于vue2、echarts5的数据大屏。有小伙伴在后台留言说适配方案很实用。最近不太忙,就打算单独把大屏适配组件封装一下,传到npm。

虽然只有一个组件,也算是一次完整的,从零到一发布按需加载的vue组件库的经历。看完后相信你也能打包并发布属于自己的vue组件库。

打包工具的选择

当前要打包的组件库,虽然只有一个组件,但是我们希望,后面可以进行扩展,并且,当组件增多时,我们希望可以对组件按需加载。

之前写过两篇文章:

用webpack打包一个按需加载的vue组件库

用rollup打包一个按需加载的组件库

分别探讨了使用webpackrollup打包按需加载组件库的原理和方法。

相比较而言,rollup的配置比较简单。而且,通过webpack打包出来的组件库,按需加载需要借助插件,比如element-ui使用的是babel-plugin-component

所以,我们这里将选用rollup

项目搭建

mkdir bs-display
cd bs-display
npm init -y 

新建各种配置文件、组件代码文件夹、测试文件夹,项目结构如下:(dist目录由打包生成) image.png rollup的配置在前面的文章中有非常详细的讲解,这里我们直接上配置文件的代码:

//rollup.prod.js
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import vue from 'rollup-plugin-vue'
import autoprefixer from 'autoprefixer'
import { terser } from 'rollup-plugin-terser'
export default {
  input: "./src/index.js",
  
  //输出umd、es、cjs三种模块规范的打包文件
  output: [   
    {
      file: './dist/bs-display-umd.js',     
      format: 'umd',
      name: 'bsDisplay'
    },
    {
      file: './dist/bs-display-es.js',
      format: 'es'
    },
    {
      file: './dist/bs-display-cjs.js',
      format: 'cjs'
    }
  ],
  plugins:[
    babel({
        exclude: 'node_modules/**'    //转换es6语法
    }),
    vue({                              //编译vue代码,并为vue组件的样式加前缀
      style: {
        postcssPlugins: [
          autoprefixer()
        ]
      }
    }),
    commonjs(),                         //支持commonjs模块规范
    terser()                            //代码压缩
  ],
  external:[  
    'vue'
  ]
}

rollup支持的打包文件的格式有amd, cjs, es\esm, iife, umd。

  • amd为AMD标准
  • cjs为CommonJS标准
  • esm\es为ES模块标准
  • iife为立即调用函数
  • umd同时支持amd、cjs和iife

上述的打包配置,会以"./src/index.js"作为入口文件,打包后,在dist文件夹下生成以下3个文件: image.png 在这三个文件中,都包含全部业务逻辑,只是导出方式不同。在下面的调试方法中,我们再展开来讲。

组件开发

适配的原理

大屏成像原理几乎都是投屏,也就是把电脑屏幕通过有线信号投放到大屏上,电脑上呈现什么内容,大屏上就会呈现什么内容,所以浏览器的横向和纵向上都不能出现滚条。

这里我们用CSS3的scale,将容器设置为设计稿宽高(如3840 * 2160),再动态根据浏览器视口的宽高进行缩放,从而实现容器始终铺满浏览器视口,而不出现滚动条。组件的内容如下:

// Frame.vue

//容器组件的结构
<template>
<div class="bsd-frame" :style="{'background': bgColor}" ref="bsdFrame">   
  <slot></slot>
</div>
</template>
<script>
export default {
  name: 'bsd-frame',
  props: {
    width: {
      type: Number,
      default: 3840 
    },
    height: {
      type: Number,
      default: 2160 
    },
    bgColor: {
      default: 'rgb(2, 2, 37)'
    }
  },
  data(){
    return {
      frameWidth: 0,
      frameHeight: 0
    }
  },
  methods: {
    setSize(){
      //获取父组件传入的容器宽高,通常是设计稿宽高,或者电脑屏幕的宽高
      this.frameWidth = this.width || screen.width
      this.frameHeight = this.height || screen.height

      //将传入的宽高设为大屏边框容器的宽高
      let frame = this.$refs.bsdFrame  
      frame.style.width = this.frameWidth + 'px'
      frame.style.height = this.frameHeight + 'px'
    },
    setScale(){
      //获取页面的宽高,使用时需要设置html,body{ height:100% },否则获取到的页面高度为0
      let bodyWidth = document.body.clientWidth,
          bodyHeight = document.body.clientHeight    
          
      //根据浏览器视口的的宽高 和 大屏边框容器的宽高 计算缩放值
      let scaleX = bodyWidth / this.frameWidth,
          scaleY = bodyHeight / this.frameHeight

      //为大屏边框容器设置缩放值
      this.$refs.bsdFrame.style.transform = `scale(${scaleX},${scaleY})`
    },
    debounce (fn, t) {
      const delay = t || 300
      let timer
      return () => {
        const args = arguments
        if (timer) {
          clearTimeout(timer)
        }
        timer = setTimeout(() => {
          timer = null
          fn.apply(this, args)
        }, delay)
      }
    }
  },
  mounted() {
    this.setSize()
    this.setScale()
    this.debouncedSetScale = this.debounce(this.setScale, 500)
    //触发resize事件时,重新计算 大屏边框容器 的缩放值
    window.addEventListener('resize', this.debouncedSetScale)
  },
  destroyed(){
    window.removeEventListener('resize', this.debouncedSetScale)
  }
}
</script>
<style lang="scss">
.bsd-frame{
  position: fixed;
  transform-origin: left top;   //将transform-origin设为左上角
}
</style>

使用方式:

<bsd-frame>     //宽高和背景色如果不传则使用默认值
  <div>大屏内容</div>
</bsd-frame>

组件库的相关文件

我们的打包配置,会以"./src/index.js"作为入口文件,那么"./src/index.js"的内容是什么呢?src文件夹的目录结构是什么呢?

image.png 组件都放在src下的components文件夹中,每一个组件对应一个文件夹,每个文件夹包含一个.vue文件和index.js文件,这是为了按需加载做准备。 image.png Frame/index.js会导出一个函数,当外部导入这个函数并执行时,就会实现Frame组件的全局注册。(test/index.js同理)

export default function(Vue){
    Vue.component(Frame.name, Frame)
}

"./src/index.js"的内容如下:

//  ./src/index.js的内容

import Frame from "./components/Frame"   //导入Frame/index.js的函数
import test from "./components/test"   //导入test/index.js的函数

// Vue.use的用法,执行该函数会全局注册所有组件
function install(Vue){    
    Vue.use(Frame)
    Vue.use(test)
}

//通过`script`标签引入组件库的情况,注册所有组件
if(window && window.Vue) {    
    Vue.use(install)
}

/***
在es模块中, 能被按需引入的变量需要用这些方式导出: 
export const a = 1 
export function a(){} 
export { a, b } 
而不能使用export default 
***/

//这里导出各组件的全局组件函数
//当以 import { Frame } from 'xxx'的方式导入时,就只会导入Frame的相关代码,而不会导入test相关代码
export { 
    Frame,
    test
}

export default install

写好了组件代码和打包配置文件,打包后,我们还需要修改package.json

"main": "./dist/bs-display-umd.js",
"module": "./dist/bs-display-es.js",

接下来就是本地调试了。

本地调试

通过script标签引入

新建一个html文档(我们这里是在example文件夹下新建一个index.html),引入打包好的组件库引入。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html,body,#app {
            height: 100%;
            width: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <div id="app">
        <bsd-frame><div>大屏内容</div></bsd-frame>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="../dist/bs-display-umd.js"></script>
<script>
    new Vue({
        el: '#app'
    })
</script>
</html>

image.png 文字是黑色的,而且被缩小后文字太小了,所以截图中基本上看不到。如果设置:

color: #fff;
font-size: 100px;

则能看到有拉伸效果的文字。 image.png

import引入

搭建一个用于测试的vue项目LibTest,或者,更方便快捷一点的,在已有的vue的项目中加一个测试组件,并添加到routes中。

在myLib组件库项目根目录下执行 npm link

cd bs-display
npm link

在测试项目根目录下执行npm link bs-display

cd LibTest
npm link bs-display  //这个bs-display是在bs-display项目的package.json中定义的"name": "bs-display"

然后,就可以和普通npm安装的组件库一样使用了

在测试项目LibTest的入口js中,引入bs-display,执行Vue.use()注册组件

import { Frame } from 'bs-display'  //按需引入
Vue.use(Frame)

在LibTest的App.vue中使用组件:

<template>
  <div>
    <bsd-frame><div>大屏内容</div></bsd-frame>
  </div>
</template>

npm发布

注册npm账号

在npm官网按照提示注册账号。npm官网

登录npm账号

在命令行登录npm账号。 在命令行输入: npm login

就会出现下面的登录界面 image.png 如果你的npm源是淘宝镜像,那么需要更换回默认的npm源:

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

npm publish

在发布之前,如果你的包引用了第三方包,则需要确保在package.json中的dependencies字段,写入了依赖的包及版本。

修改package.json中的files字段,指定需要上传到npm的文件。

"files": [
  "dist"  //上传dist文件夹中的所有文件
],

此外,包的名字对应的是package.json中的"name"字段,这里我们将包名改成big-screen-display

然后,在命令行输入:npm publish。等待发布完成即可。 image.png

这是在测试项目中通过npm i big-screen-display --S安装到node_modules中的big-screen-display包的内容。

image.png

npm更新

当包的内容进行了更新,需要再次发布时:

首先,运行npm version patch更新版本,它会修改package.json文件中的version值。

然后,再次运行npm publish,就会上传最新版本的包。

npm删除

  • npm unpublish big-screen-display@1.0.0 删除指定版本
  • npm unpublish big-screen-display --force 删除整个包

结语

以上就是写一个按需加载的vue组件库的全部流程,看完后相信你也能发布属于自己的vue组件库啦,一起加油吧。

如果有需要大屏适配组件的朋友,也欢迎安装big-screen-display