前言

Vue 项目开发过程中,经常用到插件,比如原生插件 vue-routervuex,还有 element-ui 提供的 notifymessage 等等。这些插件让我们的开发变得更简单更高效。那么 Vue 插件是怎么开发的呢?如何自己开发一个 Vue 插件然后打包发布到npm?

本文涉及技术点:

  1. Vue 插件的本质
  2. Vue.extend() 全局方法
  3. 如何手动挂载 Vue 实例
  4. Vue.use() 的原理
  5. 如何打包成 umd 格式
  6. 发布前如何测试 npm

一、定义

什么是Vue插件,它和Vue组件有什么区别?来看一下官网的解释:

“插件通常用来为 Vue 添加全局功能。”

“组件是可复用的 Vue 实例,且带有一个名字。”

—— Vue.js 官网

Emmmm,似乎好像有种朦胧美。。。

我来尝试解释一下,其实, Vue 插件Vue组件 只是在 Vue.js 中包装的两个概念而已,不管是插件还是组件,最终目的都是为了实现逻辑复用。它们的本质都是对代码逻辑的封装,只是封装方式不同而已。在必要时,组件也可以封装成插件,插件也可以改写成组件,就看实际哪种封装更方便使用了。

除此之外,插件是全局的,组件可以全局注册也可以局部注册。

我们今天只聚焦 Vue 插件。

插件一般有下面几种:

  • 添加全局方法或者属性。如: vue-custom-element
  • 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  • 通过全局混入来添加一些组件选项。如 vue-router
  • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

    —— Vue.js 官网

二、插件的使用

插件需要通过 Vue.use() 方法注册到全局,并且需要在调用 new Vue() 启动应用之前完成。之后在其他 Vue 实例里面就可以通过 this.$xxx 来调用插件中提供的 API 了。

下面以实现一个简易的提示框插件 toast 为例,给大家介绍怎么一步一步开发和发布一个 Vue 插件。

希望达到的效果:

在 main.js 中 use:

// src/main.js
import Vue from 'vue'
import toast from '@champyin/toast' Vue.use(toast)

在 App.vue 的生命周期 mounted 方法里调用 this.$toast():

// src/App.vue
<template>
<div>
<button @click='handleClick'>Toast</button>
</div>
</template>
<script>
export default {
name: 'demo',
methods: {
handleClick() {
this.$toast({
type: 'success',
msg: '成功',
duration: 3
})
}
}
}
</script>

运行后在页面上点击按钮,弹出 成功 的提示,然后3秒后消失。

在线体验地址:http://champyin.com/toast/

三、插件开发

1. 编写 toast 的本体。

在 Vue 项目(你可以使用 Vue-cli 快速生成一个 Vue 项目,也可以自己用 webpack 搭建一个)的 src 目录下创建 components/Toast/index.vue 文件。

// src/components/Toast/index.vue
<template>
<transition name='fade'>
<div class='uco-toast' v-if='isShow'>
<span :class='iconStyle'></span>
<span>{{msg}}</span>
</div>
</transition>
</template> <script>
export default {
data() {
return {
isShow: false,
type: 'success',
msg: '成功',
duration: 1,
};
},
computed: {
iconStyle() {
return `tfont icon-${this.type} toast-icon`;
},
},
mounted() {
this.isShow = true;
setTimeout(() => {
this.isShow = false;
}, this.duration * 1000);
},
};
</script> <style lang='less' scoped>
// 样式略
</style>

现在 toast 本体完成了,但是它里面的数据目前没法改变,因为我没有给它定义 props 属性。这不是 bug,而是,插件并不是通过 pops 来传值的。

2. 手动挂载 toast 实例的 dom

为了给插件传值,可以利用基础 Vue 构造器 Vue.extend() 创建一个“子类”。这个子类相当于一个继承了 Vue 的 Toast 构造器。然后在 new 这个构造函数的时候,给 Toast 的 data 属性传值,然后手动调用这个实例的 $mount() 方法手动挂载,最后使用原生JS的 appendChild 将真实 DOM (通过实例上的 $el 属性获取)添加到 body 上。

在 src 目录下新建 components/Toast/index.js 文件:

// src/components/Toast/index.js
import Vue from 'vue';
import Toast from './index.vue'; // 使用 Vue.extend() 创建 Toast 的构造器
const ToastConstructor = Vue.extend(Toast); const toast = function(options = {}) {
// 创建 Toast 实例,通过构造函数传参,
// 并调用 Vue 实例上的 $mount() 手动挂载
const toastInstance = new ToastConstructor({
data: options
}).$mount(); // 手动把真实 dom 挂到 html 的 body 上
document.body.appendChild(toastInstance.$el); return toastInstance;
}; // 导出包装好的 toast 方法
export default toast;

3. 暴露 install 方法给 Vue.use() 使用。

为了支持 Vue.use(),Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。

—— Vue.js 官网

