【读vue 源码】溯源 import Vue from 到底做了什么?

九路 等级 775 0 0

阅读资源

vue.js源码托管地址

flow 静态检查工具地址

rollup 源码构建

flow 静态检查工具

flow 是由Facebook出品的javascript静态类型检查工具。vue.js 的源码是采用flow做了静态类型检查。因为javascript是动态类型的语言。语言灵活的同时也很容易引发一些隐蔽的隐患代码。在编译的时候看上去不会报错,但运行的时候就会出现奇奇怪怪的bug。而类型检查,就是在编译期尽早发现由类型错误引起的bug,又不影响代码运行(不需要运行时动态检查类型)。一些复杂的项目使用工具的手段可以增强代码的可读性,增加项目的可维护性。

flow 类型检查分为两种方式:

  • 类型推断:通过变量使用的上下文来推断出变量类型,然后根据推断来检查类型。
  • 类型注释:注释好变量的类型,通过注释来检查类型。
// 类型推断
// @flow
function split(str) {
    return str.split(' ')
}
split(1)      // Error!
split('abc')  // Works! 

传入参数 1 的时候,flow 代码检查就会报错,因为 split()方法是字符串原型对象上的方法,它期待的参数类型是字符串,而传入的确实是数字型,所以就会报错。当传入‘abc’字符串后就可以正常执行代码了。

// 类型注释
// @flow
function concat(a: string, b: string) {
  return a + b;
}

concat("A", "B");   // Works!
concat(1, 2);       // Error! 

因为加运算符,即可以执行数字的相加,也可以执行字符串的拼接。所以 concat()方法提前注释好参数的类型。接收的参数a和b都是字符串,所以调用concat(1, 2);就会报错。调用concat("A", "B")就能正常执行。

需要注意的是:当一个文件中出现注释@flow 的标记,说明该文件是需要用flow 进行类型检查的,否则不进行 flow 检查。更多内容请查看flow 静态检查工具官方文档。

vue.js源码中的flow应用

在 Vue.js 的主目录下有 .flowconfig 文件, 它是 Flow 的配置文件。如下:

[ignore]  // 忽略的文件
.*/node_modules/.*
.*/test/.*
.*/scripts/.*
.*/examples/.*
.*/benchmarks/.*

[include]

[libs] // 这里 [libs] 配置的是 flow,表示指定的库定义都在 flow 文件夹内
flow   // 对应的 flow 目录

[options]
unsafe.enable_getters_and_setters=true
module.name_mapper='^compiler/\(.*\)$' -> '<PROJECT_ROOT>/src/compiler/\1'
module.name_mapper='^core/\(.*\)$' -> '<PROJECT_ROOT>/src/core/\1'
module.name_mapper='^shared/\(.*\)$' -> '<PROJECT_ROOT>/src/shared/\1'
module.name_mapper='^web/\(.*\)$' -> '<PROJECT_ROOT>/src/platforms/web/\1'
module.name_mapper='^weex/\(.*\)$' -> '<PROJECT_ROOT>/src/platforms/weex/\1'
module.name_mapper='^server/\(.*\)$' -> '<PROJECT_ROOT>/src/server/\1'
module.name_mapper='^entries/\(.*\)$' -> '<PROJECT_ROOT>/src/entries/\1'
module.name_mapper='^sfc/\(.*\)$' -> '<PROJECT_ROOT>/src/sfc/\1'
suppress_comment= \\(.\\|\n\\)*\\$flow-disable-line 

vue.js 内的flow目录说明

flow
├── compiler.js        # 编译相关
├── component.js       # 组件数据结构
├── global-api.js      # Global API 结构
├── modules.js         # 第三方库定义
├── options.js         # 选项相关
├── ssr.js             # 服务端渲染相关
├── vnode.js           # 虚拟 node 相关 

vue 源码的目录结构

Vue.js 的源码都在 src 目录下,目录结构如下:

