Vue 3.0 Pre Alpha 源码 packages

发布时间:2022-04-15浏览次数:0

支持注册ChatGPT Plus的OneKey虚拟卡
绑定Apple Pay、Google Pay、支付宝和微信支付进行日常消费

注册和了解更多 ->

silver
packages 说明
compiler-core 编译器核心,包括将模板转成 AST、生成 JS 逻辑等功能
compiler-dom 编译器 DOM 补充部分,与浏览器相关的模板编译和逻辑处理等功能
reactivity 数据相关的系统,包括数据监控(Proxy 代理)、计算和响应等基础能力
runtime-core 运行时核心,包括 Vue 组件系统、Vue 的各种 API 实现、虚拟 DOM 相关,同时暴露了更多的底层能力如自定义渲染器
runtime-dom 运行时 DOM 相关补充,包括处理原生 DOM API、DOM 事件和 DOM 属性等
runtime-test 用于测试的运行时,该运行时基于虚拟 DOM 的 JS 对象,可以用在所有 JS 环境里
server-renderer SSR 服务端渲染
shared 包含一些通用的配置和方法
template-explorer 模板编译输出,方便调试
vue 用于构建完整版本的 Vue,使用了编译器和运行时

我们看到完整版本的 Vue 里引用了 compiler-dom 和 runtime-dom,但这两者其实还依赖了 compiler-core、runtime-core、reactivity,也就是最终完整的的 Vue 3.0 Pre Alpha 版本了。

#@vue/complier-core

翻开源码,我们其实能看到和 Vue2.x 相比一个很大的改变:整体的功能代码进行了解耦,每个功能模块都清晰独立地抽离出来了。还可以开放更多的底层能力给到开发者。

Vue 3.0 整体使用 Function-based API,同时使用 Typescript 进行开发。除了可读性、维护性上的提升,之前提到的一些优势也都体现出来了:模块化解耦获得了更灵活的逻辑复用能力、Tree-shaking、Source Map 支持更加友好,开发者也获得更好的 TypeScript 类型推导支持。

以 complier-core 中 index.ts 中的代码为例:

// @vue/compiler-core/src/index.ts
// 函数式 API 可便捷地对逻辑进行解耦和复用
// 能看到 complier-core 中功能主要包括模板解析、转成AST、生成JS逻辑等一些功能
import { parse, ParserOptions } from "./parse";
import { transform, TransformOptions } from "./transform";
import { generate, CodegenOptions, CodegenResult } from "./codegen";
import { RootNode } from "./ast";
import { isString } from "@vue/shared";
import { transformIf } from "./transforms/vIf";
import { transformFor } from "./transforms/vFor";
import { transformExpression } from "./transforms/transformExpression";
import { transformSlotOutlet } from "./transforms/transformSlotOutlet";
import { transformElement } from "./transforms/transformElement";
import { transformOn } from "./transforms/vOn";
import { transformBind } from "./transforms/vBind";
import { defaultOnError, createCompilerError, ErrorCodes } from "./errors";
import { trackSlotScopes, trackVForSlotScopes } from "./transforms/vSlot";
import { optimizeText } from "./transforms/optimizeText";
import { transformOnce } from "./transforms/vOnce";
import { transformModel } from "./transforms/vModel";

export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions;

// 我们将其命名为“baseCompile”,以便更高级别的编译器(例如 @vue/compiler-dom)
// 可以在重新导出其他所有内容的同时导出“compile”
export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  // 省略一部分

  // 这里先解析模板,然后生成AST对象
  const ast = isString(template) ? parse(template, options) : template;

  const prefixIdentifiers =
    !__BROWSER__ &&
    (options.prefixIdentifiers === true || options.mode === "module");

  // 对每个AST对象节点进行处理
  transform(ast, {
    ...options,
    prefixIdentifiers,
    nodeTransforms: [
      transformIf,
      transformFor,
      ...(prefixIdentifiers
        ? [
            // 此处顺序很重要
            trackVForSlotScopes,
            transformExpression
          ]
        : []),
      trackSlotScopes,
      transformSlotOutlet,
      transformElement,
      optimizeText,
      // 开放给使用者添加的一些处理逻辑
      // 例如 compiler-dom 中新增了对 DOM 的一些处理
      ...(options.nodeTransforms || [])
    ],
    directiveTransforms: {
      on: transformOn,
      bind: transformBind,
      once: transformOnce,
      model: transformModel,
      // 开放给使用者添加的一些处理逻辑
      ...(options.directiveTransforms || {})
    }
  });

  // 将AST对象生成VNode、JS逻辑等
  return generate(ast, {
    ...options, // 开放给使用者添加的一些转换逻辑
    prefixIdentifiers
  });
}

