Created By JishuBao on 2019-03-29 12:38:22
Recently revised in 2019-04-01 12:38:22
歡迎大家來到技術寶的掘金世界,您的star是我寫文章最大的動力!GitHub地址
文章簡介:
1、webpack module簡介
2、rules的使用
3、loader-utils是什麼?
4、api實現
5、未完待續
一、webpack module簡介
現在前端的技術日新月異,現在比較火的前端打包工具! webpack,我想各位小夥伴應該也有所瞭解,那麼你知道如何手寫webpack嗎,作為手寫webpack系列的第一篇文章,我將帶領大家逐步走進webpack的世界。webpack中有一個及其重要的概念loader,那什麼是loader呢?在webpack的官網中,我們可以看到這樣一行對Loader的定義,原來是處理js字尾檔名除外的檔案。
webpack 可以使用 loader 來預處理檔案。這允許你打包除 JavaScript 之外的任何靜態資源。你可以使用 Node.js 來很簡單地編寫自己的 loader。
在loader中,我們們可以通過關鍵詞this訪問當前執行環境的所有變數
1、同步回撥時,可以執行this.callback(),預設第一個引數是err錯誤資訊(不報錯時返回null),第二個引數是解析完模組後的返回結果,第三個引數是sourceMap(可選),在鏈式呼叫時可將sourceMap傳給下一個loader;
2、非同步回撥時,可以執行this.async(),引數同上;
3、this.addDependency(filePath)可以把對應filePath的檔案新增到webpack的依賴樹,webpack可以監測它的檔案變動並重新整理(filePath要是絕對路徑);
4、this.resolve()可以解析處理檔案路徑;
5、this.query:獲取loader的配置選項。
瞭解或者使用過webpack的小夥伴都知道loader是使用在weboack的module裡面的,用於處理module檔案的。我們來看下module屬性對應的一些常用api!
- module.noParse
noParse 配置項可以讓 Webpack 忽略對部分沒采用模組化的檔案的遞迴解析和處理,這樣做的好處是能提高構建效能。 原因是一些庫例如 jQuery 、ChartJS 它們龐大又沒有采用模組化標準,讓 Webpack 去解析這些檔案耗時又沒有意義。noParse 是可選配置項,型別需要是 RegExp、[RegExp]、function 其中一個.例如想要忽略掉 jQuery 、ChartJS,可以使用如下程式碼:
// 使用正規表示式
noParse: /jquery|chartjs/
// 使用函式,從 Webpack 3.0.0 開始支援
noParse: (content)=> {
// content 代表一個模組的檔案路徑
// 返回 true or false
return /jquery|chartjs/.test(content);
}
複製程式碼
- module.rules
配置模組的讀取和解析規則,通常用來配置 Loader。其型別是一個陣列,陣列裡每一項都描述瞭如何去處理部分檔案。 配置一項 rules 時大致通過以下方式:
1.條件匹配:通過 test 、 include 、 exclude 三個配置項來命中 Loader 要應用規則的檔案。
2.應用規則:對選中後的檔案通過 use 配置項來應用 Loader,可以只應用一個 Loader 或者按照從後往前的順序應用一組 Loader,同時還可以分別給 Loader 傳入引數。
3.重置順序:一組 Loader 的執行順序預設是從右到左執行,通過 enforce 選項可以讓其中一個 Loader 的執行順序放到最前或者最後。 下面來通過一個例子來說明具體使用方法:
module: {
rules: [
{
// 命中 JavaScript 檔案
test: /\.js$/,
// 用 babel-loader 轉換 JavaScript 檔案
// ?cacheDirectory 表示傳給 babel-loader 的引數,用於快取 babel 編譯結果加快重新編譯速度
use: ['babel-loader?cacheDirectory'],
// 只命中src目錄裡的js檔案,加快 Webpack 搜尋速度
include: path.resolve(__dirname, 'src')
},
{
// 命中 SCSS 檔案
test: /\.scss$/,
// 使用一組 Loader 去處理 SCSS 檔案。
// 處理順序為從後到前,即先交給 sass-loader 處理,再把結果交給 css-loader 最後再給 style-loader。
use: ['style-loader', 'css-loader', 'sass-loader'],
// 排除 node_modules 目錄下的檔案
exclude: path.resolve(__dirname, 'node_modules'),
},
{
// 對非文字檔案採用 file-loader 載入
test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/,
use: ['file-loader'],
},
]
}
複製程式碼
在 Loader 需要傳入很多引數時,你還可以通過一個 Object 來描述,例如在上面的 babel-loader 配置中有如下程式碼:
use: [
{
loader:'babel-loader',
options:{
cacheDirectory:true,
},
// enforce:'post' 的含義是把該 Loader 的執行順序放到最後
// enforce 的值還可以是 pre,代表把 Loader 的執行順序放到最前面
enforce:'post'
},
// 省略其它 Loader
]
複製程式碼
上面的例子中 test include exclude 這三個命中檔案的配置項只傳入了一個字串或正則,其實它們還都支援陣列型別,使用如下:
{
test:[
/\.jsx?$/,
/\.tsx?$/
],
include:[
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'tests'),
],
exclude:[
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, 'bower_modules'),
]
}
複製程式碼
陣列裡的每項之間是或的關係,即檔案路徑符合陣列中的任何一個條件就會被命中。
- parser
因為 Webpack 是以模組化的 JavaScript 檔案為入口,所以內建了對模組化 JavaScript 的解析功能,支援 AMD、CommonJS、SystemJS、ES6。 parser 屬性可以更細粒度的配置哪些模組語法要解析哪些不解析,和 noParse 配置項的區別在於 parser 可以精確到語法層面, 而 noParse 只能控制哪些檔案不被解析。 parser 使用如下:
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
parser: {
amd: false, // 禁用 AMD
commonjs: false, // 禁用 CommonJS
system: false, // 禁用 SystemJS
harmony: false, // 禁用 ES6 import/export
requireInclude: false, // 禁用 require.include
requireEnsure: false, // 禁用 require.ensure
requireContext: false, // 禁用 require.context
browserify: false, // 禁用 browserify
requireJs: false, // 禁用 requirejs
}
},
]
}
複製程式碼
二、rules的使用
根據上文我們已經基本瞭解了rule的使用,裡面放一些loader處理對應的檔案。隨意開啟一個檔案,這裡已常用的loader file-loader舉例。
我們如果開啟大部分loader的原始碼,基本上你可以發現一個規律,基本上每個loader裡面都有這樣一句,引入了loader-utils這個工具類。那麼utils-loader究竟是什麼呢?
三、loader-utils是什麼?
loader-utils是一個webpack工具類,通過一些方法配合loader處理檔案。讓我們一起來解讀一下。 本文旨在通過手寫loader-utils來了解loader-utils的內容。新建資料夾loader-utils,執行命令npm init初始化一個npm專案
npm init
複製程式碼
新建index.js作為webpack打包入口檔案,內容為空。新建webpack.conf.js作為webpack配置檔案,內容如下:
const path=require('path');
//path是node.js的一個模組,提供了一些用於處理檔案路勁的小工具
module.exports={
entry:{
main:"./index.js"//入口起點(entry point)指示 webpack 應該使用哪個模組,來作為構建其內部依賴圖的開始。進入入口起點後,webpack 會找出有哪些模組和庫是入口起點(直接和間接)依賴的
},
resolve:{
},
module:{
rules:[
{
test:/\.js$/,//通過loader來預處理檔案 允許你打包除了js之外的任何靜態資源
use:[
{
loader:path.resolve('./loader-util.js'),
options:{
name:'wjb'
}
}
]
},
]
},
plugins:[
]
}
複製程式碼
新建loader-utils.js檔案作為webpack解析的loader。內容如下:
"use strict";
const loaderUtils=require("loader-utils");
function loader(content) {
var publicPath="a";
console.log('進入了loader內部');
return `${publicPath};`;
}
module.exports = loader;
複製程式碼
修改package.json檔案,新建dev和入口檔案
{
"name": "loader-util",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --config webpack.conf.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"webpack": "^4.29.6"
},
"devDependencies": {
"webpack-cli": "^3.3.0"
}
}
複製程式碼
執行命令npm run dev
npm run dev
複製程式碼
會發現已經進入了你寫的這個loader裡面了
四、api實現
新建loader-equal-utils資料夾存放模仿loader-utils工具類的功能。新建index.js資料夾。
- parseQuery(解析被呼叫loader的配置選項)
將傳遞的字串(例如loaderContext.resourceQuery)解析為查詢字串,並返回一個物件。
const params = loaderUtils.parseQuery(this.resourceQuery); // resource: `file?param1=foo`
if (params.param1 === "foo") {
// do something
}
複製程式碼
新建parseQuery.js檔案,首先安裝json5,json5是json的一個超集,具體的不再贅述。
'use strict';
const JSON5 = require('json5');
const specialValues = {
null: null,
true: true,
false: false,
};
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);//將query變成去掉?的字串
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) {//當下表大於0,即存在=時
let name = arg.substr(0, idx);//將=號之前 即name
let value = decodeURIComponent(arg.substr(idx + 1));//加密value
if (specialValues.hasOwnProperty(value)) {//當有值是true false 或者undefined時
value = specialValues[value];//將value變成true false undefined
}
if (name.substr(-2) === '[]') {
name = decodeURIComponent(name.substr(0, name.length - 2));
if (!Array.isArray(result[name])) {
result[name] = [];//將name鍵賦值為[]空陣列值
}
result[name].push(value);//將resultpush成值
} 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;
}
module.exports=parseQuery;
複製程式碼
在laoder-utils檔案匯入parseQuery,進行測試
更多引數解析:
-> Error
? -> {}
?flag -> { flag: true }
?+flag -> { flag: true }
?-flag -> { flag: false }
?xyz=test -> { xyz: "test" }
?xyz=1 -> { xyz: "1" } // numbers are NOT parsed
?xyz[]=a -> { xyz: ["a"] }
?flag1&flag2 -> { flag1: true, flag2: true }
?+flag1,-flag2 -> { flag1: true, flag2: false }
?xyz[]=a,xyz[]=b -> { xyz: ["a", "b"] }
?a%2C%26b=c%2C%26d -> { "a,&b": "c,&d" }
?{data:{a:1},isJSON5:true} -> { data: { a: 1 }, isJSON5: true }
複製程式碼
- parseString(是將字串轉化為json物件)
新建parseString.js檔案。
'use strict';
function parseString(str){
console.log(str)
try {
if (str[0] === '"') {
console.log('我進入了1')
console.log(str)
console.log(JSON.parse(str))
return JSON.parse(str);
}
if (str[0] === "'" && str.substr(str.length - 1) === "'") {//如果是以''包裹的字串
console.log(str)
console.log('我進入了2')
return parseString(
str
.replace(/\\.|"/g, (x) => (x === '"' ? '\\"' : x))//轉化為以""包裹的字串
.replace(/^'|'$/g, '"')
);
}
return JSON.parse('"' + str + '"');
} catch (e) {
console.log('wobaocuole')
console.log(e);
return str;
}
}
module.exports=parseString;
複製程式碼
在laoder-utils檔案匯入parseString,進行測試
- getOptions(檢索被呼叫loader的配置選項)
新建getOptions.js檔案。
檢索載入程式呼叫選項的推薦方法:
1.如果this.query是字串:嘗試解析查詢字串並返回一個新物件
2.如果它不是有效的查詢字串則丟擲
3.如果this.query是物件,它只是返回this.query
4.在任何其他情況下,它只是返回 null
// inside your loader
const options = loaderUtils.getOptions(this);
複製程式碼
'use strict';
const parseQuery=require('./parseQuery');//引入parseQuery模組
function getOptions(loaderContext){
const query=loaderContext.query;//拿到Loader上下文的query
if(typeof query==="string" && query!==''){//query是字串且query不是空字串
return parseQuery(query);//返回物件
}
if (!query || typeof query !== 'object') {//如果不存在或者不是物件返回null
// Not object-like queries are not supported.
return null;
}
return query;
}
module.exports=getOptions;
複製程式碼
在laoder-utils檔案匯入getOptions,進行測試
- stringifyRequest(將一個請求轉化為非絕對路徑的可被require或import的字串;)
新建stringifyRequest檔案
將請求轉換為可在內部使用require()或import在避免絕對路徑時使用的字串。JSON.stringify(...)如果您在載入器中生成程式碼,請使用它。為什麼這有必要?由於webpack在模組路徑轉換為模組ID之前計算雜湊值,因此我們必須避免絕對路徑以確保跨不同編譯的一致雜湊。
前置知識:
- path.posix是跨平臺相容方案,path.win32則是相容windows
- isAbsolute判斷路徑是否為絕對路徑,值得注意的是在posix上 path.isAbsolute('/foo/bar'); // true path.isAbsolute('/baz/..'); // true path.isAbsolute('qux/'); // false path.isAbsolute('.'); // false 在win上path.isAbsolute('//server'); // true path.isAbsolute('\\server'); // true path.isAbsolute('C:/foo/..'); // true path.isAbsolute('C:\foo\..'); // true path.isAbsolute('bar\baz'); // false path.isAbsolute('bar/baz'); // false path.isAbsolute('.'); // false
- path.relative(from, to)方法根據當前工作目錄返回 from 到 to 的相對路徑。 如果 from 和 to 各自解析到相同的路徑(分別呼叫 path.resolve() 之後),則返回零長度的字串。如果將零長度的字串傳入 from 或 to,則使用當前工作目錄代替該零長度的字串。例如,在 POSIX 上:path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb'); // 返回: '../../impl/bbb',在 Windows 上:path.relative('C:\orandea\test\aaa', 'C:\orandea\impl\bbb');// 返回: '..\..\impl\bbb'如果 from 或 to 不是字串,則丟擲 TypeError。
'use strict';
const path=require('path');
const matchRelativePath=/^\.\.?[/\\]/;//相對路徑正規表示式
function isAbsolutePath(str) {//是否為絕對路徑
return path.posix.isAbsolute(str) || path.win32.isAbsolute(str);
//前者為跨平臺 後者為windows 判斷是否為絕對路徑
}
function isRelativePath(str) {//是否是相對路徑 是返回true 否則返回false
return matchRelativePath.test(str);
}
function stringifyRequest(loaderContext, request) {
const splitted = request.split('!');//分割!
const context =
loaderContext.context ||
(loaderContext.options && loaderContext.options.context);//獲取內容
return JSON.stringify(
splitted
.map((part) => {
// First, separate singlePath from query, because the query might contain paths again
const splittedPart = part.match(/^(.*?)(\?.*)/);
const query = splittedPart ? splittedPart[2] : '';
let singlePath = splittedPart ? splittedPart[1] : part;
if (isAbsolutePath(singlePath) && context) {
singlePath = path.relative(context, singlePath);
if (isAbsolutePath(singlePath)) {
// If singlePath still matches an absolute path, singlePath was on a different drive than context.
// In this case, we leave the path platform-specific without replacing any separators.
// @see https://github.com/webpack/loader-utils/pull/14
return singlePath + query;
}
if (isRelativePath(singlePath) === false) {
// Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules).
singlePath = './' + singlePath;
}
}
return singlePath.replace(/\\/g, '/') + query;
})
.join('!')
);
}
複製程式碼
在laoder-utils檔案匯入stringifyRequest,進行測試
- urlToRequest(將url轉換成適合webpack環境的模組請求)
const url = "/path/to/module.js";
const root = "./root";
const request = loaderUtils.urlToRequest(url, root); // "./root/path/to/module.js"
複製程式碼
新建urlToRequest檔案
'use strict';
//我們不能使用path的isAbsolute方法 因為他對正斜槓開頭的路徑同樣有效
const matchNativeWin32Path = /^[A-Z]:[/\\]|^\\\\/i;
function urlToRequest(url, root){
// 不能寫空的url字串
if (url === '') {
return '';
}
const moduleRequestRegex = /^[^?]*~/;
let request;
if (matchNativeWin32Path.test(url)) {
// 如果是絕對路徑 保持url不變
request = url;
}else if (root !== undefined && root !== false && /^\//.test(url)) {
// 如果根路徑定義 且url是相對路徑
switch (typeof root) {
// 1. 如果根路徑是字串 根路徑作為url路徑的字首
case 'string':
// 特殊的: `~` 根轉換為模組請求
if (moduleRequestRegex.test(root)) {
request = root.replace(/([^~/])$/, '$1/') + url.slice(1);
} else {
request = root + url;
}
break;
// 2. 如果root為true,則絕對路徑允許
// *當window路徑不是以`/`,一直支援絕對路徑
case 'boolean':
request = url;
break;
default:
throw new Error(
"Unexpected parameters to loader-utils 'urlToRequest': url = " +
url +
', root = ' +
root +
'.'
);
}
}else if (/^\.\.?\//.test(url)) {
//相對路徑
request = url;
} else {
// 像一個相對
request = './' + url;
}
// A `~` makes the url an module
if (moduleRequestRegex.test(request)) {
request = request.replace(moduleRequestRegex, '');
}
return request;
}
module.exports = urlToRequest;
複製程式碼
在loader-utils檔案匯入urlToRequest,進行測試
- getHashDigest(通過限制字元長度獲取檔案部分雜湊值)
const digestString = loaderUtils.getHashDigest(buffer, hashType, digestType, maxLength);
複製程式碼
-
- buffer:應該雜湊的內容
-
- hashType之一sha1,md5,sha256,sha512或任何其他的node.js支援的雜湊型別
-
- digestType一hex,base26,base32,base36,base49,base52,base58,base62,base64
-
- maxLength 字元的最大長度
新建getHashDigest檔案
'use strict';
const baseEncodeTables = {//基本的型別
26: 'abcdefghijklmnopqrstuvwxyz',
32: '123456789abcdefghjkmnpqrstuvwxyz', // no 0lio
36: '0123456789abcdefghijklmnopqrstuvwxyz',
49: 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ', // no lIO
52: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
58: '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ', // no 0lIO
62: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
64: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_',
};
function encodeBufferToBase(buffer, base) {//buffer轉化為base
const encodeTable = baseEncodeTables[base];
if (!encodeTable) {//如果沒有 丟擲錯誤 找不到base型別
throw new Error('Unknown encoding base' + base);
}
const readLength = buffer.length;//快取的長度
const Big = require('big.js');
Big.RM = Big.DP = 0;//初始化小數點的最大位數和最小位數
let b = new Big(0);//初始化Big
for (let i = readLength - 1; i >= 0; i--) {
b = b.times(256).plus(buffer[i]);//b乘以256再加
}
let output = '';
while (b.gt(0)) {
output = encodeTable[b.mod(base)] + output;
b = b.div(base);
}
Big.DP = 20;
Big.RM = 1;
return output;
}
function getHashDigest(buffer, hashType, digestType, maxLength) {
hashType = hashType || 'md5';
maxLength = maxLength || 9999;
const hash = require('crypto').createHash(hashType);
hash.update(buffer);
if (
digestType === 'base26' ||
digestType === 'base32' ||
digestType === 'base36' ||
digestType === 'base49' ||
digestType === 'base52' ||
digestType === 'base58' ||
digestType === 'base62' ||
digestType === 'base64'
) {
return encodeBufferToBase(hash.digest(), digestType.substr(4)).substr(
0,
maxLength
);
} else {
return hash.digest(digestType || 'hex').substr(0, maxLength);
}
}
module.exports = getHashDigest;
複製程式碼
在loader-utils檔案匯入getHashDigest,進行測試
- interpolateName(自定義資源名稱、hash等)
前置知識 path.parse() 方法返回一個物件,其屬性表示 path 的重要元素。 尾部的目錄分隔符將被忽略,參閱 path.sep。返回物件包含以下屬性 <string> root <string> base <string> name <string> ext <string>
在posix上:
path.parse('/home/user/dir/file.txt');
// 返回:
// { root: '/',
// dir: '/home/user/dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file' }
┌─────────────────────┬────────────┐
│ dir │ base │
├──────┬ ├──────┬─────┤
│ root │ │ name │ ext │
" / home/user/dir / file .txt "
└──────┴──────────────┴──────┴─────┘
("" 行中的所有空格都應該被忽略,它們純粹是為了格式化)
在window上:
path.parse('C:\\path\\dir\\file.txt');
// 返回:
// { root: 'C:\\',
// dir: 'C:\\path\\dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file' }
┌─────────────────────┬────────────┐
│ dir │ base │
├──────┬ ├──────┬─────┤
│ root │ │ name │ ext │
" C:\ path\dir \ file .txt "
└──────┴──────────────┴──────┴─────┘
("" 行中的所有空格都應該被忽略,它們純粹是為了格式化)
複製程式碼
新建interpolateName檔案
'use strict';
const path = require('path');
const emojisList = require('./emoj-list');
const getHashDigest = require('./getHashDigest');
const emojiRegex = /[\uD800-\uDFFF]./;//判斷是否是emoj表情
const emojiList = emojisList.filter((emoji) => emojiRegex.test(emoji));//將不是emoj表情的排除掉
const emojiCache = {};//emoj表情快取
function encodeStringToEmoji(content, length) {//字串轉emoj表情
if (emojiCache[content]) {
return emojiCache[content];
}
length = length || 1;//如果沒有指定長度就預設為1
const emojis = [];//空陣列
do {//將表情存進emojis陣列裡面
if (!emojiList.length) {
throw new Error('Ran out of emoji');
}
const index = Math.floor(Math.random() * emojiList.length);
emojis.push(emojiList[index]);
emojiList.splice(index, 1);
} while (--length > 0);
const emojiEncoding = emojis.join('');//將陣列轉化為字串
emojiCache[content] = emojiEncoding;
return emojiEncoding;//表情組成的字串
}
function interpolateName(loaderContext, name, options) {
let filename;
if (typeof name === 'function') {//如果是函式
filename = name(loaderContext.resourcePath);
} else {
filename = name || '[hash].[ext]';
}
const context = options.context;
const content = options.content;
const regExp = options.regExp;
let ext = 'bin';
let basename = 'file';
let directory = '';
let folder = '';
if (loaderContext.resourcePath) {//如果存在路徑
const parsed = path.parse(loaderContext.resourcePath);//返回一個物件
let resourcePath = loaderContext.resourcePath;
if (parsed.ext) {
ext = parsed.ext.substr(1);
}
if (parsed.dir) {
basename = parsed.name;
resourcePath = parsed.dir + path.sep;//path.sep在window上是\ posix是/
}
if (typeof context !== 'undefined') {
directory = path
.relative(context, resourcePath + '_')
.replace(/\\/g, '/')
.replace(/\.\.(\/)?/g, '_$1');
directory = directory.substr(0, directory.length - 1);
} else {
directory = resourcePath.replace(/\\/g, '/').replace(/\.\.(\/)?/g, '_$1');
}
if (directory.length === 1) {
directory = '';
} else if (directory.length > 1) {
folder = path.basename(directory);
}
}
let url = filename;
if (content) {
// Match hash template
url = url
// `hash` and `contenthash` are same in `loader-utils` context
// let's keep `hash` for backward compatibility
.replace(
/\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi,
(all, hashType, digestType, maxLength) =>
getHashDigest(content, hashType, digestType, parseInt(maxLength, 10))
)
.replace(/\[emoji(?::(\d+))?\]/gi, (all, length) =>
encodeStringToEmoji(content, parseInt(length, 10))
);
}
url = url
.replace(/\[ext\]/gi, () => ext)
.replace(/\[name\]/gi, () => basename)
.replace(/\[path\]/gi, () => directory)
.replace(/\[folder\]/gi, () => folder);
if (regExp && loaderContext.resourcePath) {
const match = loaderContext.resourcePath.match(new RegExp(regExp));
match &&
match.forEach((matched, i) => {
url = url.replace(new RegExp('\\[' + i + '\\]', 'ig'), matched);
});
}
if (
typeof loaderContext.options === 'object' &&
typeof loaderContext.options.customInterpolateName === 'function'
) {
url = loaderContext.options.customInterpolateName.call(
loaderContext,
url,
name,
options
);
}
return url;
}
module.exports = interpolateName;
複製程式碼
在loader-utils檔案匯入interpolateName,進行測試
- isUrlRequest(判斷是否是路徑)
新建isUrlRequest檔案
'use strict';
const path = require('path');
function isUrlRequest(url, root) {
// An URL is not an request if
// 1. It's an absolute url and it is not `windows` path like `C:\dir\file`
if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !path.win32.isAbsolute(url)) {
return false;
}
// 2. It's a protocol-relative
if (/^\/\//.test(url)) {
return false;
}
// 3. It's some kind of url for a template
if (/^[{}[\]#*;,'§$%&(=?`´^°<>]/.test(url)) {
return false;
}
// 4. It's also not an request if root isn't set and it's a root-relative url
if ((root === undefined || root === false) && /^\//.test(url)) {
return false;
}
return true;
}
module.exports = isUrlRequest;
複製程式碼
在loader-utils檔案匯入isUrlRequest,進行測試
- getCurrentRequest(獲取當前請求)
'use strict';
function getCurrentRequest(loaderContext) {
if (loaderContext.currentRequest) {
return loaderContext.currentRequest;
}
const request = loaderContext.loaders
.slice(loaderContext.loaderIndex)
.map((obj) => obj.request)
.concat([loaderContext.resource]);
return request.join('!');
}
module.exports = getCurrentRequest;
複製程式碼
在loader-utils檔案匯入getCurrentRequest,進行測試
- getRemainingRequest(獲取請求)
新建getRemainingRequest檔案
'use strict';
function getRemainingRequest(loaderContext) {
if (loaderContext.remainingRequest) {
return loaderContext.remainingRequest;
}
const request = loaderContext.loaders
.slice(loaderContext.loaderIndex + 1)
.map((obj) => obj.request)
.concat([loaderContext.resource]);
return request.join('!');
}
module.exports = getRemainingRequest;
複製程式碼
在loader-utils檔案匯入getRemainingRequest,進行測試
五、未完待續
揭開了loader-utils神祕的面紗 為我們手寫webpack打下了堅實的基礎