Vue2源码学习之路(一):理解打包入口的作用

1,540 阅读3分钟

前言

最近在学习Vue2的源码,打算将学到的东西记录下来并分享给大家,与一起学习与进步。

一、准备

  1. 克隆Vue2源码:git clone https://github.com/vuejs/vue.git
  2. 安装依赖:npm i
  3. 安装rollup,因为Vue是用rollup进行打包的。npm i -g rollup
  4. 修改package.json文件中的dev命令脚本,在其中添加一个sourcemap即可,这一步是为了在浏览器进行断点调试时,改完后如下:

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"

到这里,基本的准备工作就完成了。

二、目录结构

接下来,再来了解一下目录结构,这样才能知道从何下手以及找到对应的代码文件。

image.png 图2-1

图中标注了主要的目录名称,其中最主要的当属src源码这个目录,这个目录里面就是实现Vue的核心代码文件,如下图:

image.png

当然,这里没有将每个目录都介绍一遍,只是将主要的先标注下,其他的后续遇到了再说明。

三、找到打包入口

在看别人的源码时,我们一般都会先从打包入口文件开始,再一步一步往下看。读Vue源码亦是如此, 我们可以打开package.json,因为Vue打包是通过dev这个命令去执行的,而要打包时必然要去执行一个配置文件,那么从命令中可以看出,他是去执行的scripts/config.js这个配置文件,如下图:

image.png

紧接着,我们可以在上图中看到dev命令中的末尾表明了要执行哪个目标,由于Vue会根据不同的场景打包出不同的版本,所以会设置众多环境变量。而dev要打包的是针对web的开发完整版(包含运行时和编译器),即web-full-dev

我们可以复制这个文件的路径,在vscode中使用ctrl+p快捷键,进行文件查找,然后打开,搜索web-full-dev,可以看到如下图中的代码:

image.png

图中表明了web-full-dev这个版本对应的打包入口文件路径,打包路径,对应的环境变量等信息,而我们要找的文件就是对应的web/entry-runtime-with-compiler.js,而直接这么去找,我们发现是找不到的,这是因为Vue他使用了别名,省掉了路径的前缀,通过图中的resovle方法,我们可以找到如下图对应的代码:

image.png

通过这个方法,他就能获取到对应的文件,打开alias这个别名文件,我们就能看到下图中的代码:

image.png

由上操作我们可以得到打包入口文件完整的相对路径就是src\platforms\web\entry-runtime-with-compiler.js

四、理解打包入口的作用

打开文件,我们定位到Vue.prototype.$mount方法这里来,我将多余的代码进行了删减,对于比较重要的地方进行了相关注释说明,大家可以对照源码文件的对应代码,这样能够看的更加清晰明了。

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // $options即new Vue中传入的对象
  const options = this.$options

  /*
    下列代码块的目的是为了处理编译这件事,
    我们知道new Vue中传入的对象中可以是render函数,template标签模板,el选项,进行渲染,
    如:
    const app = new Vue({
        el: '#app',
        templete: '#app',
        templete: '<div>templete 你好</div>',
        render() {
             return h('div', 'render 你好')
        },
        data: {
            str: '你好'
        }
    })
    根据下列代码的if先后顺序可得,他的处理顺序为:render -> template -> el选项
    只要满足了其中一个渲染方式,后面的都不再执行
  */
  
  if (!options.render) {
    let template = options.template
    // 如果$options中有template
    if (template) {
      if (typeof template === 'string') {
        // 如果template属性值是以#开头的,则表示这是一个id选择器
        if (template.charAt(0) === '#') {
          // 定位到idToTemplate方法,可知Vue是将其对应的元素的innerHTML进行了渲染
          template = idToTemplate(template)
        }
      } else if (template.nodeType) {
        // 如果template的属性值是node元素,则直接渲染其innerHTML
        template = template.innerHTML
      } else {
        return this
      }
    } else if (el) {
      // 如果传入的是el选择,则通过getOuterHTML方法,将内容渲染到对应的元素内
      template = getOuterHTML(el)
    }

    // 如果存在templete,则编译它,获取render函数
    if (template) {
      // 编译过程:templete -> render
      const {
        render,
        staticRenderFns
      } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)

      // 将获取到的渲染函数赋值给选项,将来使用
      options.render = render

      options.staticRenderFns = staticRenderFns
    }
  }

  // 执行默认的挂载操作
  return mount.call(this, el, hydrating)
}

看完上述代码与注释说明,大概就清楚了它的作用,接下来,我们看下以下代码示例,如果用户同时设置了el,template,render函数,那么它将会怎么执行,以及它判断的先后顺序

const app = new Vue({
    el: '#app',
    templete: '#app',
    templete: '<div>templete 你好</div>',
    render() {
         return h('div', 'render 你好')
    },
    data: {
        str: '你好'
    }
})

通过刚才对打包入口文件中的理解,我们可以知道,它会先判断$option配置项中有没有render函数,如果没有,就去判断有没有template,如果没有,则去判断有没有el选项。

即:render -> template -> el选项

只要满足上述中的一个条件,就直接进行渲染,后续条件将不再判断与执行。

五、总结

入口文件:

  • 路径:src\platforms\web\entry-runtime-with-compiler.js
  • 作用:处理render函数 / template / el选项,处理编译这件事

以上就是阅读Vue源码的相关准备,以及我对打包入口文件的学习与理解,如有不对,请多指正,谢谢大家。