当前位置: 首页 > news >正文

vue-loader 源码解析系列之 selector

笔者系 vue-loader 贡献者之一(#16)

前言

vue-loader 源码解析系列之一,阅读该文章之前,请大家首先参考大纲 vue-loader 源码解析系列之 整体分析

selector 做了什么

const path = require('path')
const parse = require('./parser')
const loaderUtils = require('loader-utils')

module.exports = function (content) {
  // 略
  const query = loaderUtils.getOptions(this) || {}
  // 略
  const parts = parse(content, filename, this.sourceMap, sourceRoot, query.bustCache)
  let part = parts[query.type]
  // 略
  this.callback(null, part.content, part.map)
}

大家可以看到,selector的代码非常简单,
通过 parser 将 .vue 解析成对象 parts, 里面分别有 style, script, template。可以根据不同的 query, 返回对应的部分。
很明显那么这个 parser 完成了分析分解 .vue 的工作,那么让我们继续深入 parser

parser 做了什么


const compiler = require('vue-template-compiler')
const cache = require('lru-cache')(100)

module.exports = (content, filename, needMap, sourceRoot, bustCache) => {
  const cacheKey = hash(filename + content)
  // 略
  let output = cache.get(cacheKey)
  if (output) return output
  output = compiler.parseComponent(content, { pad: 'line' })
  if (needMap) {
    // 略去了生成 sourceMap 的代码
  }
  cache.set(cacheKey, output)
  return output
}

同样的,为了方便读者理解主要流程,笔者去掉了部分代码。

从上面代码可以看到,.vue 解析的工作其实是交给了 compiler.parseComponent 去完成,那么我们需要继续深入 compiler。
注意,这里 vue-template-compiler 并不是 vue-loader 的一部分,从 vue-template-compiler 的 npm 主页可以了解到, vue-template-compiler 原来是 vue 本体的一部分
并不是一个单独的 package。通过查看文档可知,compiler.parseComponent 的逻辑在 vue/src/sfc/parser.js 里。

源码如下

parseComponent 做了什么

/**
 * Parse a single-file component (*.vue) file into an SFC Descriptor Object.
 */
export function parseComponent (
  content: string,
  options?: Object = {}
 ): SFCDescriptor {
  const sfc: SFCDescriptor = {
    template: null,
    script: null,
    styles: [],
    customBlocks: []
  }
  let depth = 0
  let currentBlock: ?(SFCBlock | SFCCustomBlock) = null

  function start (
    tag: string,
    attrs: Array<Attribute>,
    unary: boolean,
    start: number,
    end: number
  ) {
    // 略
  }

  function checkAttrs (block: SFCBlock, attrs: Array<Attribute>) {
    // 略
  }

  function end (tag: string, start: number, end: number) {
    // 略
  }

  function padContent (block: SFCBlock | SFCCustomBlock, pad: true | "line" | "space") {
    // 略
  }

  parseHTML(content, {
    start,
    end
  })

  return sfc
}

parseComponent 里面有以下变量

  • 处理对象 sfc

    把 .vue 里的 css, javaScript, html 抽离出来之后,存放到找个这个对象里面

  • 变量 depth

    当前正在处理的节点的深度,比方说,对于 <template><div><p>foo</p></div></template>来说,处理到 foo 时,当前深度就是 3, 处理到 </div> 时,当前深度就是 2 。

  • currentBlock

    当前正在处理的节点,以及该节点的 attr 和 content 等信息。

  • 函数 start

    遇到 openTag 节点时,对 openTag 的相关处理。逻辑不是很复杂,读者可以直接看源码。有一点值得注意的是,style 是用 array 形式存储的

  • 函数 end

    遇到 closeTag 节点时,对 closeTag 的相关处理。

  • 函数 checkAttrs

    对当前节点的 attrs 的相关处理

  • 函数 parseHTML

    这是和一个外部的函数,传入了 content (其实也就是 .vue 的内容)以及由 start和 end 两个函数组成的对象。看来,这个 parseHTML 之才是分解分析 .vue 的关键

    跟之前一样,我们要继续深入 parseHTML 函数来分析,它到底对 .vue 做了些什么,源码如下

parseHTML 做了什么


export function parseHTML (html, options) {
  const stack = []
  const expectHTML = options.expectHTML
  const isUnaryTag = options.isUnaryTag || no
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no
  let index = 0
  let last, lastTag
  while (html) {
    last = html
    if (!lastTag || !isPlainTextElement(lastTag)) {
      // 这里分离了template
    } else {
      // 这里分离了style/script
  }

  // 略

  // 前进n个字符
  function advance (n) {
    // 略
  }

  // 解析 openTag 比如 <template>
  function parseStartTag () {
    // 略
  }

  // 处理 openTag
  function handleStartTag (match) {
    // 略
    if (options.start) {
      options.start(tagName, attrs, unary, match.start, match.end)
    }
  }

  // 处理 closeTag
  function parseEndTag (tagName, start, end) {
    // 略
    if (options.start) {
      options.start(tagName, [], false, start, end)
    }
    if (options.end) {
      options.end(tagName, start, end)
    }
  }
}

深入到这一步,我想再提醒一下读者,selector的目的是将 .vue 中的 template, javaScript, css 分离出来。带着这个目的意识,我们再来审视这个 parseHTML。

parseHTML 整个函数的组成是:

  • 一个 while 循环

    在 while 循环中,存在两个大的分支,一个用来分析 template ,一个是用来分析 script 和 style。

  • 函数 advance

    向前跳过文本

  • 函数 parseStartTag

    判断当前的 node 是不是 openTag

  • 函数 handleStartTag

    处理 openTag, 这里就用到了之前提到的 start() 函数

  • 函数 parseEndTag

    判断当前的 node 是不是 closeTag,同时这里也用到了 end() 函数

通过以上各个函数的组合,在while循环中就将 sfc 分割成了三个不同的部分,读者可以对比我的注释和源码自行解读源码逻辑。

顺便在这里吐个槽,很明显这里的 parseHTML 是函数名是有问题的,parseHTML 应该叫做 parseSFC 比较合适。

  • vue-loader 源码解析之一 整体分析
  • vue-loader 源码解析之三 style-compiler (写作中)
  • vue-loader 源码解析之四 template-compiler (写作中)

作者博客

作者github

作者微博

相关文章:

  • 一行一行读Java源码——迭代器
  • asp.net core ef core mysql 新增数据并发异常处理
  • xshell连接Ubuntu系统
  • 多维分析的后台性能优化手段
  • 企业级自动化运维工具应用实战-ansible
  • 网络编程 与 面向对象
  • MapGIS6.7投影生成线-以物化探综合剖面图为例
  • C# 编码命名规则
  • HttpClient超时机制(安全问题处理:访问超大文件控制)
  • 关于如何在ElementUI中实现统计Table筛选结果数量
  • iOS网络基础 实战进阶篇
  • 使用ELK构建分布式日志分析系统
  • 后端的一些经验与心得
  • 超过父控件的部分不能响应事件怎么办
  • WKWebView的使用总结(oc与js交互使用心得)
  • JavaScript-如何实现克隆(clone)函数
  • 【347天】每日项目总结系列085(2018.01.18)
  • CSS实用技巧干货
  • Intervention/image 图片处理扩展包的安装和使用
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • JavaScript对象详解
  • JavaScript设计模式与开发实践系列之策略模式
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • Median of Two Sorted Arrays
  • nodejs:开发并发布一个nodejs包
  • 高程读书笔记 第六章 面向对象程序设计
  • 给Prometheus造假数据的方法
  • 扑朔迷离的属性和特性【彻底弄清】
  • 使用agvtool更改app version/build
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 回归生活:清理微信公众号
  • ​ArcGIS Pro 如何批量删除字段
  • #前后端分离# 头条发布系统
  • (LeetCode C++)盛最多水的容器
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (附源码)ssm码农论坛 毕业设计 231126
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • (轉貼) UML中文FAQ (OO) (UML)
  • ../depcomp: line 571: exec: g++: not found
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .net 微服务 服务保护 自动重试 Polly
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .NET处理HTTP请求
  • .net反混淆脱壳工具de4dot的使用
  • @NoArgsConstructor和@AllArgsConstructor,@Builder
  • @SuppressWarnings(unchecked)代码的作用
  • @vue/cli脚手架
  • @四年级家长,这条香港优才计划+华侨生联考捷径,一定要看!
  • [ SNOI 2013 ] Quare
  • [android] 练习PopupWindow实现对话框