从上面的代码,我们能看到,模板解析生成 AST 之后,还会根据 AST 对象来进行转换。而 Vue 3.0 中提到的 Virtual DOM 优化的内容也在这里进行,通过模版静态分析生成更优化的 Virtual DOM 渲染函数,将模版切分为 block(if, for, slot),来提升 Virtual DOM 的性能。例如对某些语法进行“block”分块,然后整个应用以树状将每个“block”串起来,这样在更新、数据对比监控的时候,都可以从上而下地进行,也可以将变更控制在更小的范围,从而提升性能。

其他的功能设计也跟这里比较相似,通过函数式 API 来解耦基础能力,同时留空一些可补充的能力。功能拆解后,不管是组装的灵活性,还是可测试、易拓展、Tree-shaking 等,都有质的提升。

#@vue/complier-dom

同样地,要了解这个模块主要是用来做什么,我们也需要首先打开 index.ts 文件:

// @vue/compiler-dom/src/index.ts
// 依赖了@vue/compiler-core
import {
  baseCompile,
  CompilerOptions,
  CodegenResult
} from "@vue/compiler-core";
// 与 DOM 相关的一些解析和转换
import { parserOptionsMinimal } from "./parserOptionsMinimal";
import { parserOptionsStandard } from "./parserOptionsStandard";
import { transformStyle } from "./transforms/transformStyle";
import { transformCloak } from "./transforms/vCloak";
import { transformVHtml } from "./transforms/vHtml";
import { transformVText } from "./transforms/vText";
import { transformModel } from "./transforms/vModel";

// 导出补充了DOM相关的“compile”模块
export function compile(
  template: string,
  options: CompilerOptions = {}
): CodegenResult {
  // 基于 @vue/compiler-core 中的 baseCompile 进行补充拓展
  // 从而导出一个完整的 Vue 3.0 版本的编译器
  return baseCompile(template, {
    ...options,
    ...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
    nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
    directiveTransforms: {
      cloak: transformCloak,
      html: transformVHtml,
      text: transformVText,
      model: transformModel, // 此处重写了 compiler-core 的 vModel 转换
      ...(options.directiveTransforms || {})
    }
  });
}
export * from "@vue/compiler-core";

显然,compiler-dom 模块是基于 compiler-core 模块补齐 DOM 相关能力,我们可以理解为运行在浏览器中需要的一些能力补齐。这里我们能看到,complier-dom 中重写了 compiler-core 的 vModel 转换,因为在 Vue 中v-model只能用在表单或是自定义表单的组件,我们来看一下实现:

// @vue/compiler-dom/src/transforms/vModel.ts
export const transformModel: DirectiveTransform = (dir, node, context) => {
  const res = baseTransform(dir, node, context);
  const { tag, tagType } = node;
  if (tagType === ElementTypes.ELEMENT) {
    // 其他省略

    // 显然,这里对原生的表单组件进行处理
    if (tag === "input" || tag === "textarea" || tag === "select") {
      let directiveToUse = V_MODEL_TEXT;
      if (tag === "input") {
        const type = findProp(node, `type`);
        if (type) {
          if (type.type === NodeTypes.DIRECTIVE) {
            // :type="foo"
            directiveToUse = V_MODEL_DYNAMIC;
          } else if (type.value) {
            switch (type.value.content) {
              case "radio":
                directiveToUse = V_MODEL_RADIO;
                break;
              case "checkbox":
                directiveToUse = V_MODEL_CHECKBOX;
                break;
            }
          }
        }
      } else if (tag === "select") {
        directiveToUse = V_MODEL_SELECT;
      } // 通过 needRuntime 返回辅助符号 // 导入将替换 resovleDirective 调用
      // 注入运行时指令
      res.needRuntime = context.helper(directiveToUse);
    } else {
      // 报错,此处省略
    }
  }
  return res;
};

