国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

手把手教你擼一個 Webpack Loader

sugarmo / 3074人閱讀

摘要:夾在中間的被鏈式調用,他們拿到上個的返回值,為下一個提供輸入。最終把返回值和傳給。前面我們說過,也是一個模塊,它導出一個函數,該函數的參數是的源模塊,處理后把返回值交給下一個。

文:小 boy(滬江網校Web前端工程師)

本文原創,轉載請注明作者及出處

經常逛 webpack 官網的同學應該會很眼熟上面的圖。正如它宣傳的一樣,webpack 能把左側各種類型的文件(webpack 把它們叫作「模塊」)統一打包為右邊被通用瀏覽器支持的文件。webpack 就像是魔術師的帽子,放進去一條絲巾,變出來一只白鴿。那這個「魔術」的過程是如何實現的呢?今天我們從 webpack 的核心概念之一 —— loader 來尋找答案,并著手實現這個「魔術」。看完本文,你可以:

知道 webpack loader 的作用和原理。

自己開發貼合業務需求的 loader。

什么是 Loader ?

在擼一個 loader 前,我們需要先知道它到底是什么。本質上來說,loader 就是一個 node 模塊,這很符合 webpack 中「萬物皆模塊」的思路。既然是 node 模塊,那就一定會導出點什么。在 webpack 的定義中,loader 導出一個函數,loader 會在轉換源模塊(resource)的時候調用該函數。在這個函數內部,我們可以通過傳入 this 上下文給 Loader API 來使用它們。回顧一下頭圖左邊的那些模塊,他們就是所謂的源模塊,會被 loader 轉化為右邊的通用文件,因此我們也可以概括一下 loader 的功能:把源模塊轉換成通用模塊。

Loader 怎么用 ?

知道它的強大功能以后,我們要怎么使用 loader 呢?

1. 配置 webpack config 文件

既然 loader 是 webpack 模塊,如果我們要使其生效,肯定離不開配置。我這里收集了三種配置方法,任你挑選。

單個 loader 的配置

增加 config.module.rules 數組中的規則對象(rule object)。

let webpackConfig = {
    //...
    module: {
        rules: [{
            test: /.js$/,
            use: [{
                //這里寫 loader 的路徑
                loader: path.resolve(__dirname, "loaders/a-loader.js"), 
                options: {/* ... */}
            }]
        }]
    }
}
多個 loader 的配置

增加 config.module.rules 數組中的規則對象以及 config.resolveLoader

let webpackConfig = {
    //...
    module: {
        rules: [{
            test: /.js$/,
            use: [{
                //這里寫 loader 名即可
                loader: "a-loader", 
                options: {/* ... */}
            }, {
                loader: "b-loader", 
                options: {/* ... */}
            }]
        }]
    },
    resolveLoader: {
        // 告訴 webpack 該去那個目錄下找 loader 模塊
        modules: ["node_modules", path.resolve(__dirname, "loaders")]
    }
}
其他配置

也可以通過 npm link 連接到你的項目里,這個方式類似 node CLI 工具開發,非 loader 模塊專用,本文就不多討論了。

2. 簡單上手

配置完成后,當你在 webpack 項目中引入模塊時,匹配到 rule (例如上面的 /.js$/)就會啟用對應的 loader (例如上面的 a-loader 和 b-loader)。這時,假設我們是 a-loader 的開發者,a-loader 會導出一個函數,這個函數接受的唯一參數是一個包含源文件內容的字符串。我們暫且稱它為「source」。

接著我們在函數中處理 source 的轉化,最終返回處理好的值。當然返回值的數量和返回方式依據 a-loader 的需求來定。一般情況下可以通過 return 返回一個值,也就是轉化后的值。如果需要返回多個參數,則須調用 this.callback(err, values...) 來返回。在異步 loader 中你可以通過拋錯來處理異常情況。Webpack 建議我們返回 1 至 2 個參數,第一個參數是轉化后的 source,可以是 string 或 buffer。第二個參數可選,是用來當作 SourceMap 的對象。

3. 進階使用

通常我們處理一類源文件的時候,單一的 loader是不夠用的(loader 的設計原則我們稍后講到)。一般我們會將多個 loader 串聯使用,類似工廠流水線,一個位置的工人(或機器)只干一種類型的活。既然是串聯,那肯定有順序的問題,webpack 規定 use 數組中 loader 的執行順序是從最后一個到第一個,它們符合下面這些規則:

順序最后的 loader 第一個被調用,它拿到的參數是 source 的內容