src
├── compiler    # 包含 Vue.js 所有编译相关的代码。
├── core        # 包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。
├── platforms   # Vue.js 是一个跨平台的 MVVM 框架,它可以跑在 web 上,也可以配合 weex 跑在 native 客户端上。
├── server      # 所有服务端渲染相关的逻辑都在这个目录下。
├── sfc         # vue.js通过 .vue 单文件来编写组件。这个目录下的代码逻辑会把 .vue 文件内容解析成一个 JavaScript 的对象。
├── shared      # 定义一些工具方法,这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。 

从整个目录结构来看,作者把功能模块拆的非常的清楚,相关的逻辑都放在同一个目录下来进行维护。可复用的代码也单独成为一个文件夹。

vue.js源码构建

Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。Rollup 是一个javascript 的模块打包工具。相比webpack更为轻量。了解更多请访问Rollup Github 地址

构建脚本

NPM 托管的项目都会有一个package.json的文件,对这个项目加以描述。script 字段用来定义NPM 的执行脚本。vue.js 的执行构建的脚本如下:

"scripts": {
    "build": "node scripts/build.js",
    "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
    "build:weex": "npm run build -- weex",
  }, 

也就是说,当我们执行 npm run build的时候,实际上就是执行 node scripts/build.js这条语句。也就是说 scripts/build.js就是构建入口的js文件。

构建过程

1. 从构建的入口文件开始:scripts/build.js

let builds = require('./config').getAllBuilds() //拿到所有的配置

// filter builds via command line arg
if (process.argv[2]) {
  const filters = process.argv[2].split(',')
  builds = builds.filter(b => {
    return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
  })
} else {
  // filter out weex builds by default
  builds = builds.filter(b => {
    return b.output.file.indexOf('weex') === -1
  })
}

build(builds) 

上面这部分代码,首先从配置文件scripts/config.js中读取配置相关的数据,在对配置进行相应的过滤,从而构建出不同用途的vue.js。

2. 查看构建的配置文件:scripts/config.js

