当前位置: 首页 > news >正文

vue输出语句_图解 VueLoader : .vue 文件是如何被打包的?

6278571d69f96ae78d30e62e96287920.png 导语 | 在 Vue 开发中,单文件组件(SFC,.vue 文件)的组件形态很常见,本文意在梳理和分享 SFC 的打包流程,便于大家对 SFC 的解析细节有所了解,也可在扩展 SFC 的能力时,有更合理的方案选择。文章作者:尹佳,腾讯前端研发工程师。

一、整体概述

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

f1400bab172321d0fb05664423a03290.png

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

1. SFC的输入和输出

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


【1】:vue-loader:

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


// 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 参数的作用是什么?我们继续往下看。

01c9c6c72bcecaf516e6c1d4ddae7e50.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.jsconst { 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 被转化的流程图解:

d5bb3e9316212bf86b833845f18b7c15.png

▲图3. 各个代码块被分别转化为相应的 import 逻辑

二、VueLoaderPlugin 的作用

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


【2】:VueLoaderPlugin:

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


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】)


【3】:对资源的 querystring 进行匹配:

https://v4.webpack.docschina.org/configuration/module/#rule-resourcequery


57f22001578b3f8f6323062d2711eeef.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 再次进行转化,转化后的结果为:

3ae0722979252637397f9e981503f63e.png

▲图5. script 块的转化流程

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


【4】:import 语法:

https://webpack.docschina.org/concepts/loaders/#inline

# 原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 进行处理

360a68b1b12c9105e6c62a491305dd09.png

▲图6. 第二次进入 vue-loader

// vue-loader lib/index.jsconst { parse } = require('@vue/component-compiler-utils');module.exports = function (source) {  // 如果querystring 包含了 type 参数,则直接返回该块的代码  if (incomingQuery.type) {    return selectBlock( ... );  }};// vue-loader lib/select.jsmodule.exports = function selectBlock (...) {  // 选择不同语法块的内容进行返回}

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

四、总结

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

f499bfbd734b531b95246c9befa1430b.png

▲图7. 完整流程

参考链接:

[1] SFC: https://cn.vuejs.org/v2/guide/single-file-components.html [2] vue-loader: https://github.com/vuejs/vue-loader/ [3] 从vue-loader源码分析CSS Scoped的实现: https://juejin.cn/post/6844903949900742670 [4] Webpack LoaderAPI: https://v4.webpack.docschina.org/api/loaders/ [5] Webpack PluginAPI: https://v4.webpack.docschina.org/api/plugins/ fe988ab25b772327dde04015b004b32b.gif

相关文章:

  • cifs挂载 mount ubuntu_ubuntu16.04挂载根文件系统报错mount:RPC:Unable to send;errno=Network is unreachable...
  • 华为手机输入键盘声音_华为手机默认输入法有6种技巧,炫酷加实用,网友:这谁顶得住...
  • 与context的关系_在React中使用Context的两点注意事项
  • cordova云相册插件_ionic 中使用 cordova camera 插件选择本地图片显示问题 -问答-阿里云开发者社区-阿里云...
  • 判断按键值_TinyUI(嵌入式UI库)-按键移植
  • 布局pd_PD-1/L1之后,肿瘤免疫疗法的这些方向也可能成为爆款!
  • 怎么投屏_手机投屏竖屏显示器怎么全屏
  • c++ map作为返回值_详解 C++ STL 中 map::erase 的正确姿势
  • python试卷(有答案版本、个人答案不是官方答案)_python试卷(有答案版本,个人答案不是官方答案)(精品文档)_共7页...
  • echarts 饼图进度条_Echarts实现环状半圆形饼图
  • 多开脚本_现阶段魔兽世界怀旧服晚上脚本成灾?G币会暴跌吗?
  • c#split方法拆分为数据_C#:使用String.Split方法在每个单独的行中拆分字符串列表?...
  • 压缩图片_图片压缩
  • python随机数应用_Python中随机数的使用于详细讲解
  • 生成图片_ThinkPHP5 动态生成图片缩略图
  • [ JavaScript ] 数据结构与算法 —— 链表
  • 【译】理解JavaScript:new 关键字
  • Android开发 - 掌握ConstraintLayout(四)创建基本约束
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • CSS中外联样式表代表的含义
  • C语言笔记(第一章:C语言编程)
  • Docker下部署自己的LNMP工作环境
  • iOS 系统授权开发
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • nodejs调试方法
  • Python 反序列化安全问题(二)
  • WebSocket使用
  • 记一次用 NodeJs 实现模拟登录的思路
  • 你不可错过的前端面试题(一)
  • 前端之Sass/Scss实战笔记
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • # 安徽锐锋科技IDMS系统简介
  • #大学#套接字
  • %check_box% in rails :coditions={:has_many , :through}
  • (13)Hive调优——动态分区导致的小文件问题
  • (MATLAB)第五章-矩阵运算
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • (转)http协议
  • (转)Sql Server 保留几位小数的两种做法
  • (转)Unity3DUnity3D在android下调试
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .NET开源项目介绍及资源推荐:数据持久层 (微软MVP写作)
  • .NET中 MVC 工厂模式浅析
  • .stream().map与.stream().flatMap的使用
  • @ConfigurationProperties注解对数据的自动封装
  • [20170705]lsnrctl status LISTENER_SCAN1
  • [20171101]rman to destination.txt
  • [20180224]expdp query 写法问题.txt
  • [2019/05/17]解决springboot测试List接口时JSON传参异常
  • [ANT] 项目中应用ANT