# vue-loader (opens new window)

@15.7.0

  1. 模块基本划分
    • 解析 .vue 文件分成 templatescriptstylecustom 模块
    • 返回拼接依赖上述模块的 js 格式文本,带上 typeid 等标识后续的解析
  2. 模块 loader 内敛设置
    • 配合 plugin 更新 module.rules,通过匹配 resourceQuery,在最前面加入解析上述模块的内置 loader pitcher
    • pitcherpitch 函数根据 type 等,再次更新文本调整分发 loader

      根据 loaderpitching,将 loader 放在最前面,并使用 pitch 最先执行并返回值不被其他 loader 处理 为了 style 模块划出去之后能用到其他 loader,把 lang 后缀加上,在下一轮中 webpack 会把匹配的 rule 都补上

  3. 依据内敛 loader 模块处理
    • template
      • 得到 template 部分的代码
      • templateLoader 解析 template 模块,转成 render 函数
    • style
      • vue-style-loaderpitch 函数基于 pitcher 指定的 loader ,根据 RSS、hot 等做调整
      • 得到 script 部分的代码
      • less/sass/styl...
      • styleLoader 解析 script 模块,处理 scoped 给节点加上 data-v${id} 属性
      • css-loader 以一般css文件处理

# 过程展示

  1. 加载一个app.vue文件
  2. webpack根据配置,构成loader链vue-loader??vue-loader-options!./app.vue
  3. vue-loader解析app.vue文件,返回拆分成多个加载项文本
    • ./app.vue?vue&type=template&id=ed45c92a&
    • ./app.vue?vue&type=script&lang=js&
    • ./app.vue?vue&type=style&index=0&lang=less&
  4. vue-loader.plugin中,在插件加载时给原rule增加了?vue&lang=匹配,在rule最前面插入匹配?vuepitch
  5. webpack根据配置,构成loader链vue-loader.plugin.pitcher!...langLoaders!vue-loader??vue-loader-options!./app.vue?vue&type=xxx&lang=xxx
  6. vue-loader.plugin.pitcher做拦截处理;结合‘langLoaders’拼接完整的loader链,直接返回
    • template:...postLoaders!templateLoader!...preLoaders!vue-loader??vue-loader-options!./app.vue?type=template&id=ed45c92a&
    • script:...loaders!vue-loader??vue-loader-options!./app.vue?type=script&id=ed45c92a&
    • style:...afterLoaders!stylePostLoader!...beforeLoaders!vue-loader??vue-loader-options!./app.vue?type=style&id=ed45c92a&index=0&lang=less&
  7. vue-loader再次解析,针对不同的type处理返回.vue文件中不同的部分
  8. ‘templateLoader’-vue-template-compiler 模版解析返回js,即render方法
  9. ‘stylePostLoader’-postcss 解析返回

# vue-template-compiler (opens new window)

文本解析主模块

# compiler

  • 闭包封装默认 option
  • 构建 ast
    • 文本解析构建树形结构

      字符串查找、正则匹配

      • 用 index 打标,last 备份
      • 用 children 、 parent 关联元素形成树
      • 用 root 保留根结点,currentParent 处理当前节点,stack 存储节点栈
    • 提供 module 插件模式
      • transformNode
      • preTransformNode
      • postTransformNode
  • 递归遍历 ast 给所有节点打标 static、staticRoot 属性
  • 遍历 ast 输出 render 文本

# style

# style scoped

检测到<style>标签有scoped属性时,主要做两件事:组件节点增加属性、css选择器都增加属性选择器。

  • 节点增加属性
    1. 在loader过程中,给组件配置项增加_scopeId
    2. 在组件实例运行时中,创建dom元素时,取函数式组件虚拟dom上的fnScopeId或直接配置项的_scopeId做属性
  • 增加css选择器
    1. 在loader过程中,在@vue/component-compiler-utils/lib/compileStyle.ts使用postcss中增加scoped处理插件
    2. 在插件中,重置节点选择器函数,使用postcss-selector-parser给选择器增加一个属性选择器

# style module

