vue.js如何让某个checkbox禁用_【第2174期】图解 VueLoader : .vue 文件是如何被打包的?...

前言

今日前端早读课文章由腾讯@尹佳分享,由腾讯@云加社区授权分享。

正文从这开始~~

一、整体概述

使用过 Vue 的同学,对于 .vue 单文件文件组件类型的文件(下文简称 SFC)应该不会陌生。SFC 文件需要通过构建工具(本文以 Webpack4 为例)打包成一个 Bundle,才能被识别和使用。那么这中间经历了什么、不同的代码块是如何被其他规则识别的、最终生成了什么?带着这些问题,且看下文一一道来。

ec97f23efc07f52ddaa3ada60d3e1b91.png

图1. SFC 经过 Webpack 打包后的产物是什么

1. SFC的输入和输出

Webpack 需要增加 vue-loader [1]和 vueLoaderPlugin 对 SFC 进行支持。我们首先聚焦到 vue-loader 的代码:入口文件为 lib/index.js ,入参 source 是 SFC 源码,经过处理逻辑后,输出 export default 的代码字符串。

// vue-loader lib/index.js

// source 是我们写的 SFC 源码

module.exports = function (source) {

...

// 返回值 code 是一段 ESModule 代码字符串

let code = `...`;

code += `\n export default component.exports`;

return code;

};

用实际的例子操作一下, demo.vue 经过运行后得到下图的输出。经过观察可以发现,原有的 template ,变成了 import...from'./demo.vue?vue&type=template' ,其他代码块也发生了类似的变化。?vue&type=template 新增的 vue 和 type 参数的作用是什么?我们继续往下看。

8b6ac19474d78a2c2159767b8d3dd8a1.png

图2. SFC 被 vue-loader 转化后的结果
2. template、script、style 代码块切分

上个小节中,可以看到 template、script、style 代码块在输出结果中已经转化为对应的 import 逻辑。这一步是 vue-loader 调用了 @vue/component-compiler-utils 的 parse 函数进行解析后,分别生成了对应的 import 逻辑,相关源码如下:

// vue-loader lib/index.js

const { parse } = require('@vue/component-compiler-utils');

module.exports = function (source) {

// 解析源码,得到描述符

const descriptor = parse({ source, ... });

// 如果 template 块存在

if (descriptor.template) { ... }

// 如果 script 块存在

if (descriptor.script) { ... }

// 如果 style 块存在(支持多 style 块)

if (descriptor.styles.length) { ... }

// Vue 还支持自定义块

if (descriptor.customBlocks && descriptor.customBlocks.length) { ... }

}

下图是 demo.vue 被转化的流程图解:

050f2403c721cee9d8a45c6faca48d21.png

图3. 各个代码块被分别转化为相应的 import 逻辑
二、VueLoaderPlugin 的作用

前面的章节留了一个疑问, ?vue&type=template 的作用是什么?可以从 VueLoaderPlugin [2]中找出答案,首先我们先了解 Webpack 中的 Plugin 能做什么。

1. Plugin 的特性

Plugin 的作用,主要有以下两条:

  • 能够 hook 到在每个编译(compilation)中触发的所有关键事件。

  • 在插件实例的 apply 方法中,可以通过 compiler.options 获取 Webpack 配置,并进行修改。

VueLoaderPlugin 通过第二个特性,在初始化阶段,对 module.rules 进行动态修改。

2. VueLoaderPlugin 预处理

VueLoaderPlugin 的处理流程中,修改了 module.rules,在原来的基础上加入了 pitcher 和 cloneRules 。这一步的作用是:新增的 rule ,能识别形如 ?vue&type=template 的 querystring,让不同语言的代码块匹配到对应的 rule。

class VueLoaderPlugin {

apply (compiler) {

// 标识 VueLoaderPlugin 已被加载

// 可用于 vue-loader 运行时,检测 VueLoaderPlugin 的加载信息

if (webpack4) { ... } esle { ... }

// 重头戏,对 Webpack 配置进行修改

const rawRules = compiler.options.module.rules;

const { rules } = new RuleSet(rawRules);

...

// 替换初始 module.rules,在原有 rule 上,增加 pitcher、clonedRules

compiler.options.module.rules = [

pitcher,

...clonedRules,

...rules

];

}

}

VueLoaderPlugin 对 rules 的修改,用下图可以更直观地理解各部分 Rule 的作用。(这里有一个小的知识点,除了常见的 Rule.test 选项外, Rule.resourceQuery 选项可以对资源的 querystring 进行匹配[3])

e31b9c2791fde328a47f38b8a5f6d451.png

图4. VueLoaderPlugin 对 module.rules 的修改
三、回到 Loader

上一节梳理了 VueLoaderPlugin 在初始化阶段的预处理,这一节我们继续回到构建阶段中,看看以 VueLoader 为中心如何协调其它 Loader ,得到每个代码块的构建结果。同样地,我们先了解一下 Webpack 的 loader 特性。

1. Webpack 的 loader 运行顺序

对于 loader ,我们知道它们的执行是有顺序的,如果是这样的配置,运行的顺序将是 c-loader -> b-loader -> a-loader。

module.exports = {

module: {

rules: [{

...

use: ['a-loader', 'b-loader', 'c-loader'],

}],

},

};

