美文网首页
vue源码分析(web平台相关)-01

vue源码分析(web平台相关)-01

作者: 爱吃豆包 | 来源:发表于2022-02-13 19:48 被阅读0次

    src/platforms/web 这个是 vue 针对web 平台

    拉取Vue源码

    git clone https://github.com/vuejs/vue.git

    版本为 Vue 2.6.10

    在根目录下执行:

    // 安装依赖
    npm i
    

    安装rollup:

    因为在 package.json 文件里面发现有这个 rollup

     npm i -g rollup
    

    修改dev脚本,添加sourcemap,在 package.jsonscripts 节点下
    "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",

    sourcemap : source-map 的基本原理是,在编译处理的过程中,在生成产物代码的同时生成产物代码中被转换的部分与源代码中相应部分的映射关系表。
    也就是说,在本地调试的时候,保证运行的代码和源码里面,位置是对应的。
    我们就可以通过 Chrome 控制台中的"Enable Javascript source map"来实现调试时的显示与定位源代码功能

    引用网络上的一张图描述

    目录结构:
    dist 发布目录
    examples 范例
    packages Vue核心之外的一些独立库(比如:ssr-serve 服务端渲染,template 模板, weex等)
    scripts 构建脚本
    src 源码目录
    types 类型申明

    源码目录结构: (src下的目录)
    compiler 编译器相关
    platforms 平台相关(web 平台【这次主要看这个】,weex 平台,ssr 平台)
    core 核心代码库
    -- components 通用组件(但是只有 keep-alive 组件)
    -- global-api 全局Api
    -- instance 构造函数
    -- observer 响应式相关
    -- vdom 虚拟dom

    入口文件 entry-runtime-with-compiler.js

    package.json

    通过运行脚本 scripts\config.js 找到这个文件

    // 运行脚本
    
      // 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
      },
    

    通过上面脚本 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)
      }
    }
    

    通过上面脚本 aliases 这个地方找到所有的前缀映射

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

    那么完整入口文件 entry-runtime-with-compiler.js位置就在 src/platforms/web/entry-runtime-with-compiler.js

    测试文件 01.html

    <!DOCTYPE html>
    <html lang="en">
        <script src="../../dist/vue.min.js"></script>
        <body>
            <div id='app'>{{ num }}</div>
        </body>
        <script>
            // 1.render 和 template 和 el 优先级?
            // 2.el 和 $mount 
            new Vue({
                el: '#app',
                // template: '<div>6666</div>',
                // render: h => h('div', '777'),
                data() {
                    return {
                        num: 5
                    }
                }
            })// .$mount('#app')
        </script>
    </html>
    

    通过 src\platforms\web\entry-runtime-with-compiler.js 入口文件

    粘贴的源码里面,我添加了注释,并删除了无用代码

    /* @flow */
    // 入口文件。覆盖 $mount ,执行模板解析和编译工作
    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'
    
    const idToTemplate = cached(id => {
      const el = query(id)
      return el && el.innerHTML
    })
    
    // 保存一份原来的 $mount 函数
    const mount = Vue.prototype.$mount
    // 覆盖(重写) Vue 上的 $mount 函数
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && query(el)
    
      // 解析option
      const options = this.$options
      // resolve template/el and convert to render function
      if (!options.render) {
        let template = options.template
        // 模板解析
        if (template) {
          if (typeof template === 'string') {
            if (template.charAt(0) === '#') {
              template = idToTemplate(template)
            }
          } else if (template.nodeType) {
            template = template.innerHTML
          } else {
            if (process.env.NODE_ENV !== 'production') {
              warn('invalid template option:' + template, this)
            }
            return this
          }
        } else if (el) {
          template = getOuterHTML(el)
        }
        // r如果模板存在,执行编译
        if (template) {
    
          // 编译得到渲染函数
          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
    
        }
      }
    
      // 执行挂载
      // 父级的 mount 函数
      return mount.call(this, el, hydrating)
    }
    
    /**
     * Get outerHTML of elements, taking care
     * of SVG elements in IE as well.
     */
    function getOuterHTML (el: Element): string {
      if (el.outerHTML) {
        return el.outerHTML
      } else {
        const container = document.createElement('div')
        container.appendChild(el.cloneNode(true))
        return container.innerHTML
      }
    }
    
    Vue.compile = compileToFunctions
    
    export default Vue
    
    

    发现在最开始的位置const mount = Vue.prototype.$mount,做了一次保存一份Vue原有 $mount 函数,然后紧接着又重写了 $mount 上的函数。

    为什么重写了 $mount 函数?

    发现重写的$mount 函数里面,做了一个事情,就是用 render 还是用 template 或者 el 进行渲染。

    但是最后还是使用原本Vue的 $mount 函数进行挂载(因为之前保存了一份 Vue 原有 $mount 函数)

    // 执行挂载
      // 父级的 mount 函数
      return mount.call(this, el, hydrating)
    

    发现这段源码里面

    // r如果模板存在,执行编译
        if (template) {
    
          // 编译得到渲染函数
          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
    
        }
    

    可以看出Vue最终渲染的属性始终是 render (位置:options.render = render)。虽然平常使用 template 方式,但是最终都会赋值给 render 进行渲染。

    结论:

    • render 和 template 和 el 优先级?
      通过源码的顺序来看,如果 render 存在那么就直接执行 render,如果没有就执行 template,如果没有就执行 el (是通过 getOuterHTML函数它的获取HTML)

    所有的,最终都是渲染 HTML

    render ==> template ==> el

    • el 和 $mount 作用
      都是指定挂载带一个节点上

    在看看 src\platforms\web\runtime\index.js

    这个文件是 web 平台的起始 js 文件

    src\platforms\web\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'
    
    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)
    
    // 指定补丁方法:传入虚拟donm转换为真是dom
    // install platform patch function
    Vue.prototype.__patch__ = inBrowser ? patch : noop
    
    // 实现$mount
    // public mount method
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && inBrowser ? query(el) : undefined
      // 初始化,将首次渲染结果替换el
      return mountComponent(this, el, hydrating)
    }
    
    export default Vue
    
    

    虚拟dom后面在分析

    看到这个代码的最下面,发现是做初始化,将首次渲染结果替换为 el 节点
    也重新定义了 $mount 函数

    上面的分析是 Vue 关于 web 平台相关的 src\platforms\web\runtime\index.js

    现在是看 Vue 核心 code 相关!

    相关文章

      网友评论

          本文标题:vue源码分析(web平台相关)-01

          本文链接:https://www.haomeiwen.com/subject/mptqlrtx.html