順序第一的 loader 最后被調用, webpack 期望它返回 JS 代碼,source map 如前面所說是可選的返回值。

夾在中間的 loader 被鏈式調用,他們拿到上個 loader 的返回值,為下一個 loader 提供輸入。

我們舉個例子:

webpack.config.js

    {
        test: /.js/,
        use: [
            "bar-loader",
            "mid-loader",
            "foo-loader"
        ]
    }

在上面的配置中:

loader 的調用順序是 foo-loader -> mid-loader -> bar-loader。

foo-loader 拿到 source,處理后把 JS 代碼傳遞給 mid,mid 拿到 foo 處理過的 “source” ,再處理之后給 bar,bar 處理完后再交給 webpack。

bar-loader 最終把返回值和 source map 傳給 webpack。

用正確的姿勢開發 Loader

了解了基本模式后,我們先不急著開發。所謂磨刀不誤砍柴工,我們先看看開發一個 loader 需要注意些什么,這樣可以少走彎路,提高開發質量。下面是 webpack 提供的幾點指南,它們按重要程度排序,注意其中有些點只適用特定情況。

1.單一職責

一個 loader 只做一件事,這樣不僅可以讓 loader 的維護變得簡單,還能讓 loader 以不同的串聯方式組合出符合場景需求的搭配。

2.鏈式組合

這一點是第一點的延伸。好好利用 loader 的鏈式組合的特型,可以收獲意想不到的效果。具體來說,寫一個能一次干 5 件事情的 loader ,不如細分成 5 個只能干一件事情的 loader,也許其中幾個能用在其他你暫時還沒想到的場景。下面我們來舉個例子。

假設現在我們要實現通過 loader 的配置和 query 參數來渲染模版的功能。我們在 “apply-loader” 里面實現這個功能,它負責編譯源模版,最終輸出一個導出 HTML 字符串的模塊。根據鏈式組合的規則,我們可以結合另外兩個開源 loader:

jade-loader 把模版源文件轉化為導出一個函數的模塊。

apply-loader 把 loader options 傳給上面的函數并執行,返回 HTML 文本。

html-loader 接收 HTMl 文本文件,轉化為可被引用的 JS 模塊。

事實上串聯組合中的 loader 并不一定要返回 JS 代碼。只要下游的 loader 能有效處理上游 loader 的輸出,那么上游的 loader 可以返回任意類型的模塊。
3.模塊化

保證 loader 是模塊化的。loader 生成模塊需要遵循和普通模塊一樣的設計原則。

4.無狀態

在多次模塊的轉化之間,我們不應該在 loader 中保留狀態。每個 loader 運行時應該確保與其他編譯好的模塊保持獨立,同樣也應該與前幾個 loader 對相同模塊的編譯結果保持獨立。

5.使用 Loader 實用工具

請好好利用 loader-utils 包,它提供了很多有用的工具,最常用的一個就是獲取傳入 loader 的 options。除了 loader-utils 之外包還有 schema-utils 包,我們可以用 schema-utils 提供的工具,獲取用于校驗 options 的 JSON Schema 常量,從而校驗 loader options。下面給出的例子簡要地結合了上面提到的兩個工具包:

import { getOptions } from "loader-utils";
import { validateOptions } from "schema-utils";

const schema = {
  type: object,
  properties: {
    test: {
      type: string
    }
  }
}

export default function(source) {
    const options = getOptions(this);

    validateOptions(schema, options, "Example Loader");

    // 在這里寫轉換 source 的邏輯 ...
    return `export default ${ JSON.stringify(source) }`;
};
loader 的依賴

如果我們在 loader 中用到了外部資源(也就是從文件系統中讀取的資源),我們必須聲明這些外部資源的信息。這些信息用于在監控模式(watch mode)下驗證可緩存的 loder 以及重新編譯。下面這個例子簡要地說明了怎么使用 addDependency 方法來做到上面說的事情。
loader.js:

import path from "path";

