Vue-loader 的巧妙玩法

声明:可以这么玩,不代表应该这么玩

一、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 都会执行一遍转换函数,显然,这对渲染性能有影响

所以,虽然可以这么玩,但是不代表应该这么玩,还需三思而行

其它的使用场景暂时也还没想到,即便如此,这种瞎折腾也不是没有意义的,没准在还无法预见的场景,这是一种绝佳的解决方案呢?如果你刚好遇到了,也同步给我吧~~

掘金开设了专栏,欢迎捧场,点此去~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,044评论 0 29
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,394评论 25 707
  • 时 间:2017年4月16日 13:30—17:30 地 点:南京市玄武区丹凤街恒基公寓B区2栋2单元501 游戏...
    是的陛下阅读 258评论 0 0
  • “依般若波罗蜜多故,心无挂碍。”前面已经讲过,般若波罗蜜多就是到达彼岸的大智慧,彼岸在哪?不在外,在你的内心。我们...
    莲连阅读 280评论 0 0
  • Art Deco演变自十九世纪末的Art Nouveau(新艺术)运动,当时的Art Nouveau是资产阶...
    柯恩阅读 471评论 0 2