原文地址javascript
本文將 webpack
的 Loader
相關的知識點整理了一下,部分文字是從官方文檔中直接摘錄過來的,並附上本身的理解。若是以爲看起來和官方文檔差很少,直接看官方文檔最好啦~css
webpack loaders 從上手到理解系列
還有這些:vue
本文不過多描述 webpack
的做用和使用方法,若是還不是太熟悉,能夠打開 webpack.js.org/ 先熟悉一下。java
關於 webpack
的工做流程,簡單來講能夠歸納爲如下幾步:node
Loader
編譯文件AST
,收集依賴Chunk
其中,真正起編譯做用的即是 Loader
,本文也就 Loader
進行詳細的闡述,其他部分暫且不談。webpack
Loader allow webpack to process other types of files and convert them into valid modules.git
Loader
的做用很簡單,就是處理任意類型的文件,而且將它們轉換成一個讓 webpack
能夠處理的有效模塊。github
Loader
能夠在 webpack.config.js
裏配置,這也是推薦的作法,定義在 module.rules
裏:web
// webpack.config.js
module.exports = {
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' },
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'postcss-loader' },
]
}
]
}
};
複製代碼
每一條 rule
會包含兩個屬性:test
和 use
,好比 { test: /\.js$/, use: 'babel-loader' }
意思就是:當 webpack
遇到擴展名爲 js
的文件時,先用 babel-loader
處理一下,而後再打包它。json
use
的類型:string|array|object|function
:
string
: 只有一個 Loader
時,直接聲明 Loader
,好比 babel-loader
。array
: 聲明多個 Loader
時,使用數組形式聲明,好比上文聲明 .css
的 Loader
。object
: 只有一個 Loader
時,須要有額外的配置項時。function
: use
也支持回調函數的形式。關於 use
的多種配置方式,這裏就很少說了,能夠點擊 更多關於 use
注意: 當 use
是經過數組形式聲明 Loader
時,Loader
的執行順序是從右到左,從下到上。好比暫且認爲上方聲明是這樣執行的:
postcss-loader
-> css-loader
-> style-loader
其實就是:
styleLoader(cssLoader(postcssLoader(content)))
複製代碼
爲何說是暫且呢,由於 style-loader
有點特殊,有興趣的看看這個 webpack loader 從上手到理解系列:style-loader。
webpack
提供了多種配置 Loader
的方法,不過通常來講,use
就已經足夠用了,若是想了解更多,能夠點擊 更多關於 rule 的配置
能夠在 import
等語句裏指定 Loader
,使用 !
來將 Loader
分開:
import style from 'style-loader!css-loader?modules!./styles.css';
複製代碼
內聯時,經過 query
來傳遞參數,例如 ?key=value
。
通常來講,推薦使用統一 config
的形式來配置 Loader
,內聯形式多出現於 Loader
內部,好比 style-loader
會在自身代碼裏引入 css-loader
:
require("!!../../node_modules/css-loader/dist/cjs.js!./styles.css");
複製代碼
module.exports = function(source) {
const result = someSyncOperation(source); // 同步邏輯
return result;
}
複製代碼
通常來講,Loader
都是同步的,經過 return
或者 this.callback
來同步地返回 source
轉換後的結果。
有的時候,咱們須要在 Loader
裏作一些異步的事情,好比說須要發送網絡請求。若是同步地等着,網絡請求就會阻塞整個構建過程,這個時候咱們就須要進行異步 Loader
,能夠這樣作:
module.exports = function(source) {
// 告訴 webpack 此次轉換是異步的
const callback = this.async();
// 異步邏輯
someAsyncOperation(content, function(err, result) {
if (err) return callback(err);
// 經過 callback 來返回異步處理的結果
callback(null, result, map, meta);
});
};
複製代碼
Pitching Loader
是一個比較重要的概念,以前在 style-loader
裏有提到過。
{
test: /\.js$/,
use: [
{ loader: 'aa-loader' },
{ loader: 'bb-loader' },
{ loader: 'cc-loader' },
]
}
複製代碼
咱們知道,Loader
老是從右到左被調用。上面配置的 Loader
,就會按照如下順序執行:
cc-loader
-> bb-loader
-> aa-loader
每一個 Loader
都支持一個 pitch
屬性,經過 module.exports.pitch
聲明。若是該 Loader
聲明瞭 pitch
,則該方法會優先於 Loader
的實際方法先執行,官方也給出了執行順序:
|- aa-loader `pitch`
|- bb-loader `pitch`
|- cc-loader `pitch`
|- requested module is picked up as a dependency
|- cc-loader normal execution
|- bb-loader normal execution
|- aa-loader normal execution
複製代碼
也就是會先從左向右執行一次每一個 Loader
的 pitch
方法,再按照從右向左的順序執行其實際方法。
咱們在 url-loader
裏和 file-loader
最後都見過這樣一句代碼:
export const raw = true;
複製代碼
默認狀況下,webpack
會把文件進行 UTF-8
編碼,而後傳給 Loader
。經過設置 raw
,Loader
就能夠接受到原始的 Buffer
數據。
所謂 Loader
,也只是一個符合 commonjs
規範的 node
模塊,它會導出一個可執行函數。loader runner
會調用這個函數,將文件的內容或者上一個 Loader
處理的結果傳遞進去。同時,webpack
還爲 Loader
提供了一個上下文 this
,其中有不少有用的 api
,咱們找幾個典型的來看看。
在 Loader
中,一般使用 return
來返回一個字符串或者 Buffer
。若是須要返回多個結果值時,就須要使用 this.callback
,定義以下:
this.callback(
// 沒法轉換時返回 Error,其他狀況都返回 null
err: Error | null,
// 轉換結果
content: string | Buffer,
// source map,方便調試用的
sourceMap?: SourceMap,
// 能夠是任何東西。好比 ast
meta?: any
);
複製代碼
通常來講若是調用該函數的話,應該手動 return
,告訴 webpack
返回的結果在 this.callback
中,以免含糊不清的結果:
module.exports = function(source) {
this.callback(null, source, sourceMaps);
return;
};
複製代碼
同上,異步 Loader
。
有些狀況下,有些操做須要耗費大量時間,每一次調用 Loader
轉換時都會執行這些費時的操做。
在處理這類費時的操做時, webapck
會默認緩存全部 Loader
的處理結果,只有當被處理的文件發生變化時,纔會從新調用 Loader
去執行轉換操做。
webpack
是默承認緩存的,能夠執行 this.cacheable(false)
手動關閉緩存。
當前處理文件的完整請求路徑,包括 query
,好比 /src/App.vue?type=templpate
。
當前處理文件的路徑,不包括 query
,好比 /src/App.vue
。
當前處理文件的 query
字符串,好比 ?type=template
。咱們在 vue-loader
裏有見過如何使用它:
const qs = require('querystring');
const { resourceQuery } = this;
const rawQuery = resourceQuery.slice(1); // 刪除前面的 ?
const incomingQuery = qs.parse(rawQuery); // 解析字符串成對象
// 取 query
if (incomingQuery.type) {}
複製代碼
讓 webpack
在輸出目錄新建一個文件,咱們在 file-loader
裏有見過:
if (typeof options.emitFile === 'undefined' || options.emitFile) {
this.emitFile(outputPath, content);
}
複製代碼
更多的 api
可在官方文檔中查看:Loader Interface
咱們來回顧一下 Loader
的一些特色:
Loader
是一個 node
模塊;Loader
能夠處理任意類型的文件,轉換成 webpack
能夠處理的模塊;Loader
能夠在 webpack.config.js
裏配置,也能夠在 require
語句裏內聯;Loader
能夠根據配置從右向左鏈式執行;Loader
接受源文件內容字符串或者 Buffer
;Loader
分爲多種類型:同步、異步和 pitching
,他們的執行流程不同;webpack
爲 Loader
提供了一個上下文,有一些 api
可使用;咱們根據以上暫時知道的特色,能夠對 Loader
的工做流程有個猜想,假設有一個 js-loader
,它的工做流程簡單來講是這樣的:
webpack.config.js
裏配置了一個 js
的 Loader
;js
文件時,觸發了 js-loader
;js-loader
接受了一個表示該 js
文件內容的 source
;js-loader
使用 webapck
提供的一系列 api
對 source
進行轉換,獲得一個 result
;result
返回或者傳遞給下一個 Loader
,直處處理完畢。webpack
的編譯流程很是複雜,暫時還不能看明白而且梳理清楚,在這裏就不誤導你們了。
關於 Loader
的工做流程以及源碼分析能夠看 【webpack進階】你真的掌握了loader麼?- loader十問。
雖然咱們對於 webpack
的編譯流程不是很熟悉,可是咱們能夠試着編寫一個簡單功能的 Loader
,從而加深對 Loader
的理解。
編寫 Loader
時須要遵循一些準則,官方有很詳細的文檔,就不重複闡述了。點擊 Loaders 用法準則 查看。
這裏說一下單一任務和鏈式調用。
一個 Loader
應該只完成一個功能,若是須要多步的轉換工做,則應該編寫多個 Loader
來進行鏈式調用完成轉換。好比 vue-loader
只是處理了 vue
文件,起到一個分發的做用,將其中的 template/style/script
分別交給不一樣的處理器來處理。
這樣會讓維護 Loader
變得更簡單,也能讓不一樣的 Loader
更容易地串聯在一塊兒,而不是重複造輪子。
編寫 Loader
的過程當中,最經常使用的兩個工具庫是 loader-utils
和 schema-utils
,在如今常見的 Loader
中都能看到它們的身影。
它提供了許多有用的工具,但最經常使用的一種工具是獲取傳遞給 Loader
的選項:
import { getOptions } from 'loader-utils';
export default function loader(src) {
// 加載 options
const options = getOptions(this) || {};
}
複製代碼
配合 loader-utils
,用於保證 Loader
選項,進行與 JSON Schema
結構一致的校驗。
import validateOptions from 'schema-utils';
import schema from './options.json';
export default function loader(src) {
// 校驗 options
validateOptions(schema, options, {
name: 'URL Loader',
baseDataPath: 'options',
});
}
複製代碼
更多關於如何編寫一個 Loader
,傳送門。
本文對 webpack
的 Loader
相關知識點進行整理和概括,正在學習中,若有不足歡迎指出。