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打下了坚实的基础