我的编程空间,编程开发者的网络收藏夹
学习永远不晚

webpack loader配置全流程详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

webpack loader配置全流程详解

前言

主要目的为稍微梳理从配置到装载的流程。另外详解当然要加点源码提升格调(本人菜鸟,有错还请友善指正)

被的WebPack打包的文件,都被转化为一个模块,比如import './xxx/x.jpg'或require('./xxx/x.js')。至于具体实际怎么转化,交由装载机处理

下文会使用打字稿(劝退警告?)以方便说明有哪些选项和各个选项的值类型

配置语法解析

模块属性

module.exports = {    ...    module: {        noParse: /jquery/,        rules: [            {                test: /\.js/,                exclude: /node_modules/,                use:[                    {                        loader: './loader1.js?num=1',                        options: {myoptions:false},                    },                    "./loader2.js?num=2",                ]            },            {                test: /\.js/,                include: /class="lazy" data-src/,                loader: './loader1.js!./loader2.js',            },        ]    }}


上述是展示常见的配置写法.webpack为其选项都编写了打字稿声明,这个模块属性的声明在的WebPack /声明中可见:

export interface ModuleOptions {    // 一般下面这两个    noParse?: RegExp[] | RegExp | Function | string[] | string;    rules?: RuleSetRules;        // 这些...已被废弃,即将被删除,不用看    defaultRules?: RuleSetRules;    exprContextCritical?: boolean;    exprContextRecursive?: boolean;    exprContextRegExp?: boolean | RegExp;    exprContextRequest?: string;    strictExportPresence?: boolean;    strictThisContextOnImports?: boolean;    unknownContextCritical?: boolean;    unknownContextRecursive?: boolean;    unknownContextRegExp?: boolean | RegExp;    unknownContextRequest?: string;    unsafeCache?: boolean | Function;    wrappedContextCritical?: boolean;    wrappedContextRecursive?: boolean;    wrappedContextRegExp?: RegExp;}

noParse 用于让的WebPack跳过对这些文件的转化,也就是他们不会被加载程序所处理(但还是会被打包并输出到DIST目录)

rules 核心配置,见下文

module.rules属性

module.rules类型是RuleSetRule[],请继续的WebPack /声明查看其打字稿,有哪些属性,属性类型一目了然。

注意RuleSetConditionsRecursive这个东西在另外一个文件声明,是interface RuleSetConditionsRecursive extends Array<import("./declarations/WebpackOptions").RuleSetCondition> {},其实就是export type RuleSetConditionsRecursive = RuleSetCondition[];,代表一个RuleSetCondition数组

意义直接贴中文文档:模块。

好了,上面基本是搬运打字稿声明,结合文档基本能知道有哪些属性,属性的类型和含义。下面结合源码对文档一些难以理解的地方补充说明。

 正文

规则集

规则的规范化(类型收敛)

由上可知一个规则对象,其属性类型有多种可能,所以应该对其规范化,底层减少代码的大量typeof等判断。这是由RuleSet.js进行规范化的。下面是经过规则集处理后的一个规则对象大致形式:

// rule 对象规范化后的形状应该是:{resource: function(),resourceQuery: function(),compiler: function(),issuer: function(),use: [{loader: string,options: string | object, // 源码的注释可能是历史遗留原因,options也可为object类型<any>: <any>} // 下文称呼这个为use数组的单个元素为 loader对象,规范化后它一般只有loader和options属性],rules: [<rule>],oneOf: [<rule>],<any>: <any>,}

rules状语从句:oneOf的英文用来嵌套的,里面的也是规范过的规则对象。

它这里的四个函数是的WebPack用来判断是否需要把文件内容交给装载器处理的。如的WebPack遇到了import './a.js',那么rule.resource('f:/a.js')===true时会才把文件交由规则中指定的装载机去处理,resourceQuery等同理。

的这里的传入参数'f:/a.js'就是官网所说的

