中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

webpack loader配置全流程詳解

發布時間:2020-08-13 09:03:19 來源:ITPUB博客 閱讀:173 作者:a1322674015 欄目:web開發

前言

1.主要目的為稍微梳理從配置到裝載的流程。另外詳解當然要加點源碼提升格調(本人菜鳥,有錯還請友善指正)

2.被的WebPack打包的文件,都被轉化為一個模塊,比如import './xxx/x.jpg'或require('./xxx/x.js')。至于具體實際怎么轉化,交由裝載機處理

3.下文會使用打字稿(勸退警告?)以方便說明有哪些選項和各個選項的值類型

配置語法解析

模塊屬性

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: /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;
}

這幾個函數比較繞,但總體來說難度不大。

這里再稍微總結幾點現象:

1.loader: './loader1!./loader2',如果在Rule.loader指明了兩個以以上裝載機,那么不可設置Rule.options,因為不知道該把這個選項傳給哪個裝載機,直接報錯

2.-loader不可省略,如babel!./loader的英文非法的,因為在webpack/lib/NormalModuleFactory.js440行左右,已經不再支持這種寫法,直接報錯叫你寫成babel-loader

3.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的中查看

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

普宁市| 牡丹江市| 基隆市| 克拉玛依市| 独山县| 沂源县| 酒泉市| 盐城市| 陇南市| 化隆| 桑植县| 苗栗市| 游戏| 广灵县| 潜山县| 郎溪县| 吉林市| 平顺县| 吐鲁番市| 印江| 砚山县| 马山县| 磴口县| 台湾省| 沾益县| 安达市| 新平| 靖江市| 科技| 许昌市| 玉田县| 淮滨县| 吉隆县| 大丰市| 高州市| 河东区| 阿城市| 宜昌市| 兴安县| 泸州市| 合阳县|