不过,在实际(从右到左)执行 loader 之前,会先从左到右调用 loader 上的 pitch 方法。

|- a-loader `pitch`

|- b-loader `pitch`

|- c-loader `pitch`

|- requested module is picked up as a dependency

|- c-loader normal execution

|- b-loader normal execution

|- a-loader normal execution

并且在 loader 的 pitch 方法中,如果有实际的返回值,将会跳过后续的 loader,比如在 b-loader 的 pitch 中,如果返回了实际值,将会产生下面的执行顺序。

# 注意 a-loader 依然会正常执行,跳过的是 c-loader

|- a-loader `pitch`

|- b-loader `pitch` returns a module

|- a-loader normal execution

知道这个特性,有利于我们理解 SFC 中各代码块在 loader 中的处理顺序。

2. SFC 转化流程

还记得第一节生成的编译结果吗?每个代码块都导出了对应逻辑,我们以 script 块为例,结合第二节的 PitcherLoader 再次进行转化,转化后的结果为:

cbd563fac3d71eeb1d9e0e9f196d48a4.png

图5. script 块的转化流程

最后的 import 语句,使用了内联方式的 import 语法[4],我们拆分一下便于理解。

# 原import

-!../../node_modules/babel-loader/lib/index.js??ref--2-

0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./demo.vue?

vue&type=script&lang=js&";

# Part1 -!

# 将禁用所有已配置的 preLoader 和 loader,但是不禁用 postLoaders

# Part2 ../../node_modules/babel-loader/lib/index.js??ref--2-0

# 参考小节:VueLoaderPlugin 的预处理,demo.vue 会自动添加 .js 后缀,以匹配所有 js 的 Rule,这里使用 babel-loader 处理 js 模块

# Part3 ../../node_modules/vue-loader/lib/index.js??vue-loader-options

# /\.vue$/ 规则匹配到 demo.vue,并使用 vue-loader 处理 .vue 后缀

# Part4 ./demo.vue?vue&type=script&lang=js&

3. PitchLoader

上述的转化发生在 PitchLoader 中,对 PitchLoader 的实现逻辑感兴趣的同学,可以阅读 loader/pitcher.js 的源码:

// vue-loader lib/loaders/pitcher.js

// PitcherLoader.pitch 方法,所有带 ?vue 的模块请求,都会走到这里

module.exports.pitch = function (remainingRequest) {

// 如 ./demo?vue&type=script&lang=js

// 此时,loaders 是所有能处理 .vue 和 .xxx 的 loader 列表

let loaders = this.loaders;

...

// 得到 -!babel-loader!vue-loader!

const genRequest = loaders => { ... };

// 处理 style 块 和 template 块,支持

if (query.type === 'style') { ... }

if (query.type === 'template') { ... }

// 处理 script 块和 custom 块

return `import mod from ${request}; export default mod; export * from ${request}`;

}

4. 再次执行 VueLoader

细心的同学可能发现了,在 PitchLoader 的转化结果中,还是会以 vue-loader 作为第一个处理的 loader,但 vue-loader 不是一开始就转化过了吗 ?与第一次不同的是,这次 vue-loader 的作用,仅仅是把 SFC 中语法块的源码提取出来,并交给后面的 loader 进行处理。

7f05e2f5948074bdab0f9d8244105eee.png

图6. 第二次进入 vue-loader

// vue-loader lib/index.js

const { parse } = require('@vue/component-compiler-utils');

module.exports = function (source) {

// 如果querystring 包含了 type 参数,则直接返回该块的代码

if (incomingQuery.type) {

return selectBlock( ... );

}

};

// vue-loader lib/select.js

module.exports = function selectBlock (...) {

// 选择不同语法块的内容进行返回

}

至此,vue-loader 里面的处理逻辑基本已经梳理完成。各部分代码块也传入后续的 loader 中进行解析和转化。

四、总结

我们再用一张完整的处理流程图总结一下 SFC 构建流程吧:

67bf2b1fa5ff494c36bafddc2588b68e.png

图7. 完整流程
参考链接:
  • SFC:https://cn.vuejs.org/v2/guide/single-file-components.html

  • vue-loader:https://github.com/vuejs/vue-loader/

  • 从vue-loader源码分析CSS Scoped的实现:https://juejin.cn/post/6844903949900742670

  • Webpack LoaderAPI:https://v4.webpack.docschina.org/api/loaders/

  • Webpack PluginAPI:https://v4.webpack.docschina.org/api/plugins/

[1]: vue-loader:https://github.com/vuejs/vue-loader/blob/master/lib/index.js#L32

[2]: VueLoaderPlugin:https://github.com/vuejs/vue-loader/blob/master/lib/plugin-webpack4.js

[3]: 对资源的 querystring 进行匹配:https://v4.webpack.docschina.org/configuration/module/#rule-resourcequery

[4]: import 语法:https://webpack.docschina.org/concepts/loaders/#inline

关于本文 作者:@尹佳原文:https://mp.weixin.qq.com/s/FJzDRLchG_DWA80Wp141Vg

726d5d7ed95029d3036614f13fb6fcda.png

为你推荐

【第2027期】图解CORS

【第2124期】CommonJS 是如何导致打包后体积增大的?

欢迎自荐投稿,前端早读课等你来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值