筆者系 vue-loader 貢獻者(#16)之一
官方說明
vue-loader is a loader for Webpack that can transform Vue components written in the following format into a plain JavaScript module
簡單來說就是:將 .vue 檔案變成 .bundle.js,然後放入瀏覽器執行。
觀察輸入輸出
輸入
測試是最好的文件,所以我們從測試用例開始分析,找到test/fixture/basic.vue,內容如下:
<template>
<h2 class="red">{{msg}}</h2>
</template>
<script>
export default {
data () {
return {
msg: `Hello from Component A!`
}
}
}
</script>
<style>
comp-a h2 {
color: #f00;
}
</style>
輸出
通過執行測試之後,可以得到以下輸出,但是由於檔案巨大,筆者只抽出部分開始分析,如下
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// CONCATENATED MODULE: ./node_modules/babel-loader/lib!./lib/selector.js?type=script&index=0&bustCache!./test/fixtures/basic.vue
/* harmony default export */
var basic = ({
data() {
return {
msg: `Hello from Component A!`
};
}
});
// CONCATENATED MODULE: ./lib/template-compiler?{"id":"data-v-b647d0ce","hasScoped":false,"buble":{"transforms":{}}}!./lib/selector.js?type=template&index=0&bustCache!./test/fixtures/basic.vue
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("h2", { staticClass: "red" }, [_vm._v(_vm._s(_vm.msg))])
}
var staticRenderFns = []
render._withStripped = true
var esExports = { render: render, staticRenderFns: staticRenderFns }
/* harmony default export */ var fixtures_basic = (esExports);
if (false) {
module.hot.accept()
if (module.hot.data) {
require("vue-hot-reload-api") .rerender("data-v-b647d0ce", esExports)
}
}
// CONCATENATED MODULE: .!./test/fixtures/basic.vue
var disposed = false
function injectStyle (ssrContext) {
if (disposed) return
__webpack_require__(3)
}
var normalizeComponent = __webpack_require__(8)
/* script */
/* template */
/* template functional */
var __vue_template_functional__ = false
/* styles */
var __vue_styles__ = injectStyle
/* scopeId */
var __vue_scopeId__ = null
/* moduleIdentifier (server only) */
var __vue_module_identifier__ = null
var Component = normalizeComponent(
basic,
fixtures_basic,
__vue_template_functional__,
__vue_styles__,
__vue_scopeId__,
__vue_module_identifier__
)
Component.options.__file = "test/fixtures/basic.vue"
if (Component.esModule && Object.keys(Component.esModule).some(function (key) { return key !== "default" && key.substr(0, 2) !== "__"})) { console.error("named exports are not supported in *.vue files.")}
})()}
分析輸出
以上的輸出就是最終可以拿到瀏覽器上執行的 javaScript,儘管筆者已經刪除了一些會影響理解的部分程式碼,但是這麼直接觀察這個檔案,難免還是無從下手。
那麼我們繼續細化分析步驟,vue-loader 將 basic.vue 編譯到最終輸出的 bundle.js 的過程中,其實呼叫了四個小的 loader。它們分別是:
- selector
- style-compiler
- template-compiler
- babel-loader
以上四個 loader ,除了 babel-loader 是外部的package,其他三個都存在於 vue-loader 的內部(lib/style-compiler 和 lib/template-compiler 和 lib/selector)。
首先 vue-loader 將 basic.vue 編譯成以下內容
/* script */
import __vue_script__ from "!!babel-loader!../../lib/selector?type=script&index=0&bustCache!./basic.vue"
/* template */
import __vue_template__ from "!!../../lib/template-compiler/index?{"id":"data-v-793be54c","hasScoped":false,"buble":{"transforms":{}}}!../../lib/selector?type=template&index=0&bustCache!./basic.vue"
/* styles */
import __vue_styles__ from "!!vue-style-loader!css-loader!../../lib/style-compiler/index?{"vue":true,"id":"data-v-793be54c","scoped":false,"hasInlineConfig":false}!../../lib/selector?type=styles&index=0&bustCache!./basic.vue"
var Component = normalizeComponent(
__vue_script__,
__vue_template__,
__vue_template_functional__,
__vue_styles__,
__vue_scopeId__,
__vue_module_identifier__
)
為了方便理解,筆者刪除修改了一些內容。
在三個 import 語句中,不管它們用了多少個不同的 loader 去載入,loader chain 的源頭都是 basic.vue。
JavaScript 部分
首先分析 script 部分
/* script */
import __vue_script__ from "!!babel-loader!../../lib/selector?type=script&index=0&bustCache!./basic.vue"
從做右到左,也就是 basic.vue 被先後被 selector 和 babel-loader 處理過了。
selector(引數type=script) 的處理結果是將 basic.vue 中的 javaScript 抽出來之後交給babel-loader去處理,最後生成可用的 javaScript
Template 部分
再來分析 template 部分
/* template */
import __vue_template__ from "!!../../lib/template-compiler/index?{"id":"data-v-793be54c","hasScoped":false,"buble":{"transforms":{}}}!../../lib/selector?type=template&index=0&bustCache!./basic.vue"
同樣的,從左到右,basic.vue 先後被 selector 和 template-compiler 處理過了。
selector (引數type=template) 的處理結果是將 basic.vue 中的 template 抽出來之後交給 template-compiler 處理,最終輸出成可用的 HTML。
Style 部分
最後分析 style 部分
/* styles */
import __vue_styles__ from "!!vue-style-loader!css-loader!../../lib/style-compiler/index?{"vue":true,"id":"data-v-793be54c","scoped":false,"hasInlineConfig":false}!../../lib/selector?type=styles&index=0&bustCache!./basic.vue"
style 涉及的 loader 較多,一個一個來分析, 從上程式碼可知,basic.vue 先後要被 selector, style-compiler, css-loader 以及 vue-style-loader 處理。
selector (引數type=style) 的處理結果是將 basic.vue 中的 css 抽出來之後交給 style-compiler 處理成 css, 然後交給 css-loader 處理生成 module, 最後通過 vue-style-loader 將 css 放在 <style>
裡面,然後注入到 HTML 裡。
注意,這裡之所以沒有用 style-loader 是因為 vue-style-loader 是在 fork 了 style-loader 的基礎上,增加了後端繪製 (SSR) 的支援。具體的不同,讀者可以檢視官方文件,筆者這裡不再累述。
後續
通過上面的介紹,想必讀者已經對 vue-loader 以及它涉及的幾個 loader 的作用有了一個大概的瞭解。
那麼接下來,在後續文章中我們來開始一個個分析這幾個 loader 的原始碼。
- vue-loader 原始碼解析之二 selector
- vue-loader 原始碼解析之三 style-compiler (寫作中)
- vue-loader 原始碼解析之四 template-compiler (寫作中)