const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.dev.js'),
    format: 'cjs',
    env: 'development',
    banner
  },
  'web-runtime-cjs-prod': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.prod.js'),
    format: 'cjs',
    env: 'production',
    banner
  },
  // Runtime+compiler CommonJS build (CommonJS)
  'web-full-cjs-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.dev.js'),
    format: 'cjs',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  'web-full-cjs-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.prod.js'),
    format: 'cjs',
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime only ES modules build (for bundlers)
  'web-runtime-esm': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.esm.js'),
    format: 'es',
    banner
  },
  // Runtime+compiler ES modules build (for bundlers)
  'web-full-esm': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.js'),
    format: 'es',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime+compiler ES modules build (for direct import in browser)
  'web-full-esm-browser-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.browser.js'),
    format: 'es',
    transpile: false,
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime+compiler ES modules build (for direct import in browser)
  'web-full-esm-browser-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.browser.min.js'),
    format: 'es',
    transpile: false,
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // runtime-only build (Browser)
  'web-runtime-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.js'),
    format: 'umd',
    env: 'development',
    banner
  },
  // runtime-only production build (Browser)
  'web-runtime-prod': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.min.js'),
    format: 'umd',
    env: 'production',
    banner
  },
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime+compiler production build  (Browser)
  'web-full-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.min.js'),
    format: 'umd',
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // Web compiler (CommonJS).
  'web-compiler': {
    entry: resolve('web/entry-compiler.js'),
    dest: resolve('packages/vue-template-compiler/build.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies)
  },
  // Web compiler (UMD for in-browser use).
  'web-compiler-browser': {
    entry: resolve('web/entry-compiler.js'),
    dest: resolve('packages/vue-template-compiler/browser.js'),
    format: 'umd',
    env: 'development',
    moduleName: 'VueTemplateCompiler',
    plugins: [node(), cjs()]
  },
  // Web server renderer (CommonJS).
  'web-server-renderer-dev': {
    entry: resolve('web/entry-server-renderer.js'),
    dest: resolve('packages/vue-server-renderer/build.dev.js'),
    format: 'cjs',
    env: 'development',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-prod': {
    entry: resolve('web/entry-server-renderer.js'),
    dest: resolve('packages/vue-server-renderer/build.prod.js'),
    format: 'cjs',
    env: 'production',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-basic': {
    entry: resolve('web/entry-server-basic-renderer.js'),
    dest: resolve('packages/vue-server-renderer/basic.js'),
    format: 'umd',
    env: 'development',
    moduleName: 'renderVueComponentToString',
    plugins: [node(), cjs()]
  },
  'web-server-renderer-webpack-server-plugin': {
    entry: resolve('server/webpack-plugin/server.js'),
    dest: resolve('packages/vue-server-renderer/server-plugin.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-webpack-client-plugin': {
    entry: resolve('server/webpack-plugin/client.js'),
    dest: resolve('packages/vue-server-renderer/client-plugin.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  // Weex runtime factory
  'weex-factory': {
    weex: true,
    entry: resolve('weex/entry-runtime-factory.js'),
    dest: resolve('packages/weex-vue-framework/factory.js'),
    format: 'cjs',
    plugins: [weexFactoryPlugin]
  },
  // Weex runtime framework (CommonJS).
  'weex-framework': {
    weex: true,
    entry: resolve('weex/entry-framework.js'),
    dest: resolve('packages/weex-vue-framework/index.js'),
    format: 'cjs'
  },
  // Weex compiler (CommonJS). Used by Weex's Webpack loader.
  'weex-compiler': {
    weex: true,
    entry: resolve('weex/entry-compiler.js'),
    dest: resolve('packages/weex-template-compiler/build.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies)
  }
} 

上面这部分代码是vue.js构建的配置、服务端渲染webpack插件、weex的打包配置。对于单个配置,遵循了Rollup 的构建规则。配置说明:

  • entry属性:构建入口js文件的地址。
  • dest属性:构建完成后的js文件地址
  • format属性:构建文件的格式。'cjs'表示构建出来的文件遵循 CommonJS 规范;'es' 表示构建出来的文件遵循 ES Module 规范; 'umd' 表示构建出来的文件遵循 UMD 规范。
  • banner属性:对vue.js的一个简单的描述。包含作者信息,版本号等。

3. 以一个配置为例探寻构建过程:web-runtime-cjs

'web-runtime-cjs-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.dev.js'),
    format: 'cjs',
    env: 'development',
    banner
  }, 

从配置中可见:入口的js文件地址,与完成后的js地址,均调用了resolve() 方法。

const aliases = require('./alias')
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
} 

resolve() 方法将传入的参数 p 调用split()方法,通过'/'分割成数组,然后取第一个元素设置为base,那么上述案例中 base即为 web。但是base 并不是真实路径,而是借助了别名的配置。别名配置的代码如下:scripts/alias

const path = require('path')

const resolve = p => path.resolve(__dirname, '../', p)
// 到真实文件的一个映射关系
module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  sfc: resolve('src/sfc')
} 

由上述代码可知:web 对应的知识路径是path.resolve(__dirname, '../', 'src/platforms/web')。由此找到它的入口文件是src/platforms/web/entry-runtime.js它经过 Rollup 的构建打包后,最终会在 dist/vue.runtime.common.js

Runtime Only VS Runtime + Compiler

通常我们利用 vue-cli 去初始化我们的 Vue.js 项目的时候会询问是用 Runtime Only 版本的还是 Runtime + Compiler 版本。他们的区别如下:

  • Runtime Only 通常需要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成 JavaScript,将template 编译成render 函数。因为是在编译阶段做的,所以它只包含运行时的 Vue.js 代码,因此代码体积也会更轻量。

  • Runtime + Compiler
    我们如果没有对代码做预编译,但又使用了 Vue 的 template 属性并传入一个字符串,则需要在客户端编译模板,如下所示:

// 需要编译器的版本
new Vue({
  template: '<div>{{ hi }}</div>'
})

// 这种情况不需要
new Vue({
  render (h) {
    return h('div', this.hi)
  }
}) 

综上:因为在 Vue.js 2.0 中,最终渲染都是通过 render 函数,如果写 template 属性,则需要编译成 render函数,那么这个编译过程会发生运行时,所以需要带有编译器的版本。显然,这个编译过程对性能会有一定损耗,所以推荐使用 Runtime Only

vue 的入口

当我们开发的时候import Vue from 'vue'到底做了些什么?顺着 Runtime Only 构建出来的vue.js 它的入口是在src/platforms/web/entry-runtime.js代码如下:

/* @flow */

import Vue from './runtime/index'

export default Vue 

上述代码 导出一个 Vue,而这个Vue是从./runtime/index导入的。

vue静态的全局配置和原型对象上的方法

继续看./runtime/index文件。代码如下:

/* @flow */

import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'

import {
 query,
 mustUseProp,
 isReservedTag,
 isReservedAttr,
 getTagNamespace,
 isUnknownElement
} from 'web/util/index'

import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

// install platform specific utils
// 静态的全局配置
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
// 原型__patch__
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
// 定义了原型上的$mount 方法
Vue.prototype.$mount = function (
 el?: string | Element,
 hydrating?: boolean
): Component {
 el = el && inBrowser ? query(el) : undefined
 return mountComponent(this, el, hydrating)
}

// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
 setTimeout(() => {
   if (config.devtools) {
     if (devtools) {
       devtools.emit('init', Vue)
     } else if (
       process.env.NODE_ENV !== 'production' &&
       process.env.NODE_ENV !== 'test'
     ) {
       console[console.info ? 'info' : 'log'](
         'Download the Vue Devtools extension for a better development experience:\n' +
         'https://github.com/vuejs/vue-devtools'
       )
     }
   }
   if (process.env.NODE_ENV !== 'production' &&
     process.env.NODE_ENV !== 'test' &&
     config.productionTip !== false &&
     typeof console !== 'undefined'
   ) {
     console[console.info ? 'info' : 'log'](
       `You are running Vue in development mode.\n` +
       `Make sure to turn on production mode when deploying for production.\n` +
       `See more tips at https://vuejs.org/guide/deployment.html`
     )
   }
 }, 0)
}

