写点什么

webpack 系列之四 loader 详解 1

  • 2019-09-19
  • 本文字数:4475 字

    阅读完需:约 15 分钟

webpack系列之四loader详解1

在上一期我们 webpack 系列中,我们介绍了针对普通文件的 resolve 流程 和 loader 的 resolve 主流程。本期我们就来介绍 loader 的基本配置以及匹配规则。如有疑问或想要交流,欢迎在文末留言。


本篇来分析下 webpack loader 详细的分析部分,由于涉及内容比较多,所以总共分成三篇文章来分析:


  1. loader 的基本配置以及匹配规则

  2. loader 的解析执行详解

  3. loader 的实践

1.loader 的配置

webpack 对于一个 module 所使用的 loader 对开发者提供了 2 种使用方式:

webpack config 配置形式

形如:


// webpack.config.jsmodule.exports = {  ...  module: {    rules: [{      test: /.vue$/,      loader: 'vue-loader'    }, {      test: /.scss$/,      use: [        'vue-style-loader',        'css-loader',        {          loader: 'sass-loader',          options: {            data: '$color: red;'          }        }      ]    }]  }  ...}
复制代码

inline 内联形式

// moduleimport a from 'raw-loader!../../utils.js'
复制代码


2 种不同的配置形式,在 webpack 内部有着不同的解析方式。此外,不同的配置方式也决定了最终在实际加载 module 过程中不同 loader 之间相互的执行顺序等。

2.loader 的匹配

在讲 loader 的匹配过程之前,首先从整体上了解下 loader 在整个 webpack 的 workflow 过程中出现的时机。



在一个 module 构建过程中,首先根据 module 的依赖类型(例如 NormalModuleFactory)调用对应的构造函数来创建对应的模块。在创建模块的过程中(new NormalModuleFactory()),会根据开发者的 webpack.config 当中的 rules 以及 webpack 内置的 rules 规则实例化 RuleSet 匹配实例,这个 RuleSet 实例在 loader 的匹配过滤过程中非常的关键,具体的源码解析可参见 Webpack Loader Ruleset 匹配规则解析。实例化 RuleSet 后,还会注册 2 个钩子函数:


class NormalModuleFactory {  ...  // 内部嵌套 resolver 的钩子,完成相关的解析后,创建这个 normalModule  this.hooks.factory.tap('NormalModuleFactory', () => (result, callback) => { ... })  // 在 hooks.factory 的钩子内部进行调用,实际的作用为解析构建一共 module 所需要的 loaders 及这个 module 的相关构建信息(例如获取 module 的 packge.json等)  this.hooks.resolver.tap('NormalModuleFactory', () => (result, callback) => { ... })  ...}
复制代码


当 NormalModuleFactory 实例化完成后,并在 compilation 内部调用这个实例的 create 方法开始真实开始创建这个 normalModule。首先调用 hooks.factory 获取对应的钩子函数,接下来就调用 resolver 钩子(hooks.resolver)进入到了 resolve 的阶段,在真正开始 resolve loader 之前,首先就是需要匹配过滤找到构建这个 module 所需要使用的所有的 loaders。首先进行的是对于 inline loaders 的处理:


// NormalModuleFactory.js// 是否忽略 preLoader 以及 normalLoaderconst noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!");// 是否忽略 normalLoaderconst noAutoLoaders =  noPreAutoLoaders || requestWithoutMatchResource.startsWith("!");// 忽略所有的 preLoader / normalLoader / postLoaderconst noPrePostAutoLoaders = requestWithoutMatchResource.startsWith("!!");// 首先解析出所需要的 loader,这种 loader 为内联的 loaderlet elements = requestWithoutMatchResource  .replace(/^-?!+/, "")  .replace(/!!+/g, "!")  .split("!");let resource = elements.pop(); // 获取资源的路径elements = elements.map(identToLoaderRequest); // 获取每个loader及对应的options配置(将inline loader的写法变更为module.rule的写法)
复制代码


首先是根据模块的路径规则,例如模块的路径是以这些符号开头的 ! / -! / !! 来判断这个模块是否只是使用 inline loader,或者剔除掉 preLoader, postLoader 等规则:


  • ! 忽略 webpack.config 配置当中符合规则的 normalLoader

  • -! 忽略 webpack.config 配置当中符合规则的 preLoader/normalLoader

