首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue源码解读(一)

vue源码解读(一)

作者头像
用户3258338
发布2019-07-19 17:37:14
1.1K0
发布2019-07-19 17:37:14
举报

装作无所畏惧的样子,之后会越来越勇敢

目录设计

src文件夹下:

  • compiler 编译相关,把模板解析成ast语法树,编译比较耗性能,推荐离线编译
  • core 核心代码。包括内置组件、全局API封装、Vue实例化、观察者、虚拟DOM、工具函数
  • platform vue.js是一个跨平台的MVVM框架,可以跑在web上,也可以配合weex跑在native客户端上。platform下有两个目录:web、weex。代表分别打包成运行在web和weex上的vue.js
  • server 所有服务端渲染的逻辑。这部分是跑在服务端的node.js。服务端渲染主要工作是把组件渲染成服务端的html字符串,将他们直接发送到浏览器,最后将静态标记“混合”为客户端上完全交互的应用程序。
  • sfc 将.vue文件内容解析为一个javascript的对象。
  • shared vue.js定义了一些方法。可以被浏览器端的vue.js和服务端的vue.js共享的工具方法。

源码构建

在项目根目录下package.json文件中script配置如下:

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

后两条是在第一条命令的基础上,添加了一些环境参数。最终都会执行scripts/build.js文件。我们一起看一下scripts/build:

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)

先从配置文件读取配置,在通过命令行参数对配置构建做过滤,这样就可以构建出不同用途的vue.js了。接下来一起看看scripts/config.js

const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.js'),
    format: 'cjs',
    banner
  },
  // Runtime+compiler CommonJS build (CommonJS)
  'web-full-cjs': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.js'),
    format: 'cjs',
    alias: { he: './entity-decoder' },
    banner
  }
  ...
 }

对于单个的配置,它是遵循Rollup的构建规则的。其中

  • entry属性表示构建的入口JS文件地址
  • dest 构建后的JS文件地址
  • format 表示构建的格式。cjs 表示构建出来的文件遵循commonJS、es表示构建出来的文件遵循ES Module规范。

以web-runtime-cjs的构建为例,它的入口是 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将传入的目录按照'/'分隔,取得第一个,在这个例子中取得'web',他的真实路径借助别名配置,从下面的alias文件可以看出,web对应的真实路径是path.resolve(__dirname, '../', 'src/platforms/web');

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')
}

也就是说 resolve('web/entry-runtime.js') == path.resolve(../src/platforms/web, p.slice(base.length + 1)) == path.resolve(../src/platforms/web, 'entry-runtime.js') 最终定位到src/platforms/web/entry-runtime.js作为入口文件。

根据如下配置文件,最后生成文件vue.runtime.common.js

dest: resolve('dist/vue.runtime.common.js')

Runtime Only VS Runtime + Compiler

  • Runtime Only 我们使用Runtime Only版本的vue时,通常需要webpack的vue-loader工具把.vue文件编译成Jacascript,因为在编译阶段做的,所以它只包含运行时的vue.js代码,so代码体积更轻量级。
  • Runtime + Compiler 运行时编译需要在客户端进行编译,所以需要带着编译器。显然这个编译过程是对性能有一定消耗的,所以通常推荐使用Runtime only的vue.js

以上我们知道了不同的作用和功能的vue.js他们对应的入口以及最终编译生成的JS文件。为了分析vue的编译过程,我们会着重分析runtime + compiler的vue.js

我们从入口文件开始吧,入口文件:src/platforms/web/entry-runtime-with-compiler.js

/* @flow */

import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'

import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

...

export default Vue

这个文件import vue 之后又export,import Vue from './runtime/index';继续追踪runtime/index.js文件

/* @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'

...

export default Vue

根据import Vue from 'core/index',追踪到文件core/index.js:

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)

...

export default Vue

这个文件有两处重要的:import Vue from './instance/index' 和initGlobalAPI(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'

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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

终于我们在这里找到了Vue的定义,实际是一个用Function实现的类。我们通过new Vue可以实例化它。

initGlobalAPI(Vue)

根据import { initGlobalAPI } from './global-api/index'

/* @flow */

import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

在这里给Vue扩展了一些全局方法。如Vue.nextTick ,Vue.set...

到现在vue的初始化过程基本即完成了。实际vue的本质是Function实现的Class,然后它的原型prototype以及它本身都扩展了一系列的方法和属性。具体的我们后面继续看。

愿我们有能力不向生活缴械投降---Lin

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 女程序员的日常 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录设计
  • 源码构建
  • Runtime Only VS Runtime + Compiler
    • initGlobalAPI(Vue)
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档