export default Vue 

上述代码还是从core/index文件中导入一个Vue,最后将其导出。在该文件中定义了Vue的一些静态的全局配置,和原型对象上的方法。

通过initGlobalAPI 给 vue 添加静态方法

继续往下看core/index如何定义 Vue 的,代码如下:

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue) // 定义了vue 本身的静态方法

Object.defineProperty(Vue.prototype, '$isServer', {
 get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
 get () {
   /* istanbul ignore next */
   return this.$vnode && this.$vnode.ssrContext
 }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
 value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue 

同样 它是从./instance/index 文件中导入 Vue ,最后将其导出。该文件通过initGlobalAPI方法 给 vue 添加静态方法。

通过 Mixin 混入往Vue 的原型上添加方法

继续往下,到./instance/index 文件,代码如下:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'


// 终于溯源结束了,Vue就是一个用 Function 实现的类,所以才通过 new Vue 去实例化它。
function Vue (options) {
 if (process.env.NODE_ENV !== 'production' &&
   !(this instanceof Vue)
 ) {
   warn('Vue is a constructor and should be called with the `new` keyword')
 }
 this._init(options)
}

// 在vue原型上挂了方法
initMixin(Vue) 
stateMixin(Vue) 
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue 

到此,终于溯源结束了,Vue就是一个用 Function 实现的类,所以才通过 new Vue 去实例化它。该文件中通过 Mixin 混入的方法,往Vue 的原型上添加了方法。

结束

最近一段时间都会认真的去看vue.js的源码。【读vue 源码】会按照一个系列去更新。分享自己学习的同时,也希望与更多的同行交流所得,如此而已。

收藏
评论区

相关推荐

【读vue 源码】溯源 import Vue from 到底做了什么?
阅读资源 vue.js源码托管地址(https://links.jianshu.com/go?tohttps%3A%2F%2Fgithub.com%2Fvuejs%2Fvue) flow 静态检查工具地址(https://links.jianshu.com/go?tohttps%3A%2F%2Fflow.org%2Fen%2Fdoc
vue 自定义指令-----文字提示气泡
实现一个简单的 鼠标滑过出现文字提示。啥也不说 上代码 包括了 指令类容更新 import Vue from 'vue' Vue.directive('mouse', { // 初始化 bind: function(el, binding, vnode) { }, // 被插入 inserted:function(el,
史上最全前端面试题(但是没有答案 自己百度 手动狗头!)
Vue面试题 生命周期函数面试题 1.什么是 vue 生命周期 2.vue生命周期的作用是什么 3.第一次页面加载会触发哪几个钩子 4.简述每个周期具体适合哪些场景 5.created和mounted的区别 6.vue获取数据在哪个周期函数 7.请详细说下你对vue生命周期的理解? vue路由面试题 1.mvvm 框架是什么? 2.vuerout
介绍 | Vue3中文文档
已经了解 Vue 2,只想了解 Vue 3 的新功能可以参阅 Vue.js 是什么Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与以及各种 结合使用时
vue项目无配置文件解决方案
问题在开发vue项目时,需要重新设置项目的启动端口,发现项目目录中并没有相关的配置文件【config目录】vue脚手架版本【通过vue version查询】:@vue/cli 4.5.13解决方案在vue项目根目录下创建vue.config.js配置文件vue.config.jsmodule.exports devServer: d
10分钟阅读一篇关于Vue
![file](https://oscimg.oschina.net/oscnet/up-4f8ccb1755bb73c4ffe6a7ba91253ddc.jpg "file") Vue-cli Vue脚手架的基本用法,vue脚手架用于快速生成vue项目基础架构: 地址: https://cli.vuejs.org/zh/ 使用方式,安装3.x版本
VUE AntDesign DatePicker设置默认显示当前日期
1:main.js中引入依赖 import Vue from "vue"; import { DatePicker } from 'ant-design-vue'; import 'ant-design-vue/dist/antd.css'; #设置中文 import moment from 'm
Vue CLI 3搭建vue+vuex 最全分析
一、介绍 ==== Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统。有三个组件: **CLI**:`@vue/cli` 全局安装的 npm 包,提供了终端里的vue命令(如:vue create 、vue serve 、vue ui 等命令) **CLI 服务**:`@vue/cli-service`是一个开发环境依赖。构建于 [we
Vue CLI 2.x搭建vue,目录最全分析
一、vue-cli介绍 =========== vue-cli是一个用于快速搭建vue项目的 脚手架。 二、vue-cli安装、更新 ============== 安装过nodeJs 、cnpm 后,全局安装vue-cli(以后其他项目可直接使用): cnpm install -g vue-cli 更新: cnpm update
Vue 兼容 ie9 的全面解决方案
前言 -- **背景情况** * vue - 2.5.11 * vue-cli 使用模板 `webpack-simple` * http请求:axios Vue 官方对于 ie 浏览器版本兼容情况的描述是 ie9+,即是 ie9 及更高的版本。经过测试,Vue 的核心框架 `vuejs` 本身,以及生态的官方核心插件(VueRouter、V
Vue+ElementUI 导航组件
创建导航页组件 ------- 在components目录下新建一个navigation目录,在Navi目录中新建一个名为Navi.vue的组件。至此我们的目录应该是如下图所示:  ![](https://img2018.cnblogs.com/blog/1213900/201909/1213900-20190911165808209-545381725
Vue+Spring Boot简单用户登录Demo
1 概述 ==== 前后端分离的一个简单用户登录`Demo`。 2 技术栈 ===== * `Vue` * `BootstrapVue` * `Kotlin` * `Spring Boot` * `MyBatis Plus` 3 前端 ==== 3.1 创建工程 -------- 使用`vue-cli`创建,没安装的可以先安装
vue & font
vue & font-awesome ------------------ // 使用npm安装依赖 npm install font-awesome@4.7.0 --save --verbose // 会在包管理文件(package.json)的依赖项(dependencies)中自动添加"font-awesome": "^4.7
vue echarts vue
1、git地址 [https://github.com/ecomfe/vue-echarts](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fecomfe%2Fvue-echarts) 2、使用 (1)安装 npm install vue-echa
vue 使用gojs绘制简单的流程图
在vue项目中需要展示工作流进度,可以使用的流程图插件很多 * flowchart.js  [http://adrai.github.io/flowchart.js/](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fadrai.github.io%2Fflowchart.js%2F%2