检测到<style>标签有module属性时,主要做两件事:构建样式对象、更新css选择器。

  • 构建样式对象
    1. 在loader第一阶段中,在组件配置项增加beforeCreatehook,注册$style(或其他自定义模块名)实例属性
    2. 在loader第二阶段中,vue-style-loadercss-loader提供的export.locals插入到脚本中
  • 更新css选择器
    1. 在loader第二阶段中,给css-loader传递了modules参数
    2. css-loaderpostcss处理css时增加 modules 相关插件
      • postcss-modules-values
      • postcss-modules-local-by-default
      • postcss-modules-extract-imports
      • postcss-modules-scope

# ast 节点属性说明

declare type CompilerOptions = {
  warn?: Function; // allow customizing warning in different environments; e.g. node
  modules?: Array<ModuleOptions>; // platform specific modules; e.g. style; class
  directives?: { [key: string]: Function }; // platform specific directives
  staticKeys?: string; // a list of AST properties to be considered static; for optimization
  isUnaryTag?: (tag: string) => ?boolean; // check if a tag is unary for the platform
  canBeLeftOpenTag?: (tag: string) => ?boolean; // check if a tag can be left opened
  isReservedTag?: (tag: string) => ?boolean; // check if a tag is a native for the platform
  preserveWhitespace?: boolean; // preserve whitespace between elements? (Deprecated)
  whitespace?: 'preserve' | 'condense'; // whitespace handling strategy
  optimize?: boolean; // optimize static content?

  // web specific
  mustUseProp?: (tag: string, type: ?string, name: string) => boolean; // check if an attribute should be bound as a property
  isPreTag?: (attr: string) => ?boolean; // check if a tag needs to preserve whitespace
  getTagNamespace?: (tag: string) => ?string; // check the namespace for a tag
  expectHTML?: boolean; // only false for non-web builds
  isFromDOM?: boolean;
  shouldDecodeTags?: boolean;
  shouldDecodeNewlines?:  boolean;
  shouldDecodeNewlinesForHref?: boolean;
  outputSourceRange?: boolean;

  // runtime user-configurable
  delimiters?: [string, string]; // template delimiters
  comments?: boolean; // preserve comments in template

  // for ssr optimization compiler
  scopeId?: string;
};

declare type WarningMessage = {
  msg: string;
  start?: number;
  end?: number;
};

declare type CompiledResult = {
  ast: ?ASTElement;
  render: string;
  staticRenderFns: Array<string>;
  stringRenderFns?: Array<string>;
  errors?: Array<string | WarningMessage>;
  tips?: Array<string | WarningMessage>;
};

declare type ModuleOptions = {
  // transform an AST node before any attributes are processed
  // returning an ASTElement from pre/transforms replaces the element
  preTransformNode: (el: ASTElement) => ?ASTElement;
  // transform an AST node after built-ins like v-if, v-for are processed
  transformNode: (el: ASTElement) => ?ASTElement;
  // transform an AST node after its children have been processed
  // cannot return replacement in postTransform because tree is already finalized
  postTransformNode: (el: ASTElement) => void;
  genData: (el: ASTElement) => string; // generate extra data string for an element
  transformCode?: (el: ASTElement, code: string) => string; // further transform generated code for an element
  staticKeys?: Array<string>; // AST properties to be considered static
};

declare type ASTModifiers = { [key: string]: boolean };
declare type ASTIfCondition = { exp: ?string; block: ASTElement };
declare type ASTIfConditions = Array<ASTIfCondition>;

declare type ASTAttr = {
  name: string; // 属性名
  value: any; // 属性值
  dynamic?: boolean;
  start?: number;
  end?: number
};

declare type ASTElementHandler = {
  value: string;
  params?: Array<any>;
  modifiers: ?ASTModifiers;
  dynamic?: boolean;
  start?: number;
  end?: number;
};

declare type ASTElementHandlers = {
  [key: string]: ASTElementHandler | Array<ASTElementHandler>;
};

declare type ASTDirective = {
  name: string;
  rawName: string;
  value: string;
  arg: ?string;
  isDynamicArg: boolean;
  modifiers: ?ASTModifiers;
  start?: number;
  end?: number;
};

