loader 概念
幫助 webpack 將不同類型的文件轉(zhuǎn)換為 webpack 可識(shí)別的模塊。
loader 執(zhí)行順序
分類
- pre: 前置 loader
- normal: 普通 loader
- inline: 內(nèi)聯(lián) loader
- post: 后置 loader
執(zhí)行順序
- 4 類 loader 的執(zhí)行優(yōu)級(jí)為:pre > normal > inline > post 。
- 相同優(yōu)先級(jí)的 loader 執(zhí)行順序?yàn)椋簭挠业阶螅瑥南碌缴稀?/li>
例如:
// 此時(shí)loader執(zhí)行順序:loader3 - loader2 - loader1
module: {
rules: [
{
test: /\.js$/,
loader: "loader1",
},
{
test: /\.js$/,
loader: "loader2",
},
{
test: /\.js$/,
loader: "loader3",
},
],
},
// 此時(shí)loader執(zhí)行順序:loader1 - loader2 - loader3
module: {
rules: [
{
enforce: "pre",
test: /\.js$/,
loader: "loader1",
},
{
// 沒有enforce就是normal
test: /\.js$/,
loader: "loader2",
},
{
enforce: "post",
test: /\.js$/,
loader: "loader3",
},
],
},
使用 loader 的方式
- 配置方式:在 webpack.config.js 文件中指定 loader。(pre、normal、post loader)
- 內(nèi)聯(lián)方式:在每個(gè) import 語句中顯式指定 loader。(inline loader)
inline loader
用法:import Styles from 'style-loader!css-loader?modules!./styles.css';
含義:
使用 css-loader 和 style-loader 處理 styles.css 文件
通過 ! 將資源中的 loader 分開
inline loader 可以通過添加不同前綴,跳過其他類型 loader。
! 跳過 normal loader。
import Styles from '!style-loader!css-loader?modules!./styles.css';
-! 跳過 pre 和 normal loader。
import Styles from '-!style-loader!css-loader?modules!./styles.css';
!! 跳過 pre、 normal 和 post loader。
import Styles from '!!style-loader!css-loader?modules!./styles.css';
開發(fā)一個(gè) loader
1. 最簡單的 loader
// loaders/loader1.js
module.exports = function loader1(content) {
console.log("hello loader");
return content;
};
它接受要處理的源碼作為參數(shù),輸出轉(zhuǎn)換后的 js 代碼。
2. loader 接受的參數(shù)
- content 源文件的內(nèi)容
- map SourceMap 數(shù)據(jù)
- meta 數(shù)據(jù),可以是任何內(nèi)容
loader 分類
1. 同步 loader
module.exports = function (content, map, meta) {
return content;
};
this.callback 方法則更靈活,因?yàn)樗试S傳遞多個(gè)參數(shù),而不僅僅是 content。
module.exports = function (content, map, meta) {
// 傳遞map,讓source-map不中斷
// 傳遞meta,讓下一個(gè)loader接收到其他參數(shù)
this.callback(null, content, map, meta);
return; // 當(dāng)調(diào)用 callback() 函數(shù)時(shí),總是返回 undefined
};
2. 異步 loader
module.exports = function (content, map, meta) {
const callback = this.async();
// 進(jìn)行異步操作
setTimeout(() => {
callback(null, result, map, meta);
}, 1000);
};
由于同步計(jì)算過于耗時(shí),在 Node.js 這樣的單線程環(huán)境下進(jìn)行此操作并不是好的方案,我們建議盡可能地使你的 loader 異步化。但如果計(jì)算量很小,同步 loader 也是可以的。
3. Raw Loader
默認(rèn)情況下,資源文件會(huì)被轉(zhuǎn)化為 UTF-8 字符串,然后傳給 loader。通過設(shè)置 raw 為 true,loader 可以接收原始的 Buffer。
module.exports = function (content) {
// content是一個(gè)Buffer數(shù)據(jù)
return content;
};
module.exports.raw = true; // 開啟 Raw Loader
4. Pitching Loader
module.exports = function (content) {
return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
console.log("do somethings");
};
webpack 會(huì)先從左到右執(zhí)行 loader 鏈中的每個(gè) loader 上的 pitch 方法(如果有),然后再從右到左執(zhí)行 loader 鏈中的每個(gè) loader 上的普通 loader 方法。
在這個(gè)過程中如果任何 pitch 有返回值,則 loader 鏈被阻斷。webpack 會(huì)跳過后面所有的的 pitch 和 loader,直接進(jìn)入上一個(gè) loader 。
loader API
方法名 | 含義 | 用法 |
---|---|---|
this.async | 異步回調(diào) loader。返回 this.callback | const callback = this.async() |
this.callback | 可以同步或者異步調(diào)用的并返回多個(gè)結(jié)果的函數(shù) | this.callback(err, content, sourceMap?, meta?) |
this.getOptions(schema) | 獲取 loader 的 options | this.getOptions(schema) |
this.emitFile | 產(chǎn)生一個(gè)文件 | this.emitFile(name, content, sourceMap) |
this.utils.contextify | 返回一個(gè)相對路徑 | this.utils.contextify(context, request) |
this.utils.absolutify | 返回一個(gè)絕對路徑 | this.utils.absolutify(context, request) |
更多文檔,請查閱?webpack 官方 loader api 文檔?
手寫 clean-log-loader
作用:用來清理 js 代碼中的console.log
// loaders/clean-log-loader.js
module.exports = function cleanLogLoader(content) {
// 將console.log替換為空
return content.replace(/console\.log\(.*\);?/g, "");
};
手寫 banner-loader
作用:給 js 代碼添加文本注釋
loaders/banner-loader/index.js
const schema = require("./schema.json");
module.exports = function (content) {
// 獲取loader的options,同時(shí)對options內(nèi)容進(jìn)行校驗(yàn)
// schema是options的校驗(yàn)規(guī)則(符合 JSON schema 規(guī)則)
const options = this.getOptions(schema);
const prefix = `
/*
* Author: ${options.author}
*/
`;
return `${prefix} \n ${content}`;
};
loaders/banner-loader/schema.json
{
"type": "object",
"properties": {
"author": {
"type": "string"
}
},
"additionalProperties": false
}
手寫 babel-loader
作用:編譯 js 代碼,將 ES6+語法編譯成 ES5-語法。
下載依賴
npm i @babel/core @babel/preset-env -D
loaders/babel-loader/index.js
const schema = require("./schema.json");
const babel = require("@babel/core");
module.exports = function (content) {
const options = this.getOptions(schema);
// 使用異步loader
const callback = this.async();
// 使用babel對js代碼進(jìn)行編譯
babel.transform(content, options, function (err, result) {
callback(err, result.code);
});
};
loaders/banner-loader/schema.json
{
"type": "object",
"properties": {
"presets": {
"type": "array"
}
},
"additionalProperties": true
}
手寫 file-loader
作用:將文件原封不動(dòng)輸出出去
下載包
npm i loader-utils -D
loaders/file-loader.js
const loaderUtils = require("loader-utils");
function fileLoader(content) {
// 根據(jù)文件內(nèi)容生產(chǎn)一個(gè)新的文件名稱
const filename = loaderUtils.interpolateName(this, "[hash].[ext]", {
content,
});
// 輸出文件
this.emitFile(filename, content);
// 暴露出去,給js引用。
// 記得加上''
return `export default '${filename}'`;
}
// loader 解決的是二進(jìn)制的內(nèi)容
// 圖片是 Buffer 數(shù)據(jù)
fileLoader.raw = true;
module.exports = fileLoader;
loader 配置
{
test: /\.(png|jpe?g|gif)$/,
loader: "./loaders/file-loader.js",
type: "javascript/auto", // 解決圖片重復(fù)打包問題
},
手寫 style-loader
作用:動(dòng)態(tài)創(chuàng)建 style 標(biāo)簽,插入 js 中的樣式代碼,使樣式生效。文章來源:http://www.zghlxwxcb.cn/news/detail-816030.html
loaders/style-loader.js文章來源地址http://www.zghlxwxcb.cn/news/detail-816030.html
const styleLoader = () => {};
styleLoader.pitch = function (remainingRequest) {
/*
remainingRequest: C:\Users\86176\Desktop\source\node_modules\css-loader\dist\cjs.js!C:\Users\86176\Desktop\source\src\css\index.css
這里是inline loader用法,代表后面還有一個(gè)css-loader等待處理
最終我們需要將remainingRequest中的路徑轉(zhuǎn)化成相對路徑,webpack才能處理
希望得到:../../node_modules/css-loader/dist/cjs.js!./index.css
所以:需要將絕對路徑轉(zhuǎn)化成相對路徑
要求:
1. 必須是相對路徑
2. 相對路徑必須以 ./ 或 ../ 開頭
3. 相對路徑的路徑分隔符必須是 / ,不能是 \
*/
const relativeRequest = remainingRequest
.split("!")
.map((part) => {
// 將路徑轉(zhuǎn)化為相對路徑
const relativePath = this.utils.contextify(this.context, part);
return relativePath;
})
.join("!");
/*
!!${relativeRequest}
relativeRequest:../../node_modules/css-loader/dist/cjs.js!./index.css
relativeRequest是inline loader用法,代表要處理的index.css資源, 使用css-loader處理
!!代表禁用所有配置的loader,只使用inline loader。(也就是外面我們style-loader和css-loader),它們被禁用了,只是用我們指定的inline loader,也就是css-loader
import style from "!!${relativeRequest}"
引入css-loader處理后的css文件
為什么需要css-loader處理css文件,不是我們直接讀取css文件使用呢?
因?yàn)榭赡艽嬖贎import導(dǎo)入css語法,這些語法就要通過css-loader解析才能變成一個(gè)css文件,否則我們引入的css資源會(huì)缺少
const styleEl = document.createElement('style')
動(dòng)態(tài)創(chuàng)建style標(biāo)簽
styleEl.innerHTML = style
將style標(biāo)簽內(nèi)容設(shè)置為處理后的css代碼
document.head.appendChild(styleEl)
添加到head中生效
*/
const script = `
import style from "!!${relativeRequest}"
const styleEl = document.createElement('style')
styleEl.innerHTML = style
document.head.appendChild(styleEl)
`;
// style-loader是第一個(gè)loader, 由于return導(dǎo)致熔斷,所以其他loader不執(zhí)行了(不管是normal還是pitch)
return script;
};
module.exports = styleLoader;
到了這里,關(guān)于Webpack5入門到原理17:Loader 原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!