宣告:可以這麼玩,不代表應該這麼玩
一、Vue-loader 幹了啥?
Vue-loader 是一個 webpack 載入器,它可以把形如:
<template>
...
</template>
<script>
...
</script>
<style>
...
</style>複製程式碼
的 Vue 元件轉化為 Js 模組,這其中最最值得關注的是,它生成了 render function code
,在前之前的一篇文章 詳解 render function code 中,已經對它進行了細緻的介紹:
render function code
是從模板編譯而來(可以並且應該預編譯)的元件核心渲染方法,在每一次元件的 Render 過程中,通過注入的資料執行可生成虛擬 Dom
既然 Vue-loader 預編譯生成了 render function code
,那麼我們就可以通過改造 Vue-loader 來改寫 render function code
的生成結果,從而全域性的影響元件的每一次渲染結果
二、如何改造?
找到目的碼
Vue loader 並不普通,需要通過語法樹分析的方式最終生成 render function code
(並且不限於此),如果通篇閱讀如此複雜的程式碼可能會讓你——沮喪。幸運的是,要完成改寫 render function code
的小目標,我們並不需要讀得太多,找到生成結果那一小段程式碼,在返回之前改寫即可。那麼新的問題又來了,這小段程式碼又如何定位?
一是靠猜:開啟 Vue-loader 的目錄結構,見名知義,顯然 template-compiler
模板編譯的意思更為接近
二是搜尋:在 template-compiler
目錄中搜尋 render
, 有沒有發現有一段程式碼很有嫌疑
var code
if (compiled.errors && compiled.errors.length) {
this.emitError(
`\n Error compiling template:\n${pad(html)}\n` +
compiled.errors.map(e => ` - ${e}`).join('\n') + '\n'
)
code = 'module.exports={render:function(){},staticRenderFns:[]}'
} else {
var bubleOptions = options.buble
// 這段程式碼太可疑了
code = transpile('module.exports={' +
'render:' + toFunction(compiled.render) + ',' +
'staticRenderFns: [' + compiled.staticRenderFns.map(toFunction).join(',') + ']' +
'}', bubleOptions)
// mark with stripped (this enables Vue to use correct runtime proxy detection)
if (!isProduction && (
!bubleOptions ||
!bubleOptions.transforms ||
bubleOptions.transforms.stripWith !== false
)) {
code += `\nmodule.exports.render._withStripped = true`
}
}複製程式碼
三是除錯確認:列印 toFunction(compiled.render)
, 檢視輸出結果,如果跟 render function code
的表現一致的話,那就是它了
with(this) {
return _c('div', {
attrs: {
"id": "app"
},
staticStyle: {
"width": "100px"
},
style: styleObj
},
[_c('p', [_v("普通屬性:" + _s(message))]), _v(" "), _c('p', [_v(_s(msg()))]), _v(" "), _c('p', [_v(_s(ct))]), _v(" "), _c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (message),
expression: "message"
}],
domProps: {
"value": (message)
},
on: {
"input": function($event) {
if ($event.target.composing) return;
message = $event.target.value
}
}
}), _v(" "), _l((items),
function(item) {
return _c('div', [_v("\n\t\t " + _s(item.text) + "\n\t ")])
}), _v(" "), _c('button', {
on: {
"click": bindClick
}
},
[_v("點我出奇跡抓同偉")])], 2)
}複製程式碼
如何改造?
假如我們想把所有元件的所有靜態樣式(staticStyle)的畫素值乘二(雖然有點搞破壞),那麼我們需要對上述 toFunction(compiled.render)
進行正則替換
var renderCode = toFunction(compiled.render)
renderCode = renderCode.replace(/(staticStyle:)(\s*{)([^}]*)(})/g, function (m, n1, n2, n3, n4) {
n3 = n3.replace(/(".*")(\s*:\s*")(\d+px)(")/g, function (m, n31, n32, n33, n34) {
return n31 + n32 + parseInt(n33)*2 + 'px' + n34
})
return n1 + n2 + n3 + n4
})複製程式碼
如果是改造動態樣式(style),由於在 render function code
中,動態 style 以變數的形式出現,我們不能直接替換模板,那麼我們可以通過傳入一個方法,在執行時執行轉換。不要企圖寫一個普通的方法,通過方法名的引用在 render function code
中執行,因為 render function code
執行時的作用域,不是在 Vue-loader
階段可以確認的,所以我們需要寫一個立即執行函式:
var convertCode = "(function(styleObj){styleObj = (...此處省略N行程式碼)})(##style##)"複製程式碼
立即執行函式的入參用 ##style##
佔位,替換的過程中用具體的變數代替,上述 render function code
替換結果為:
with(this) {
return _c('div', {
attrs: {
"id": "app"
},
staticStyle: {
"width": "100px"
},
// 重點在這裡 在這裡
style: (function(styleObj){styleObj = (...此處省略N行程式碼)})(styleObj)
},
...
)
}複製程式碼
三、有何使用場景?
例如,當你一開始使用了 px 作為佈局單位,卻需要改造為 rem 佈局單位的時候(業務程式碼很多很繁雜,不方便一個個去改,並且由於動態樣式的存在,難以全域性替換)
對於插入立即執行函式去處理動態變數的方式,每一次 Re-render
都會執行一遍轉換函式,顯然,這對渲染效能有影響
所以,雖然可以這麼玩,但是不代表應該這麼玩,還需三思而行
其它的使用場景暫時也還沒想到,即便如此,這種瞎折騰也不是沒有意義的,沒準在還無法預見的場景,這是一種絕佳的解決方案呢?如果你剛好遇到了,也同步給我吧~~
更多精彩,期待您的關注