其实在 Vue 中,除了原生表单组件 input、textarea、select 这些,还有一些自定义组件也需要进行处理的,这里也需要补齐对自定义组件的处理,这部分我们会在 runtime-dom 运行时 DOM 补充的部分能看到。

#@vue/reactivity

该模块实现了一个数据管理模块,它可以单独使用,也可以与框架配合使用。我们能看到 Vue 3.0 的数据监控的优化亮点都包含在这个模块了,这个模块主要包括了:

  • baseHandlers/collectionHandlers: 处理器,用作 Proxy 的处理器
  • computed: 计算属性部分
  • effect: 一些辅助能力,例如跟踪依赖项、触发一些检测、响应等等
  • reactive: 响应式数据,基于 Proxy 代理实现
  • ref: 数据容器,在 Vue 中是指向组件实例的引用

我们可以看看创建响应式数据的部分:

// @vue/reactivity/src/reactive.ts
// 创建响应式数据
function createReactiveObject(
  target: any,
  toProxy: WeakMap<any, any>,
  toRaw: WeakMap<any, any>,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`);
    }
    return target;
  }
  // 目标已经有相应的代理
  let observed = toProxy.get(target);
  if (observed !== void 0) {
    return observed;
  }
  // 目标已经是一个代理
  if (toRaw.has(target)) {
    return target;
  }
  // 只有在白名单里的值可被观察
  if (!canObserve(target)) {
    return target;
  }
  // 获取集合或是非集合类型的处理器
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers;
  // 创建代理,并记录源对象和代理对象的关系
  observed = new Proxy(target, handlers);
  toProxy.set(target, observed);
  toRaw.set(observed, target);
  // 存储{target-> key-> dep}关系
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map());
  }
  return observed;
}

我们已经可以看到,通过 Proxy 的方式创建一个响应式数据,并将数据之间的依赖关系、源数据与代理对象之间的关系都记录下来,当我们更新数据的时候,就可以根据依赖进行范围内的检查更新:

// @vue/reactivity/src/baseHandlers.ts
// 这是响应式数据 set 的时候代理处理函数
function set(
  target: any,
  key: string | symbol,
  value: any,
  receiver: any
): boolean {
  // 获取代理对象数据
  value = toRaw(value);
  // 获取原对象数据
  const hadKey = hasOwn(target, key);
  const oldValue = target[key];
  if (isRef(oldValue) && !isRef(value)) {
    oldValue.value = value;
    return true;
  }
  // 采用 Reflect.set 方法将值赋值给对象的属性
  // 确保完成原有的行为,然后再部署额外的功能
  const result = Reflect.set(target, key, value, receiver);
  // 如果目标是原始原型链中的某个对象,请勿触发
  // 可防止循环触发
  if (target === toRaw(receiver)) {
    // 计算数据变更方式(更新或新增),并进行变更触发
    // trigger 会根据依赖关系,对 computed 和需要连带更新的数据进行更新
    if (__DEV__) {
      const extraInfo = { oldValue, newValue: value };
      if (!hadKey) {
        trigger(target, OperationTypes.ADD, key, extraInfo);
      } else if (value !== oldValue) {
        trigger(target, OperationTypes.SET, key, extraInfo);
      }
    } else {
      if (!hadKey) {
        trigger(target, OperationTypes.ADD, key);
      } else if (value !== oldValue) {
        trigger(target, OperationTypes.SET, key);
      }
    }
  }
  return result;
}

我们能看到,使用了 Proxy 代理+依赖关系链的方式来进行数据检测和变更更新,同时结合运行时 DOM 相关补充,就可以完成对页面的局部刷新。

#@vue/runtime-core

运行时核心模块主要包括了组件系统和 Vue 的 API 实现、虚拟 DOM 相关的功能,我们同样地可以从 index.ts 文件中看到:

// @vue/runtime-core/src/index.ts
// 公共API部分------------
export { createComponent } from "./apiCreateComponent";
export { nextTick } from "./scheduler";
export * from "./apiReactivity";
export * from "./apiWatch";
export * from "./apiLifecycle";
export * from "./apiInject";

// 高级API部分--------------
// 需要进行原始渲染功能的用户可以使用
export { h } from "./h";
// VNode(虚拟DOM)相关能力
export {
  createVNode,
  cloneVNode,
  mergeProps,
  openBlock,
  createBlock
} from "./vnode";
// VNode类型的symbols
export { Text, Comment, Fragment, Portal, Suspense } from "./vnode";
// VNode的标志
export { PublicShapeFlags as ShapeFlags } from "./shapeFlags";
export { PublicPatchFlags as PatchFlags } from "@vue/shared";

// 可用于高级插件
export { getCurrentInstance } from "./component";

// 可用于自定义渲染
export { createRenderer } from "./createRenderer";
export { warn } from "./warning";
export {
  handleError,
  callWithErrorHandling,
  callWithAsyncErrorHandling
} from "./errorHandling";

// 内部API,用于编译器生成的代码
// 应该与'@vue/compiler-core/src/runtimeConstants.ts'同步
export { applyDirectives } from "./directives";
export { resolveComponent, resolveDirective } from "./helpers/resolveAssets";
export { renderList } from "./helpers/renderList";
export { toString } from "./helpers/toString";
export { toHandlers } from "./helpers/toHandlers";
export { renderSlot } from "./helpers/renderSlot";
export { createSlots } from "./helpers/createSlots";
export { capitalize, camelize } from "@vue/shared";

// 内部API,用于与运行时编译器集成
export { registerRuntimeCompiler } from "./component";

我们能看到,runtime-core 暴露了一些底层 API 能力,例如自定义渲染等,开发者可以基于这些基础能力之上,补充平台相关能力,打造一个完整的运行时。像@vue/runtime-dom 就时补充了 DOM 相关的运行时能力,从而构成完整的 Vue 运行时功能模块。

#@vue/runtime-dom

runtime-dom 模块是基于 runtime-core 开发的浏览器上的运行时,主要补齐了浏览器环境下 DOM 节点和节点属性的一些渲染和更新能力。

怎么补齐呢?我们看一个例子:

// @vue/runtime-dom/src/nodeOps.ts
// 对一些 VNode 操作,增加更新到页面的 DOM 节点操作

// 获取 document 对象
const doc = document;

export const nodeOps = {
  insert: (child: Node, parent: Node, anchor?: Node) => {
    if (anchor != null) {
      parent.insertBefore(child, anchor);
    } else {
      parent.appendChild(child);
    }
  },

  remove: (child: Node) => {
    const parent = child.parentNode;
    if (parent != null) {
      parent.removeChild(child);
    }
  },

  createElement: (tag: string, isSVG?: boolean): Element =>
    isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag),

  querySelector: (selector: string): Element | null =>
    doc.querySelector(selector)

  // 篇幅原因,省略一部分内容
};

我们可以通过runtime-core提供的自定义渲染能力,把 DOM 节点增删改查的能力添加进去:

// @vue/runtime-dom/src/index.ts
import { createRenderer } from '@vue/runtime-core'
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'

const { render, createApp } = createRenderer<Node, Element>({
  patchProp,
  ...nodeOps
})

export { render, createApp }

所以在 Vue 3.0 版本中,可以通过 render 和 createApp 这两个 API 来进行初始化项目和页面渲染。

字节笔记本扫描二维码查看更多内容