通过 Vue.js 源码也可以看出,Vue.use() 方法所做的事情就是调用插件或者组件的 install 方法,然后把全局 Vue 传进去供插件和组件使用。

// https://github.com/vuejs/vue/blob/dev/src/core/global-api/use.js
/* @flow */ import { toArray } from '../util/index' export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
} // additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}

在 src 目录下新建 components/index.js 文件,定义一个 install 方法,在里面将 toast 实例放到 Vue.prototype 上作为 Vue 实例的方法暴露到全局。

// src/components/index.js
import toast from './Toast/index';
import '../icon/iconfont.css'; // 准备好 install 方法 给 Vue.use() 使用
const install = function (Vue) {
if (install.installed) return;
install.installed = true; // 将包装好的 toast 挂到Vue的原型上,作为 Vue 实例上的方法
Vue.prototype.$toast = toast;
} // 默认导出 install
export default {
install,
};

现在插件就开发完成了,可以在当前项目中本地引用这个插件了。

//在 main.js 中
import toast from src/components/index.js;
Vue.use(toast); //在 App.vue 中
handleClick(){
this.$toast();
}

四、发布到npm

为了方便其他人也可以使用到这个插件,我们可以把它发布到 npm 上去。发布的步骤很简单,但是发布之前,需要有一些小配置和一些注意的地方。

1. 打包配置

首先我们要把它打包成可以给浏览器解析的 UMD 格式的的模块,并且去掉对 Vue.js 的打包,这样别人在 Vue 项目中使用这个插件的时候就不会有两份 Vue 或者出现 Vue 版本冲突的问题,以保证可以更好被独立引用。

如果你是用 Vue-cli 生成的项目,那只需要在你的 npm 脚本中配置一下库的打包命令:

// package.json
"build:lib": "vue-cli-service build --target lib --name toast --dest lib src/components/index.js"

命令说明:

