webpack rules与loaders有什么区别

默认分类 未结 1 1123
0天使折翼0
0天使折翼0 2023-03-17 17:32
相关标签:
1条回答
  • 2023-03-17 18:17

    这篇文章主要介绍了webpack源码之compile流程-rules参数处理技巧的相关知识,需要的朋友参考下吧

    上篇文章给大家介绍了细说webpack源码之compile流程-rules参数处理技巧(1), 细说webpack源码之compile流程-入口函数run

    大家可以点击查看。

    第一步处理rule为字符串,直接返回一个包装类,很简单看注释就好了。

    test

      然后处理test、include、exclude,如下:

    if (rule.test || rule.include || rule.exclude) { // 标记使用参数 checkResourceSource("test + include + exclude"); // 没有就是undefined 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)); }}

      checkResourceSource直接看源码:

    let resourceSource;// ...function checkResourceSource(newSource) { // 第一次直接跳到后面赋值 if (resourceSource && resourceSource !== newSource) throw new Error(RuleSet.buildErrorMessage(rule, new Error("Rule can only have one resource source (provided " + newSource + " and " + resourceSource + ")"))); resourceSource = newSource;}

      这个用于检测配置来源的唯一性,后面会能看到作用,同样作用的还有checkUseSource方法。

      随后将三个参数包装成一个对象传入normalizeCondition方法,该方法对常规参数进行函数包装:

    class RuleSet { constructor(rules) { /**/ }; 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; } // 正则表达式返回一个正则的test函数 if (condition instanceof RegExp) { return condition.test.bind(condition); } // 数组map递归处理 有一个满足返回true 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 = []; // 对象会对每个值进行函数包装弹入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); }}

      这里用js的rules做案例,看这个方法的返回:

    class RuleSet { constructor(rules) { /**/ }; /* Example: { test: /.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test')] } */ /* condition: { test: /.js$/, include: ['d:workspacesrc', 'd:workspace est'], exclude: undefined } */ static normalizeCondition(condition) { // include返回类似于 [(str) => str.indexOf('d:workspacesrc') === 0,...] 的函数 if (typeof condition === "string") { return str => str.indexOf(condition) === 0; } // test参数返回了 /.js$/.test 函数 if (condition instanceof RegExp) { return condition.test.bind(condition); } // include为数组 if (Array.isArray(condition)) { const items = condition.map(c => RuleSet.normalizeCondition(c)); return orMatcher(items); } const matchers = []; // 解析出['test','include','exclude'] Object.keys(condition).forEach(key => { const value = condition[key]; switch (key) { // 此value为一个数组 case "include": case "test": if (value) matchers.push(RuleSet.normalizeCondition(value)); break; // undefined跳过 case "exclude": if (value) { /**/ } break; default: throw new Error("Unexcepted property " + key + " in condition"); } }); return andMatcher(matchers); }}

      这里继续看orMatcher、andMatcher函数的处理:

    function orMatcher(items) { // 当一个函数被满足条件时返回true return function(str) { for (let i = 0; i < items.length; i++) { if (items[i](str)) return true; } return false; };}function andMatcher(items) { // 当一个条件不满足时返回false return function(str) { for (let i = 0; i < items.length; i++) { if (!items[i](str)) return false; } return true; };}

      从字面意思也可以理解函数作用,比如说这里的include被包装成一个orMatcher函数,传入的字符串无论是以数组中任何一个元素开头都会返回true,andMatcher以及未出现的notMatcher同理。

    resource

      下面是对resource参数的处理,源码如下:

    if (rule.resource) { // 如果前面检测到了test || include || exclude参数 这里会报错 checkResourceSource("resource"); try { newRule.resource = RuleSet.normalizeCondition(rule.resource); } catch (error) { throw new Error(RuleSet.buildErrorMessage(rule.resource, error)); }}

      可以看出这个参数与前面那个是互斥的,应该是老版API,下面两种方式实现是一样的:

    /*方式1: rules:[ { test: /.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test')] } ]*//*方式2: rules:[ { resource:{ test: /.js$/, include: [resolve('src'), resolve('test')] exclude: undefined } } ]*/

      接下来的resourceQuery、compiler、issuer先跳过,脚手架没有,不知道怎么写。

    loader

      下面是另一块主要配置loader(s),这里loader与loaders作用一样的,当初还头疼啥区别:

    const loader = rule.loaders || rule.loader;// 单loader情况if (typeof loader === "string" && !rule.options && !rule.query) { checkUseSource("loader"); newRule.use = RuleSet.normalizeUse(loader.split("!"), ident);}// loader配合options或query出现else if (typeof loader === "string" && (rule.options || rule.query)) { checkUseSource("loader + options/query"); newRule.use = RuleSet.normalizeUse({ loader: loader, options: rule.options, query: rule.query }, ident);}// options与query同时出现报错else if (loader && (rule.options || rule.query)) { throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query cannot be used with loaders (use options for each array item)")));}/* 处理这种愚蠢用法时: { test: /.css$/, loader: [{ loader: 'less-loader' }, { loader: 'css-loader' }] }*/else if (loader) { checkUseSource("loaders"); newRule.use = RuleSet.normalizeUse(loader, ident);}// 单独出现options或者query报错else if (rule.options || rule.query) { throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query provided without loader (use loader + options)")));}

      之前举例的babel-loader就是第一种单loader配置,这里使用vue-loader嵌套的css配置作为示例。

      首先normalizeUse方法如下:

    static normalizeUse(use, ident) { // 单loader字符串 if (Array.isArray(use)) { return use // 如果是单loader情况这里会返回[[loader1...],[loader2...]] .map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`)) // 扁平化后变成[loader1,loader2] .reduce((arr, items) => arr.concat(items), []); } // 对象或字符串 return [RuleSet.normalizeUseItem(use, ident)];};

      先讲解有options或者query的模式,这里会把参数包装一个对象传入normalizeUse方法:

    loader && (options || query)

    // indet => 'ref-'static normalizeUseItem(item, ident) { if (typeof item === "function") return item; 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; // 防止options:null的情况 if (typeof newItem.options === "object" && newItem.options) { // 这里只是为了处理ident参数 if (newItem.options.ident) newItem.ident = newItem.options.ident; else newItem.ident = ident; } // 取出loader参数 const keys = Object.keys(item).filter(function(key) { return ["options", "query"].indexOf(key) < 0; }); keys.forEach(function(key) { newItem[key] = item[key]; }); /* newItem = { loader:'原字符串', ident:'ref-', (或自定义) options:{...} } */ return newItem;}

      比起对test的处理,这里就简单的多,简述如下:

    1、尝试取出options(query)中的ident参数,赋值给newItem的ident,没有就赋值为默认的ref-

    2、取出对象中不是options、query的键,赋值给newItem,这里传进来的键只有三个,剩下的就是loader,这个filter是逗我???

    3、返回newItem对象

      总之,不知道为什么防止什么意外情况而写出来的垃圾代码,这段代码其实十分简单。

    单loader

      第二种情况是单字符串,会对'!'进行切割调用map方法处理,再调用reduce方法扁平化为一个数组,直接看normalizeUseItemString方法:

    /*Example:{ test: /.css$/, loader: 'css-loader?{opt:1}!style-loader'}

    返回:

    [{loader:'css-loader',options:{opt:1}},{loader:'style-loader'}]*/static normalizeUseItemString(useItemString) { // 根据'?'切割获取loader的参数 const idx = useItemString.indexOf("?"); if (idx >= 0) { return { loader: useItemString.substr(0, idx), // 后面的作为options返回 options: useItemString.substr(idx + 1) }; } return { loader: useItemString };}

      这种就是串行调用loader的处理方式,代码很简单。

    Tips

      这里有一点要注意,一旦loader使用了串行调用方式,不要传options或者query参数,不然loader不会被切割解析!!!

      这两种方式只是不同的使用方法,最后的结果都是一样的。

    use

      下面是use参数的解析,估计因为这是老版的API,所以格式要求严格,处理比较随便:

    if (rule.use) { checkUseSource("use"); newRule.use = RuleSet.normalizeUse(rule.use, ident);}

      如果不用loader(s),可以用use替代,但是需要按照格式写,比如说上述串行简写的loader,在use中就需要这样写:

    /* { test:/.css$/, user:['css-loader','style-loader] }*/

      而对应options或query,需要这样写:

    /* { test:/.css$/, user:{ loader:'css-loader', options:'1' } }*/

      因为基本上不用了,所以这里简单看一下。

    rules、oneOfif (rule.rules) newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`);if (rule.oneOf) newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`);

      这两个用得少,也没啥难理解的。

      下一步是过滤出没有处理的参数,添加到newRuls上:

    const keys = Object.keys(rule).filter((key) => { return ["resource", "resourceQuery", "compiler", "test", "include", "exclude", "issuer", "loader", "options", "query", "loaders", "use", "rules", "oneOf"].indexOf(key) < 0;});keys.forEach((key) => { newRule[key] = rule[key];});

      基本上用到都是test、loader、options,暂时不知道有啥额外参数。

    ident

    // 防止rules:[]的情况if (Array.isArray(newRule.use)) { newRule.use.forEach((item) => { // ident来源于options/query的ident参数 if (item.ident) { refs[item.ident] = item.options; } });}

      最后这个地方是终于用到了传进来的纯净对象refs。

      如果在options中传了ident参数,会填充这个对象,key为ident值,value为对应的options。

      至此,所有rules的规则已经解析完毕,真是配置简单处理复杂。

    上面是我整理给大家的,希望今后会对大家有帮助。

    相关文章:

    在nodejs中如何实现websocket通信功能

    在js中如何实现绑定点击事件(详细教程)

    通过Vue如何实现SSR(详细教程)

    1. 为什么用 webpack? 他像 Browserify, 但是将你的应用打包为多个文件. 如果你的单页面应用有多个页面, 那么用户只从对应页面的代码. 当他么访问到另一个页面, 他们不需要重新通用的代码. 他在很多地方能替代 Grunt 跟 Gulp 因为他能够.

    我不太懂但是后盾人是专业的,里面的一线讲师亲自录制高清视频来帮大家解决一些不懂的问题,含金量高

    webpack1.x ->2.x 中的更改:mole.loaders 改成了 mole.rules旧的 loader 配置被更强大的 rules 系统取代,后者允许配置 loader 以及其他更多项。为了兼容旧版,mole.loaders 语法被保留,旧的属性名依然可以被解析。新的命名约定更易于理解并且是升级配置使用 mole.rules 的好理由。http://www.css88.com/doc/webpack2/guides/migrating/

    0 讨论(0)
提交回复