Bootstrap

vue-core 3.3alpha版本源码解析1--创建应用

序言

       从个人角度而已,了解学习优质开源项目的源码,可以学习其设计思想和提升自身代码质量。所以本次从vue-core 3.3alpha入手进行源码解析。

      对于我而言,项目设计者和学习者是站在不同角度去看待源码,设计者基于功能、模块的设计思想去一步步实现,而学习者无法从设计思想上宏观看待源码,所以只能基于实际使用逆推其设计思想!

正文

按照官网所述

快速使用vue框架,首先调用 createApp创建app,并进行mount挂载到指定元素上,所以我们先从createApp开始解析。

解析前的准备

1.在开始解析之前,需要从github上clone项目

项目链接:GitHub - vuejs/core: 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.2.克隆完成后,因为vue core是使用pnpm管理的单一代码库(monorepos),所以需要使用pnpm install进行依赖包安装

3.依赖包加载后,可以参照package.json中的配置

同时启动pnpm dev 和pnpm serve服务

dev服务:实时更新源码修改,便于调试

serve服务:启动本地服务器访问文件及页面

可以访问packages/vue/examples目录下页面进行调试,及新增调试页面

项目结构简介

正常启动调试界面,正式解析之前,需要对项目整体结构有所了解,便于解析源码时,理解其相互依赖关系。

本次解析以浏览器渲染为主,所以使用

1.compiler模块:解析器模块

  • compiler-core
  • compiler-dom

2.reactivity模块:响应式模块

  • reactivity

3.runtime模块:运行时模块

  • runtime-core
  • runtime-dom

4.shared模块:公共函数模块

  • shared

5.vue模块:集成模块

  • vue

tip:packages/vue/examples目录下所使用的vue包皆是使用vue集成模块打包的库

源码解析

基于上述,可以从vue模块开始定位createApp函数是由runtime-dom/src的index.ts文件所导出

export const createApp = ((...args) => {
  //ensureRenderer函数是懒汉单例模式,有renderer直接返回,无renderer则调用createRenderer创建并调用返回的createApp方法创建app对象
  const app = ensureRenderer().createApp(...args)

  if (__DEV__) {
    //开发模式下开启本地组件标签名称检查
    injectNativeTagCheck(app)
    //开发模式下开启编译器配置检查
    injectCompilerOptionsCheck(app)
  }
  
  //缓存mount方法
  const { mount } = app
  //重写mount方法
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    //normalizeContainer基于containerOrSelector类型进行处理,字符串类型使用document.querySelector选择器获取元素,其他类型则直接返回
    const container = normalizeContainer(containerOrSelector)
    //没有container容器情况下则直接返回  不进行挂载
    if (!container) return

    //需要判断__COMPAT__条件的情况,主要是针对低版本兼容,先跳过
    const component = app._component
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML
     
      if (__COMPAT__ && __DEV__) {
        for (let i = 0; i < container.attributes.length; i++) {
          const attr = container.attributes[i]
          if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
            compatUtils.warnDeprecation(
              DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
              null
            )
            break
          }
        }
      }
    }

    // 挂载前清空容器
    container.innerHTML = ''
    //调用缓存mount方法挂载
    const proxy = mount(container, false, container instanceof SVGElement)
    if (container instanceof Element) {
      //container为元素情况下,移除v-cloak属性  添加data-v-app属性
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

先从宏观角度看createApp函数,从上述代码可以看出该createApp是一个装饰器,用来创建app实例,并装饰扩展原app实例的mount方法。接下来我们再看ensureRenderer函数

//nodeOps包含dom元素的操作方法
import { nodeOps } from './nodeOps'
//patchProp主要是对元素的属性处理方法
import { patchProp } from './patchProp'

//extend是Object.assign的声明,主要用来合并patchProp和nodeOps
const rendererOptions =  extend({ patchProp }, nodeOps)

//懒汉式单例,用来创建renderer,并配置rendererOptions
function ensureRenderer() {
  return (
    renderer ||
    (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  )
}

ensureRenderer除了是懒汉式单例外,最主要的功能是注入nodeOps元素操作方法和patchProp元素属性的处理方法(nodeOps,patchProp内容比较常规,这里不详细叙述)。

项目中第一次使用createApp函数,renderer是绝对不存在的,所以现在详细说明下createRenderer函数做了什么

//createRenderer函数在runtime-core/renderer.ts文件下
//createRenderer函数是对baseCreateRenderer函数的再封装,明确类型定义,与createHydrationRenderer函数区分开来
export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}

从上述代码可以看出createRenderer是对baseCreateRenderer再封装,因此接下来便来详细解析一下baseCreateRenderer


function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {
  // 编译时特性标志检查,可跳过
  if (__ESM_BUNDLER__ && !__TEST__) {
    initFeatureFlags()
  }

  //获取全局对象,用户端为浏览器时,target指向的是window
  const target = getGlobalThis()

  //设置全局对象的__VUE__属性为true
  target.__VUE__ = true
  //开发模式下的配置,可跳过
  if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
    setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target)
  }

  //对createRenderer传入的nodeOps,patchProp方法重命名
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    insertStaticContent: hostInsertStaticContent
  } = options

 //此处内部函数定义较多,具体函数在调用时再解析

  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

