筆者系 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 (寫作中)