 条件已经两个输入值:

 资源:请求文件的绝对路径。它已经根据resolve规则解析。issuer :被请求资源(请求的资源)的模块文件的绝对路径。是导入时的位置。

首先要做的是把Rule.loader, ,Rule.options(Rule.query已废弃,但尚未删除),移动全部到Rule.use数组元素的对象里。主要这由static normalizeRule(rule, refs, ident)函数处理,代码主要是处理各种“简写”,把值搬运到装载器对象,做一些报错处理,难度不大看一下即可,下面挑它里面的“条件函数”规范化来说一说。

Rule.resource规范化

由上可知这是一个“条件函数”,它是根据我们的配置中的test,include,exclude,resource规范化而生成的源码180多行中:

if (rule.test || rule.include || rule.exclude) {    checkResourceSource("test + include + exclude");    condition = {        test: rule.test,        include: rule.include,        exclude: rule.exclude    };    try {        newRule.resource = RuleSet.normalizeCondition(condition);    } catch (error) {        throw new Error(RuleSet.buildErrorMessage(condition, error));    }}if (rule.resource) {    checkResourceSource("resource");    try {        newRule.resource = RuleSet.normalizeCondition(rule.resource);    } catch (error) {        throw new Error(RuleSet.buildErrorMessage(rule.resource, error));    }}

中文档说Rule.test的英文Rule.resource.test的简写,实际就是这串代码。

checkResourceSource用来检查是否重复配置,即文档中提到的:你如果提供了一个Rule.test选项对话,就不能再提供Rule.resource

求最后RuleSet.normalizeCondition生成一个“条件函数”,如下:

static normalizeCondition(condition) {    if (!condition) throw new Error("Expected condition but got falsy value");    if (typeof condition === "string") {        return str => str.indexOf(condition) === 0;    }    if (typeof condition === "function") {        return condition;    }    if (condition instanceof RegExp) {        return condition.test.bind(condition);    }    if (Array.isArray(condition)) {        const items = condition.map(c => RuleSet.normalizeCondition(c));        return orMatcher(items);    }    if (typeof condition !== "object") {        throw Error(            "Unexcepted " +                typeof condition +                " when condition was expected (" +                condition +                ")"        );    }    const matchers = [];    Object.keys(condition).forEach(key => {        const value = condition[key];        switch (key) {            case "or":            case "include":            case "test":                if (value) matchers.push(RuleSet.normalizeCondition(value));                break;            case "and":                if (value) {                    const items = value.map(c => RuleSet.normalizeCondition(c));                    matchers.push(andMatcher(items));                }                break;            case "not":            case "exclude":                if (value) {                    const matcher = RuleSet.normalizeCondition(value);                    matchers.push(notMatcher(matcher));                }                break;            default:                throw new Error("Unexcepted property " + key + " in condition");        }    });    if (matchers.length === 0) {        throw new Error("Excepted condition but got " + condition);    }    if (matchers.length === 1) {        return matchers[0];    }    return andMatcher(matchers);}

这串代码主要就是根据字符串,正则表达式,对象,功能类型来生成不同的“条件函数”,难度不大。

notMatcher,orMatcher,andMatcher这三个是辅助函数,看名字就知道了,实现上非常简单,不贴源码了。有什么不明白的逻辑,代入进去跑一跑就知道了

规则使用规范化

我们接下来要把Rule.use给规范分类中翻译上面提到的那种形式,即让装载机只对象保留loader状语从句:options这两个属性(当然,并不是它一定只有这两个属性)源码如下:

static normalizeUse(use, ident) {    if (typeof use === "function") {        return data => RuleSet.normalizeUse(use(data), ident);    }    if (Array.isArray(use)) {        return use            .map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`))            .reduce((arr, items) => arr.concat(items), []);    }    return [RuleSet.normalizeUseItem(use, ident)];}static normalizeUseItemString(useItemString) {    const idx = useItemString.indexOf("?");    if (idx >= 0) {        return {            loader: useItemString.substr(0, idx),            options: useItemString.substr(idx + 1)        };    }    return {        loader: useItemString,        options: undefined    };}static normalizeUseItem(item, ident) {    if (typeof item === "string") {        return RuleSet.normalizeUseItemString(item);    }    const newItem = {};    if (item.options && item.query) {        throw new Error("Provided options and query in use");    }    if (!item.loader) {        throw new Error("No loader specified");    }    newItem.options = item.options || item.query;    if (typeof newItem.options === "object" && newItem.options) {        if (newItem.options.ident) {            newItem.ident = newItem.options.ident;        } else {            newItem.ident = ident;        }    }    const keys = Object.keys(item).filter(function(key) {        return !["options", "query"].includes(key);    });    for (const key of keys) {        newItem[key] = item[key];    }    return newItem;}

这几个函数比较绕,但总体来说难度不大。

这里再稍微总结几点现象:

loader: './loader1!./loader2',如果在Rule.loader指明了两个以以上装载机,那么不可设置Rule.options,因为不知道该把这个选项传给哪个装载机,直接报错

-loader不可省略,如babel!./loader的英文非法的,因为在webpack/lib/NormalModuleFactory.js440行左右,已经不再支持这种写法,直接报错叫你写成babel-loader

loader: './loader1?num1=1&num2=2'将被处理成{loader: './loader', options: 'num=1&num=2'},以?进行了字符串分割,最终处理成规范化装载机对象

规则集规范化到此结束,有兴趣的可以继续围观源码的高管方法和构造函数

装载机

接下来算是番外,讨论各种装载机如何读取我们配置的对象。

**属性在的WebPack的传递与处理选项**

首先一个装载机就是简单的导出一个函数即可,比如上面举例用到的

loader1.js:module.exports = function (content){    console.log(this)    console.log(content)    return content}

这个函数里面的这个被绑定到一个loaderContext(loader上下文)中,官方api:loader API。

直接把这个loader1.js加入到配置文件webpack.config.js里面即可,在编译时他就会打印出一些东西。

简单而言,就是在装载机中,可以我们通过this.query来访问到规范化装载机对象options属性。比如{loader: './loader1.js', options: 'num1=1&num=2'},那么this.query === '?num1=1&num=2'。

问题来了,这个问号哪里来的如果它是一个对象?

的WebPack通过装载机的领先者来执行装载机,这个问题可以去loader-runner/lib/LoaderRunner.js,在createLoaderObject函数中有这么一段:

if (obj.options === null)    obj.query = "";else if (obj.options === undefined)    obj.query = "";else if (typeof obj.options === "string")    obj.query = "?" + obj.options;else if (obj.ident) {    obj.query = "??" + obj.ident;}else if (typeof obj.options === "object" && obj.options.ident)    obj.query = "??" + obj.options.ident;else    obj.query = "?" + JSON.stringify(obj.options);

在以及runLoaders函数里面的这段:

Object.defineProperty(loaderContext, "query", {    enumerable: true,    get: function() {        var entry = loaderContext.loaders[loaderContext.loaderIndex];        return entry.options && typeof entry.options === "object" ? entry.options : entry.query;    }});

总结来说,当选项存在且是一个对象时,那么this.query就是这个对象;如果选项是一个字符串,那么this.query等于一个问号+这个字符串

多数装载机读取选项的方法

const loaderUtils=require('loader-utils')module.exports = function (content){    console.log(loaderUtils.getOptions(this))    return content}

借助架utils的读取那么接下来走进loaderUtils.getOptions看看:

const query = loaderContext.query;if (typeof query === 'string' && query !== '') {  return parseQuery(loaderContext.query);}if (!query || typeof query !== 'object') {  return null;}return query;

这里只复制了关键代码,它主要是做一些简单判断,对字符串的核心转换在parseQuery上,接着看:

const JSON5 = require('json5');function parseQuery(query) {  if (query.substr(0, 1) !== '?') {    throw new Error(      "A valid query string passed to parseQuery should begin with '?'"    );  }  query = query.substr(1);  if (!query) {    return {};  }  if (query.substr(0, 1) === '{' && query.substr(-1) === '}') {    return JSON5.parse(query);  }  const queryArgs = query.split(/[,&]/g);  const result = {};  queryArgs.forEach((arg) => {    const idx = arg.indexOf('=');    if (idx >= 0) {      let name = arg.substr(0, idx);      let value = decodeURIComponent(arg.substr(idx + 1));      if (specialValues.hasOwnProperty(value)) {        value = specialValues[value];      }      if (name.substr(-2) === '[]') {        name = decodeURIComponent(name.substr(0, name.length - 2));        if (!Array.isArray(result[name])) {          result[name] = [];        }        result[name].push(value);      } else {        name = decodeURIComponent(name);        result[name] = value;      }    } else {      if (arg.substr(0, 1) === '-') {        result[decodeURIComponent(arg.substr(1))] = false;      } else if (arg.substr(0, 1) === '+') {        result[decodeURIComponent(arg.substr(1))] = true;      } else {        result[decodeURIComponent(arg)] = true;      }    }  });  return result;}

使用了json5库,以及自己的一套参数的转换。

总结来说,只要你能确保自己使用的装载器是通过loader-utils来获取选项对象的,那么你可以直接给选项写成如下字符串(inline loader中常用,如import 'loader1?a=1&b=2!./a.js'):

options: "{a: '1', b: '2'}" // 非json,是json5格式字符串,略有出入,请右转百度options: "list[]=1&list=2[]&a=1&b=2" // http请求中常见的url参数部分

更多示例可在的WebPack /架utils的中查看

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

webpack loader配置全流程详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

webpack loader配置全流程详解

前言1.主要目的为稍微梳理从配置到装载的流程。另外详解当然要加点源码提升格调(本人菜鸟,有错还请友善指正)2.被的WebPack打包的文件,都被转化为一个模块,比如import ./xxx/x.jpg或require(./xxx/x.js)
2023-06-03

webpack自定义loader全面详解

这篇文章主要为大家介绍了webpack自定义loader全面详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-04

webpack中的optimization配置示例详解

这篇文章主要介绍了webpack中的optimization配置详解,主要就是根据不同的策略来分割打包出来的bundle,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2023-02-23

Redis Sentinel服务配置流程(详解)

1、Redis Sentinel服务配置1.1简介 Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务: 监控(Monitoring): Sentinel 会不断地检查你的主
2022-06-04

SpringSecurity使用LambdaDSL配置流程详解

SpringSecurity5.2对LambdaDSL语法的增强,允许使用lambda配置HttpSecurity、ServerHttpSecurity,重要提醒,之前的配置方法仍然有效。lambda的添加旨在提供更大的灵活性,但是用法是可选的。让我们看一下HttpSecurity的lambda配置与以前的配置样式相比
2023-02-15

JavaScript自定义Webpack配置实现流程介绍

本系列主要整理前端面试中需要掌握的知识点。本节介绍webpack如何优化前端性能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2022-11-13

C++中LibCurl库使用流程及配置详解

本指南详细介绍了C++中LibCurl库的使用流程和配置。LibCurl是一款网络传输库,可用于各种协议。安装涉及下载源代码、解压缩、配置(指定安装目录、启用SSL等)和构建。配置涉及设置编译选项和运行时参数(指定URL、设置回调函数等)。用法示例代码演示了如何发起HTTPGET请求。高级功能包括多线程、SSL/TLS、Cookie支持和身份验证。最佳实践包括错误处理、资源管理、线程安全和性能优化。
C++中LibCurl库使用流程及配置详解
2024-04-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录