baseCreateRenderer内部主要是对外部配置进行重命名,规范命名空间,同时定义了很多内部方法(虚拟节点的处理等,也就是备受关注的diff算法函数),baseCreateRenderer返回一个对象,包含了render、hydrate、createApp方法,没错,此处的createApp即ensureRenderer().createApp(...args)所调用的createApp方法,so...,打开runtime-core/apiCreateApp.ts看看createAppAPI主要干了什么吧

export function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    //如果传入的根组件不是函数,则进行属性拷贝
    if (!isFunction(rootComponent)) {
      rootComponent = extend({}, rootComponent)
    }

    //如果根组件的属性不是一个对象的话,进行置空,在开发模式下警告提示
    if (rootProps != null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }

    // 创建app上下文环境
    const context = createAppContext()
    // 创建一个Set集合用来缓存安装的插件
    const installedPlugins = new Set()
    // 用来判断app是否挂载
    let isMounted = false

   
    const app: App = (context.app = {//上下文context与app相互绑定
      // 自增型唯一uid
      _uid: uid++,
      // 根组件
      _component: rootComponent as ConcreteComponent,
      // 根组件属性
      _props: rootProps,
      // 根组件容器
      _container: null,
      // 上下文
      _context: context,
      // 实例
      _instance: null,
      // vue版本
      version,
      //app的config同context的config
      get config() {
        return context.config
      },
      //无法设置congfig,开发模式下警告
      set config(v) {
        if (__DEV__) {
          warn(
            `app.config cannot be replaced. Modify individual options instead.`
          )
        }
      },

      //注册插件,缓存到installedPlugins中
      use(plugin: Plugin, ...options: any[]) {
        if (installedPlugins.has(plugin)) {//如果插件已注册,且在开发模式下,警告提示
          __DEV__ && warn(`Plugin has already been applied to target app.`)
        } else if (plugin && isFunction(plugin.install)) {//如果传入的插件有install方法,则注册插件且调用install方法
          installedPlugins.add(plugin)
          plugin.install(app, ...options)
        } else if (isFunction(plugin)) {//如果传入的插件本身就是一个函数,则注册插件和直接调用
          installedPlugins.add(plugin)
          plugin(app, ...options)
        } else if (__DEV__) {//其他情况下,在开发模式下警告提示
          warn(
            `A plugin must either be a function or an object with an "install" ` +
              `function.`
          )
        }
        return app
      },

      //注册mixin到context的mixins集中
      mixin(mixin: ComponentOptions) {
       
        if (__FEATURE_OPTIONS_API__) {
          if (!context.mixins.includes(mixin)) {//如果上下文中的mixins集中不包含参数mixin则注册
            context.mixins.push(mixin)
          } else if (__DEV__) {//否则在开发模式下警告提示
            warn(
              'Mixin has already been applied to target app' +
                (mixin.name ? `: ${mixin.name}` : '')
            )
          }
        } else if (__DEV__) {
          warn('Mixins are only available in builds supporting Options API')
        }
        return app
      },
      //注册组件到context的components集中
      component(name: string, component?: Component): any {
        if (__DEV__) {//开发模式下,验证名称是否vue的关键字slot 和component还有上下文配置的是否是原生标签名
          validateComponentName(name, context.config)
        }
        if (!component) {//无component参数情况下,则根据name返回component
          return context.components[name]
        }
        if (__DEV__ && context.components[name]) {//开发模式下,会提示已注册的组件
          warn(`Component "${name}" has already been registered in target app.`)
        }
        //将组件注册到上下文的组件集
        context.components[name] = component
        // 返回app
        return app
      },
      //注册组件到context的components集中
      directive(name: string, directive?: Directive) {
        if (__DEV__) {//开发模式下  验证名称是否使用了vue的保留关键字
          validateDirectiveName(name)
        }

        if (!directive) {//无参数directive情况下,根据name返回directive
          return context.directives[name] as any
        }
        if (__DEV__ && context.directives[name]) {//开发模式下,提示已组成的指令
          warn(`Directive "${name}" has already been registered in target app.`)
        }
        //将指令注册到上下文的指令集
        context.directives[name] = directive
         // 返回app
        return app
      },

      mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
        if (!isMounted) {//未挂载情况下开始挂载
          
          if (__DEV__ && (rootContainer as any).__vue_app__) {
            warn(
              `There is already an app instance mounted on the host container.\n` +
                ` If you want to mount another app on the same host container,` +
                ` you need to unmount the previous app by calling \`app.unmount()\` first.`
            )
          }
          //创建根虚拟节点
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )

          //根虚拟节点的appContext指向上下文
          vnode.appContext = context

          // HMR root reload
          if (__DEV__) {//开发模式下,定义reload方法,render克隆的vnode
            context.reload = () => {
              render(cloneVNode(vnode), rootContainer, isSVG)
            }
          }

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            //调用baseCreateRenderer中render函数进行渲染
            render(vnode, rootContainer, isSVG)
          }
          //渲染后,状态变成已挂载
          isMounted = true
          //app的_container指向根节点容器
          app._container = rootContainer
          //根节点容器的__vue_app__属性指向app
          ;(rootContainer as any).__vue_app__ = app

          if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            app._instance = vnode.component
            devtoolsInitApp(app, version)
          }
         
          return getExposeProxy(vnode.component!) || vnode.component!.proxy
        } else if (__DEV__) {
          warn(
            `App has already been mounted.\n` +
              `If you want to remount the same app, move your app creation logic ` +
              `into a factory function and create fresh app instances for each ` +
              `mount - e.g. \`const createMyApp = () => createApp(App)\``
          )
        }
      },
      //卸载
      unmount() {
        if (isMounted) {
          render(null, app._container)
          if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            app._instance = null
            devtoolsUnmountApp(app)
          }
          delete app._container.__vue_app__
        } else if (__DEV__) {
          warn(`Cannot unmount an app that is not mounted.`)
        }
      },
      //注入全局属性到context的provides中
      provide(key, value) {
        if (__DEV__ && (key as string | symbol) in context.provides) {
          warn(
            `App already provides property with key "${String(key)}". ` +
              `It will be overwritten with the new value.`
          )
        }

        context.provides[key as string | symbol] = value

        return app
      }
    })

    if (__COMPAT__) {
      installAppCompatProperties(app, context, render)
    }

    return app
  }
}
export function createAppContext(): AppContext {
  return {
    app: null as any,//与全局app相互绑定
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: {}
    },
    mixins: [],//全局mixin集
    components: {},//全局组件集
    directives: {},//全局指令集
    provides: Object.create(null),//提供依赖集
    optionsCache: new WeakMap(),//配置缓存
    propsCache: new WeakMap(),//属性缓存
    emitsCache: new WeakMap()//事件缓存
  }
}

参考上面2段注释内容,大家可以明白这里返回的createApp函数,主要是创建全局上下文context对象和app对象并且相互绑定关联,并在app对象中定义use、mixin、component、directive、mount、unmount、provide等方法

截止此处,大家应该已经大致了解了创建应用的整体流程

总结

创建应用createApp函数,主要作用是

  1. 导入外部定义的dom节点操作方法和dom元素属性操作方法
  2. 创建app对象和context对象
  3. 通过app对象定义的方法,配置全局组件、mixin、指令等和挂载、卸载功能

下一篇:vue-core 3.3alpha版本源码解析2--挂载应用(mount)_zhang78529的博客-CSDN博客

;