Vue-loader 的巧妙玩法

t174in22 7年前發布 | 26K 次閱讀 JavaScript開發 JavaScript

聲明:可以這么玩,不代表應該這么玩

一、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 都會執行一遍轉換函數,顯然,這對渲染性能有影響

所以,雖然可以這么玩,但是不代表應該這么玩,還需三思而行

其它的使用場景暫時也還沒想到,即便如此,這種瞎折騰也不是沒有意義的,沒準在還無法預見的場景,這是一種絕佳的解決方案呢?如果你剛好遇到了,也同步給我吧~~

更多精彩,期待您的關注

 

來自:https://juejin.im/post/5935803a0ce46300572e1160

 

 本文由用戶 t174in22 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!