  • !! 忽略 webpack.config 配置当中符合规则的 postLoader/preLoader/normalLoader


这几个匹配规则主要适用于在 webpack.config 已经配置了对应模块使用的 loader,但是针对一些特殊的 module,你可能需要单独的定制化的 loader 去处理,而不是走常规的配置,因此可以使用这些规则来进行处理。


接下来将所有的 inline loader 转化为数组的形式,例如:


import 'style-loader!css-loader!stylus-loader?a=b!../../common.styl'
复制代码


最终 inline loader 统一格式输出为:


[{  loader: 'style-loader',  options: undefined}, {  loader: 'css-lodaer',  options: undefined}, {  loader: 'stylus-loader',  options: '?a=b'}]
复制代码


对于 inline loader 的处理便是直接对其进行 resolve,获取对应 loader 的相关信息:


asyncLib.parallel([  callback =>     this.resolveRequestArray(      contextInfo,      context,      elements,      loaderResolver,      callback    ),  callback => {    // 对这个 module 进行 resolve    ...    callack(null, {      resouceResolveData, // 模块的基础信息,包含 descriptionFilePath / descriptionFileData 等(即 package.json 等信息)      resource // 模块的绝对路径    })  }], (err, results) => {  const loaders = results[0] // 所有内联的 loaders  const resourceResolveData = results[1].resourceResolveData; // 获取模块的基本信息  resource = results[1].resource; // 模块的绝对路径  ...    // 接下来就要开始根据引入模块的路径开始匹配对应的 loaders  let resourcePath =    matchResource !== undefined ? matchResource : resource;  let resourceQuery = "";  const queryIndex = resourcePath.indexOf("?");  if (queryIndex >= 0) {    resourceQuery = resourcePath.substr(queryIndex);    resourcePath = resourcePath.substr(0, queryIndex);  }  // 获取符合条件配置的 loader,具体的 ruleset 是如何匹配的请参见 ruleset 解析(https://github.com/CommanderXL/Biu-blog/issues/30)  const result = this.ruleSet.exec({    resource: resourcePath, // module 的绝对路径    realResource:      matchResource !== undefined        ? resource.replace(/\?.*/, "")        : resourcePath,    resourceQuery, // module 路径上所带的 query 参数    issuer: contextInfo.issuer, // 所解析的 module 的发布者    compiler: contextInfo.compiler   });  // result 为最终根据 module 的路径及相关匹配规则过滤后得到的 loaders,为 webpack.config 进行配置的  // 输出的数据格式为:
/* [{ type: 'use', value: { loader: 'vue-style-loader', options: {} }, enforce: undefined // 可选值还有 pre/post 分别为 pre-loader 和 post-loader }, { type: 'use', value: { loader: 'css-loader', options: {} }, enforce: undefined }, { type: 'use', value: { loader: 'stylus-loader', options: { data: '$color red' } }, enforce: undefined }] */
const settings = {}; const useLoadersPost = []; // post loader const useLoaders = []; // normal loader const useLoadersPre = []; // pre loader for (const r of result) { if (r.type === "use") { // postLoader if (r.enforce === "post" && !noPrePostAutoLoaders) { useLoadersPost.push(r.value); } else if ( r.enforce === "pre" && !noPreAutoLoaders && !noPrePostAutoLoaders ) { // preLoader useLoadersPre.push(r.value); } else if ( !r.enforce && !noAutoLoaders && !noPrePostAutoLoaders ) { // normal loader useLoaders.push(r.value); } } else if ( typeof r.value === "object" && r.value !== null && typeof settings[r.type] === "object" && settings[r.type] !== null ) { settings[r.type] = cachedMerge(settings[r.type], r.value); } else { settings[r.type] = r.value; } // 当获取到 webpack.config 当中配置的 loader 后,再根据 loader 的类型进行分组(enforce 配置类型) // postLoader 存储到 useLoaders 内部 // preLoader 存储到 usePreLoaders 内部 // normalLoader 存储到 useLoaders 内部 // 这些分组最终会决定加载一个 module 时不同 loader 之间的调用顺序
// 当分组过程进行完之后,即开始 loader 模块的 resolve 过程 asyncLib.parallel([ [ // resolve postLoader this.resolveRequestArray.bind( this, contextInfo, this.context, useLoadersPost, loaderResolver ), // resove normal loaders this.resolveRequestArray.bind( this, contextInfo, this.context, useLoaders, loaderResolver ), // resolve preLoader this.resolveRequestArray.bind( this, contextInfo, this.context, useLoadersPre, loaderResolver ) ], (err, results) => { ... // results[0] -> postLoader // results[1] -> normalLoader // results[2] -> preLoader // 这里将构建 module 需要的所有类型的 loaders 按照一定顺序组合起来,对应于: // [postLoader, inlineLoader, normalLoader, preLoader] // 最终 loader 所执行的顺序对应为: preLoader -> normalLoader -> inlineLoader -> postLoader // 不同类型 loader 上的 pitch 方法执行的顺序为: postLoader.pitch -> inlineLoader.pitch -> normalLoader.pitch -> preLoader.pitch (具体loader内部执行的机制后文会单独讲解) loaders = results[0].concat(loaders, results[1], results[2]);
process.nextTick(() => { ... // 执行回调,创建 module }) } ]) }})
复制代码


简单总结下匹配的流程就是:


首先处理 inlineLoaders,对其进行解析,获取对应的 loader 模块的信息,接下来利用 ruleset 实例上的匹配过滤方法对 webpack.config 中配置的相关 loaders 进行匹配过滤,获取构建这个 module 所需要的配置的的 loaders,并进行解析,这个过程完成后,便进行所有 loaders 的拼装工作,并传入创建 module 的回调中。


本文转载自公众号滴滴技术(ID:didi_tech)。


原文链接:


https://mp.weixin.qq.com/s/CdMGRVPO9KxYr1F-dVJkYw


2019-09-19 15:583300

评论

发布
暂无评论
发现更多内容

如何做一次完美的 ABTest?

vivo互联网技术

数据分析 AB testing实战

有了“质量墙”,程序员再也没有秃头的烦恼

华为云开发者联盟

程序员 软件 代码审查 项目 代码

API接口管理平台YAPI的搭建

Man

DevOps APi设计 YAPI

linux上强大的字符串匹配工具详解-grep

X先生

Shell grep

Java8——Stream流

Java旅途

小白如何学习操作系统?

苹果看辽宁体育

操作系统

简单的了解一下K8S,并搭建自己的集群

leonsh

Kubernetes 微服务

GitHub Actions和mp-ci助力微信小程序持续集成

neo

微信小程序 taro GitHub CI/CD

[架构师训练营] Week03 - 学习总结

谭方敏

第六期总结

GAC·DU

Java HashMap 的那么多为什么

多选参数

Java Java源码

我从LongAdder中窥探到了高并发的秘籍,上面只写了两个字...

why技术

jdk 高并发 LongAdder

前端杂记-回调地狱

阡陌r

JavaScript 回调地狱

有趣的条漫版 HashMap,25岁大爷都能看懂

古时的风筝

hashmap

架构师训练营week06 作业

GunShotPanda

架构师训练营week06 学习总结

GunShotPanda

重读vue2.0风格指南,我整理了这些关键规则

前端有的玩

Java Vue 代码规范

数据分析师完整的指标体系构建 (干货)

博文视点Broadview

数据挖掘 读书笔记 数据分析 数据 求职

技术科普丨服务发现和负载均衡的来龙去脉

华为云开发者联盟

负载均衡 微服务 开发者工具 服务端 服务

腾讯面了五轮,面委挂了,挂的原因让大家唏嘘...

程序员生活志

腾讯 面试

优傲机器人以人机协作助力中国“智能制造”落地

Geek_116789

一文快速掌握华为云IPv6基础知识及使用指南

华为云开发者联盟

物联网中台 物联网 网络 华为云

第六期作业

GAC·DU

计算机网络基础(四)---网络层-ARP协议与RARP协议

书旅

laravel 计算机网络 网络协议 计算机基础 网络层

解决方案|智能消防预警系统突破高层楼房限制

华为云开发者联盟

AI 物联网 边缘计算 华为云

猿灯塔:spring Boot Starter开发及源码刨析(七)

猿灯塔

探索无限潜能,英特尔神经拟态计算除了有“嗅觉”还能有“触觉”

最新动态

变性手术后,产品总监和当当网打起了官司

赵新龙

法律 判决书 案例

知乎,挣钱?果然有长尾效应

非著名程序员

程序员 副业 副业赚钱 知乎 好物推荐

信创舆情一线--十五部门印发指导意见进一步促进服务型制造发展

统小信uos

如何帮助技术员工高效成长?这几家企业的做法值得借鉴

极客时间企业版

研发管理 研发团队培训

webpack系列之四loader详解1_文化 & 方法_肖磊_InfoQ精选文章