Created By JishuBao on 2019-03-29 12:38:22
Recently revised in 2019-04-01 12:38:22css
歡迎你們來到技術寶的掘金世界,您的star是我寫文章最大的動力!GitHub地址 前端
文章簡介:node
一、webpack module簡介jquery
二、rules的使用webpack
三、loader-utils是什麼?git
四、api實現github
五、未完待續web
如今前端的技術突飛猛進,如今比較火的前端打包工具! webpack,我想各位小夥伴應該也有所瞭解,那麼你知道如何手寫webpack嗎,做爲手寫webpack系列的第一篇文章,我將帶領你們逐步走進webpack的世界。webpack中有一個及其重要的概念loader,那什麼是loader呢?在webpack的官網中,咱們能夠看到這樣一行對Loader的定義,原來是處理js後綴文件名除外的文件。正則表達式
webpack 可使用 loader 來預處理文件。這容許你打包除 JavaScript 以外的任何靜態資源。你可使用 Node.js 來很簡單地編寫本身的 loader。npm
在loader中,我們能夠經過關鍵詞this訪問當前執行環境的全部變量
一、同步回調時,能夠執行this.callback(),默認第一個參數是err錯誤信息(不報錯時返回null),第二個參數是解析完模塊後的返回結果,第三個參數是sourceMap(可選),在鏈式調用時可將sourceMap傳給下一個loader;
二、異步回調時,能夠執行this.async(),參數同上;
三、this.addDependency(filePath)能夠把對應filePath的文件添加到webpack的依賴樹,webpack能夠監測它的文件變更並刷新(filePath要是絕對路徑);
四、this.resolve()能夠解析處理文件路徑;
五、this.query:獲取loader的配置選項。
瞭解或者使用過webpack的小夥伴都知道loader是使用在weboack的module裏面的,用於處理module文件的。咱們來看下module屬性對應的一些經常使用api!
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);
}
複製代碼
配置模塊的讀取和解析規則,一般用來配置 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'),
]
}
複製代碼
數組裏的每項之間是或的關係,即文件路徑符合數組中的任何一個條件就會被命中。
由於 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
}
},
]
}
複製代碼
根據上文咱們已經基本瞭解了rule的使用,裏面放一些loader處理對應的文件。隨意打開一個文件,這裏已經常使用的loader file-loader舉例。
那麼utils-loader到底是什麼呢?
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-equal-utils文件夾存放模仿loader-utils工具類的功能。新建index.js文件夾。
將傳遞的字符串(例如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.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.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在避免絕對路徑時使用的字符串。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,進行測試
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,進行測試
const digestString = loaderUtils.getHashDigest(buffer, hashType, digestType, 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,進行測試
前置知識 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文件
'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,進行測試
'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文件
'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打下了堅實的基礎