declare type ASTNode = ASTElement | ASTText | ASTExpression;

declare type ASTElement = {
  type: 1; // 类型,默认为1,
  tag: string; // 标签名,如:‘div’、‘input’
  attrsList: Array<ASTAttr>; // 属性列表
  attrsMap: { [key: string]: any }; // 以属性名为 key,属性值为 value 的 object
  rawAttrsMap: { [key: string]: ASTAttr }; // 以属性名为 key,属性对象为 value 的 object
  parent: ASTElement | void; // 父节点
  children: Array<ASTNode>; // 子节点列表

  start?: number; // 节点的起始在文本的位置
  end?: number; // 节点的结束在文本的位置

  processed?: true; //?解析结束

  static?: boolean;
  staticRoot?: boolean;
  staticInFor?: boolean;
  staticProcessed?: boolean;
  hasBindings?: boolean;

  text?: string;
  attrs?: Array<ASTAttr>; // attrsList 的复制
  dynamicAttrs?: Array<ASTAttr>;
  props?: Array<ASTAttr>;
  plain?: boolean; // !element.key && !element.scopedSlots && !element.attrsList.length
  pre?: true; // 是否有‘v-pre’属性,属性列表里会删除,【跳过这个元素和它的子元素的编译过程】
  ns?: string; // namespace,有:‘svg’、‘math’

  component?: string;
  inlineTemplate?: true;
  transitionMode?: string | null;
  slotName?: ?string;
  slotTarget?: ?string;
  slotTargetDynamic?: boolean;
  slotScope?: ?string;
  scopedSlots?: { [name: string]: ASTElement };

  ref?: string; // v-ref 表达式
  refInFor?: boolean; // 父节点有 for

  if?: string; // v-if 属性绑定的表达式
  ifProcessed?: boolean;
  elseif?: string; // v-else-if 属性绑定的表达式
  else?: true; // 有 v-else 属性
  ifConditions?: ASTIfConditions; // 条件列表

  for?: string; // v-for 属性绑定的 data 值
  forProcessed?: boolean;
  key?: string; // v-key 属性绑定的表达式
  alias?: string; // v-for 属性遍历的 value
  iterator1?: string; // v-for 属性遍历的 数组的index,对象的key
  iterator2?: string; // v-for 属性遍历的 对象的index

  staticClass?: string;
  classBinding?: string;
  staticStyle?: string;
  styleBinding?: string;
  events?: ASTElementHandlers;
  nativeEvents?: ASTElementHandlers;

  transition?: string | true;
  transitionOnAppear?: boolean;

  model?: {
    value: string;
    callback: string;
    expression: string;
  };

  directives?: Array<ASTDirective>;

  forbidden?: true; // 是否为无效元素,默认没有此字段,有:‘style’、‘script’
  once?: true; // 有 v-once 属性
  onceProcessed?: boolean;
  wrapData?: (code: string) => string;
  wrapListeners?: (code: string) => string;

  // 2.4 ssr optimization
  ssrOptimizability?: number;

  // weex specific
  appendAsTree?: boolean;
};

declare type ASTExpression = {
  type: 2;
  expression: string;
  text: string;
  tokens: Array<string | Object>;
  static?: boolean;
  // 2.4 ssr optimization
  ssrOptimizability?: number;
  start?: number;
  end?: number;
};

declare type ASTText = {
  type: 3;
  text: string;
  static?: boolean;
  isComment?: boolean;
  // 2.4 ssr optimization
  ssrOptimizability?: number;
  start?: number;
  end?: number;
};

// SFC-parser related declarations

// an object format describing a single-file component
declare type SFCDescriptor = {
  template: ?SFCBlock;
  script: ?SFCBlock;
  styles: Array<SFCBlock>;
  customBlocks: Array<SFCBlock>;
  errors: Array<string | WarningMessage>;
}

declare type SFCBlock = {
  type: string;
  content: string;
  attrs: {[attribute:string]: string};
  start?: number;
  end?: number;
  lang?: string;
  src?: string;
  scoped?: boolean;
  module?: string | boolean;
};
最后更新: 1/12/2023, 1:44:05 PM