export default function(source) {
    var callback = this.async();
    var headerPath = path.resolve("header.js");

    this.addDependency(headerPath);

    fs.readFile(headerPath, "utf-8", function(err, header) {
        if(err) return callback(err);
        //這里的 callback 相當于異步版的 return
        callback(null, header + "
" + source);
    });
};
模塊依賴

不同的模塊會以不同的形式指定依賴。比如在 CSS 中我們使用 @importurl(...) 聲明來完成指定,而我們應該讓模塊系統解析這些依賴。

如何讓模塊系統解析不同聲明方式的依賴呢?下面有兩種方法:

把不同的依賴聲明統一轉化為 require 聲明。

通過 this.resolve 函數來解析路徑。

對于第一種方式,有一個很好的例子就是 css-loader。它把 @import 聲明轉化為 require 樣式表文件,把 url(...) 聲明轉化為 require 被引用文件。

而對于第二種方式,則需要參考一下 less-loader。由于要追蹤 less 中的變量和 mixin,我們需要把所有的 .less 文件一次編譯完畢,所以不能把每個 @import 轉為 require。因此,less-loader 用自定義路徑解析邏輯拓展了 less 編譯器。這種方式運用了我們剛才提到的第二種方式 —— this.resolve 通過 webpack 來解析依賴。

如果某種語言只支持相對路徑(例如 url(file) 指向 ./file)。你可以用 ~ 將相對路徑指向某個已經安裝好的目錄(例如 node_modules)下,因此,拿 url 舉例,它看起來會變成這樣:url(~some-library/image.jpg)
代碼公用

避免在多個 loader 里面初始化同樣的代碼,請把這些共用代碼提取到一個運行時文件里,然后通過 require 把它引進每個 loader。

絕對路徑

不要在 loader 模塊里寫絕對路徑,因為當項目根路徑變了,這些路徑會干擾 webpack 計算 hash(把 module 的路徑轉化為 module 的引用 id)。loader-utils 里有一個 stringifyRequest 方法,它可以把絕對路徑轉化為相對路徑。

同伴依賴

如果你開發的 loader 只是簡單包裝另外一個包,那么你應該在 package.json 中將這個包設為同伴依賴(peerDependency)。這可以讓應用開發者知道該指定哪個具體的版本。
舉個例子,如下所示 sass-loadernode-sass 指定為同伴依賴:

"peerDependencies": {
  "node-sass": "^4.0.0"
}
Talk is cheep

以上我們已經為砍柴磨好了刀,接下來,我們動手開發一個 loader。

如果我們要在項目開發中引用模版文件,那么壓縮 html 是十分常見的需求。分解以上需求,解析模版、壓縮模版其實可以拆分給兩給 loader 來做(單一職責),前者較為復雜,我們就引入開源包 html-loader,而后者,我們就拿來練手。首先,我們給它取個響亮的名字 —— html-minify-loader

接下來,按照之前介紹的步驟,首先,我們應該配置 webpack.config.js ,讓 webpack 能識別我們的 loader。當然,最最開始,我們要創建 loader 的 文件 —— src/loaders/html-minify-loader.js

于是,我們在配置文件中這樣處理:
webpack.config.js

module: {
    rules: [{
        test: /.html$/,
        use: ["html-loader", "html-minify-loader"] // 處理順序 html-minify-loader => html-loader => webpack
    }]
},
resolveLoader: {
    // 因為 html-loader 是開源 npm 包,所以這里要添加 "node_modules" 目錄
    modules: [path.join(__dirname, "./src/loaders"), "node_modules"]
}

接下來,我們提供示例 html 和 js 來測試 loader:

src/example.html




    
    
    
    Document


    

src/app.js

var html = require("./expamle.html");
console.log(html);

好了,現在我們著手處理 src/loaders/html-minify-loader.js。前面我們說過,loader 也是一個 node 模塊,它導出一個函數,該函數的參數是 require 的源模塊,處理 source 后把返回值交給下一個 loader。所以它的 “模版” 應該是這樣的:

module.exports = function (source) {
    // 處理 source ...
    return handledSource;
}

module.exports = function (source) {
    // 處理 source ...
    this.callback(null, handledSource)
    return handledSource;
}
注意:如果是處理順序排在最后一個的 loader,那么它的返回值將最終交給 webpack 的 require,換句話說,它一定是一段可執行的 JS 腳本 (用字符串來存儲),更準確來說,是一個 node 模塊的 JS 腳本,我們來看下面的例子。
// 處理順序排在最后的 loader
module.exports = function (source) {
    // 這個 loader 的功能是把源模塊轉化為字符串交給 require 的調用方
    return "module.exports = " + JSON.stringify(source);
}

整個過程相當于這個 loader 把源文件

這里是 source 模塊

轉化為

// example.js
module.exports = "這里是 source 模塊";

然后交給 require 調用方:

// applySomeModule.js
var source = require("example.js"); 

console.log(source); // 這里是 source 模塊

而我們本次串聯的兩個 loader 中,解析 html 、轉化為 JS 執行腳本的任務已經交給 html-loader 了,我們來處理 html 壓縮問題。

作為普通 node 模塊的 loader 可以輕而易舉地引用第三方庫。我們使用 minimize 這個庫來完成核心的壓縮功能:

// src/loaders/html-minify-loader.js

var Minimize = require("minimize");

module.exports = function(source) {
    var minimize = new Minimize();
    return minimize.parse(source);
};

當然, minimize 庫支持一系列的壓縮參數,比如 comments 參數指定是否需要保留注釋。我們肯定不能在 loader 里寫死這些配置。那么 loader-utils 就該發揮作用了:

// src/loaders/html-minify-loader.js
var loaderUtils = require("loader-utils");
var Minimize = require("minimize");

module.exports = function(source) {
    var options = loaderUtils.getOptions(this) || {}; //這里拿到 webpack.config.js 的 loader 配置
    var minimize = new Minimize(options);
    return minimize.parse(source);
};

這樣,我們可以在 webpack.config.js 中設置壓縮后是否需要保留注釋:

    module: {
        rules: [{
            test: /.html$/,
            use: ["html-loader", {
                loader: "html-minify-loader",
                options: {
                    comments: false
                }
            }] 
        }]
    },
    resolveLoader: {
        // 因為 html-loader 是開源 npm 包,所以這里要添加 "node_modules" 目錄
        modules: [path.join(__dirname, "./src/loaders"), "node_modules"]
    }

當然,你還可以把我們的 loader 寫成異步的方式,這樣不會阻塞其他編譯進度:

var Minimize = require("minimize");
var loaderUtils = require("loader-utils");

module.exports = function(source) {
    var callback = this.async();
    if (this.cacheable) {
        this.cacheable();
    }
    var opts = loaderUtils.getOptions(this) || {};
    var minimize = new Minimize(opts);
    minimize.parse(source, callback);
};

你可以在這個倉庫查看相關代碼,npm start 以后可以去 http://localhost:9000 打開控制臺查看 loader 處理后的內容。

總結

到這里,對于「如何開發一個 loader」,我相信你已經有了自己的答案。總結一下,一個 loader 在我們項目中 work 需要經歷以下步驟:

創建 loader 的目錄及模塊文件

在 webpack 中配置 rule 及 loader 的解析路徑,并且要注意 loader 的順序,這樣在 require 指定類型文件時,我們能讓處理流經過指定 laoder。

遵循原則設計和開發 loader。

最后,Talk is cheep,趕緊動手擼一個 loader 耍耍吧~

參考
Writing a loader

推薦: 翻譯項目Master的自述: 1. 干貨|人人都是翻譯項目的Master 2. iKcamp出品微信小程序教學共5章16小節匯總(含視頻) 3. 開始免費連載啦~每周2更共11堂iKcamp課|基于Koa2搭建Node.js實戰項目教學(含視頻)| 課程大綱介紹

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/92782.html

相關文章

  • 把手你擼個vue2.0彈窗組件

    摘要:組件結構同組件結構通過方法獲取元素的大小及其相對于視口的位置,之后對提示信息進行定位。可以用來進行一些復雜帶校驗的彈窗信息展示,也可以只用于簡單信息的展示。可以通過屬性來顯示任意標題,通過屬性來修改顯示區域的寬度。 手把手教你擼個vue2.0彈窗組件 在開始之前需要了解一下開發vue插件的前置知識,推薦先看一下vue官網的插件介紹 預覽地址 http://haogewudi.me/k...

    mrli2016 評論0 收藏0
  • 把手你擼個vue2.0彈窗組件

    摘要:組件結構同組件結構通過方法獲取元素的大小及其相對于視口的位置,之后對提示信息進行定位。可以用來進行一些復雜帶校驗的彈窗信息展示,也可以只用于簡單信息的展示。可以通過屬性來顯示任意標題,通過屬性來修改顯示區域的寬度。 手把手教你擼個vue2.0彈窗組件 在開始之前需要了解一下開發vue插件的前置知識,推薦先看一下vue官網的插件介紹 預覽地址 http://haogewudi.me/k...

    taoszu 評論0 收藏0
  • 把手你擼一個泡妞神奇

    摘要:畫字首先我在畫布上畫了個點,用這些點來組成我們要顯示的字,用不到的字就隱藏起來。星星閃爍效果這個效果實現很簡單,就是讓星星不停的震動,具體就是讓點的目的地坐標不停的進行小范圍的偏移。 哈哈哈哈!!!當我說在寫這邊文章的時候,妹子已經追到了,哈哈哈哈哈!!! 其實東西是一年前寫的,妹子早就追到手了,當時就是用這個東西來表白的咯,二話不說,先看效果(點擊屏幕可顯示下一句) showImg(...

    funnyZhang 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<