--target:构建的目标
targetType 有三个选项:lib | wc | wc-async
lib:库
wc:web component
wc-async:异步的 web component --name:库或组件的名字
当入口为单一文件时,name为库或组件的文件名
当入口为global表达式时,name为每个库或组件文件名字的前缀 [entry]:打包入口
可以是.vue文件,也可以是.js文件
当注册多个web component时,入口可以是一个global表达式,如 components/*.vue --dest:输出目录
默认为dist目录,也可以修改为自定义的目录

然后运行 npm run build:lib,即可在 lib 目录下生成如下文件:

toast.umd.js 一个直接给浏览器或者AMD loader 使用的 UMD 包
toast.umd.min.js 一个压缩版 UMD 构建版本
toast.common.js 一个给打包器用的CommonJS包

如果你是用 webpack 搭建的 Vue 项目,那就需要在 webpck 中配置一下 output.libraryTarget 等属性:

// build/webpack.lib.conf.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = {
mode: 'production',
entry: './src/components/index.js',
output: {
path: path.resolve(__dirname, '../lib'),
filename: 'toast.js',
library: 'toast',
libraryTarget: 'umd',
libraryExport: 'default',
umdNamedDefine: true,
globalObject: 'typeof self !== \'undefined\' ? self : this',
},
externals: {
vue: {
root: 'Vue',
commonjs: 'vue',
commonjs2: 'vue',
amd: 'vue',
},
},
plugins: [
new CleanWebpackPlugin(),
],
};

然后运行 npm run build:lib,即可在 lib 目录下生成如下文件:

toast.js 直接给浏览器或者AMD loader 使用的 UMD 包

2. 发布前的测试

发布前,我们需要配置一下 package.json 里的 namemain 字段:

name 的值是最终包的名字,installimport 的就是这个名字(请确保全网唯一)。

main 的值是包的入口文件路径(相对当前文件的路径),一定要填写正确,否则包无法被引用。

"name": "@champyin/toast",
"main": "lib/toast.js",

为了确保包的配置没有问题,我们可以利用 npm link 命令在本地测试一下包的使用情况。使用npm link测试包的使用估计很多人都会,就不赘述了。如果有需要可以看我的另一篇中文章npm link详解

这个时候,我们其实就可以发布了,但是为了防止把不必要的文件发布出去(比如测试用例和demo)浪费人家下载的流量,我们最好是建一个 .npmigore 文件,语法跟 .gitignore 相同。

3. 发布

发布的方法很简单(不过首先你要有个 npm 账号),在 package.json 所在的目录下执行这两句就可以了:

npm add user
npm publish

关于更详细的发布教程,我在另一篇文章有专门细说:如何发布一个npm模块

4. 安装测试

其实到了这一步一99.99%是不会出错了,安装一遍只是为了那 0.01% 的万一。

在另一个 Vue 项目里(注意不能在开发toast的项目里哈),从 npm 安装自己刚才发布的包:

npm i -D @champyin/toast

然后在项目中使用一下自己的插件,点击按钮就会弹出 toast 小提示了。

//在 main.js 中
import toast from '@champyin/toast';
Vue.use(toast); //在 App.vue 中
handleClick(){
this.$toast();
}

项目体验地址:http://champyin.com/toast/

npm 地址:https://www.npmjs.com/package/@champyin/toast

欢迎给我提 issue:https://github.com/yc111/toast/issues

Happy coding

如何开发和发布一个Vue插件的更多相关文章

  1. 用webpack发布一个vue插件包

    创建库 本来以为很简单,结果配置了webpack之后,运行build就报错了,似乎不认识es6语法,于是先后安装了几个包: @babel/core @babel/preset-env babel-lo ...

  2. 第一个Vue插件从封装到发布

    前言 这是我封装的第一个Vue插件,实现的功能是滑动选择省市区,虽然只是一个简单的插件,但还是挺开心的,记录一下步骤. 插件地址:https://github.com/leichangchun/vue ...

  3. 从零开始编写一个vue插件

    title: 从零开始编写一个vue插件 toc: true date: 2018-12-17 10:54:29 categories: Web tags: vue mathjax 写毕设的时候需要一 ...

  4. 自己编写并发布一个Vue组件

    自己编写并发布一个Vue组件 1. 几种开源协议的介绍 https://blog.csdn.net/techbirds_bao/article/details/8785413 2.开始编写组件 新建p ...

  5. 开发第一个VUE插件

    背景 项目中用到element-ui,里面用到了弹出组件,但是效果不太满意,于是自己就想写一个简单的弹出组件.目前已经发布到npm:可以通过npm i dialog-wxy -s 进行下载使用页面调用 ...

  6. 从0到1发布一个Vue Collapse组件

    需求背景 最近在项目中遇到了一个类似Collapse的交互需求,因此到github上找了一圈关于Vue Collapse的相关轮子,但是多少都有些问题.有的是实现问题,例如vue2-collapse, ...

  7. 学习如何写一个vue插件【入门篇】

    #### 疑答 1.市面上已经有那么多插件可用,为什么还要造轮子?学习.借鉴思想.应用到开发 2.能否在项目中使用?与网上插件使用相同   更新维护问题怎么解决? 自身动力,使用者反馈等 #### 准 ...

  8. 如何创建并发布一个 vue 组件

    步骤 创建 vue 的脚手架 npm install -g @vue/cli vue init webpack 绑定 git 项目 cd existing_folder git init git re ...

  9. 如何发布一个npm包(基于vue)

    前言:工作的时候总是使用别人的npm包,然而我有时心底会好奇自己如何发布一个npm包呢,什么时候自己的包能够被很多人喜欢并使用呢...今天我终于迈出了第一步. 前提:会使用 npm,有 vue 基础, ...

随机推荐

  1. How Cocoa Beans Grow And Are Harvested Into Chocolate

    What is Cocoa Beans Do you like chocolate? Most people do. The smooth, brown candy is deliciously sw ...

  2. 5.redis主从配置

    Redis的主从复制 1.什么是主从复制 持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据 ...

  3. [LC] 198. House Robber

    You are a professional robber planning to rob houses along a street. Each house has a certain amount ...

  4. 吴裕雄--天生自然python学习笔记:python文档操作自动查找替换 Word 文件中的指定文字

    Win32com 组件提供了自动替换 Word 文件中指定文字 的功能 .在使用“查找” 功能替换文字之前,可先清除源文字及目标文字的格式,以免影响替换效果,语法为 : 替换 Word 文件特定文字的 ...

  5. react全家桶从0搭建一个完整的react项目(react-router4、redux、redux-saga)

    react全家桶从0到1(最新) 本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目.文中针对react.webpack.babel.react-route.redux.redu ...

  6. 编程原理—如何用javascript代码解决一些问题

    关于编程,我最喜欢的就是解决问题.我不相信有谁天生具有解决问题的能力.这是一种通过反复锻炼而建立并维持的能力.像任何练习一样,有一套指导方针可以帮助你更有效地提高解决问题的能力.我将介绍5个最重要的软 ...

  7. python自动化工具

    公司有些业务不断的重复复制和黏贴实在让人头疼,于是乎考虑使用python自动的生成文件,并且替换文件中的一些内容,把需要复制和黏贴的内容制作成 模版,以后的开发工作可以根据模版来自动生成文件,自己以后 ...

  8. [PyTorch入门之60分钟入门闪击战]之神经网络

    神经网络 来源于这里. 神经网络可以使用torch.nn包构建. 现在你对autograd已经有了初步的了解,nn依赖于autograd定义模型并区分它们.一个nn.Module包含了层(layers ...

  9. golang实现chunk方式的查询

    有一个需求,是把表里面所有的数据都查询出来,并且生成json文件.因为一张表里面的数据很多,所以不可能一次性全部查询出来,所以需要用到chunk.之前用的gorm,但是发现gorm没有chunk方式的 ...

  10. Leetcode 981. Time Based Key-Value Store(二分查找)

    题目来源:https://leetcode.com/problems/time-based-key-value-store/description/ 标记难度:Medium 提交次数:1/1 代码效率 ...