# 实现常用的loader
# loader知识查漏补缺
# 什么是loader
webpack
只能处理js的模块,如果要处理其他类型的文件,需要使用loader
来进行转换。loader
是webpack
中一个重要的概念,它是指用来将一段代码转化为另一段代码的webpack
加载器
# loader的加载顺序
默认来说loader
的执行顺序是从右到左,从下到上。
但是根据loader
的分类loader
的执行顺序也可以为pre
- normal
- inline
- post
# inline-loader的用法
行内loader
我们用的比较少,我们一般是在js文件中引入行内loader
,而不是在配置文件了配置
const str = require(inline-loader!'./a.js')
表示加载a.js
这个文件使用行内loader来处理
我们还需要注意行内loader的一些规则写法
const str = require(-!inline-loader!'./a.js') //-! 表示文件不会通过pre-normal来处理了
const str = require(!inline-loader!'./a.js') //! 表示文件不会通过normal来处理了
const str = require(!!inline-loader!'./a.js') //!! 表示文件只经过行内loader来说处理了
# pitchLoader 和 normalLoader
每个loader
都有两部分组成pitchLoader
和 normalLoader
, pitch
和normal
的执行顺序是相反的
pitchLoader
没有返回值
pitchLoader
有返回值
# loader的特点
第一个
loader
要返回js脚本每个
loader
只做一件内容,为了让更多的loader
在更多的场景链式调用每一个
loader
都是一个模块每个
loader
都是无状态的,确保loader
在不同模块转换之间不保存状态
# 实现babel-loader
首先安装需要的包
npm i @babel/core @babel/preset-env loader-utils
我们来看下使用babel-loader
时候的配置
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
}
然后我们来实现一个babel-loader
const babel = require('@babel/core')
const utils = require('loader-utils')
function loader (source) {
const options = utils.getOptions(this) //拿到当前loader的配置项
const cb = this.async() //因为babel转换是异步 这里用提供的方法来处理异步
babel.transform(source, {
...options, //拿到配置中配置的preset
sourceMap: true, //开启soucreMap 方便调试
filename: this.resourcePath.split('/').pop() //拿到当前文件名作为map文件的名字
}, function (err, res) {
cb(err, res.code, res.map) // 将处理之后的代码返回
})
}
module.exports = loader
# 实现file-loader
file-loader
的大致作用是根据图片生成一个md5,发射到dist目录下,dile-loader
还会返回当前的图片路径
const utils = require('loader-utils')
function loader(source) {
const filename = utils.interpolateName(this, '[hash].[ext]', {
content: source
}) //将图片拼上hash戳
this.emitFile(filename, source) //发射文件
return `module.exports = "${filename}"` //替换生成的字符串
}
loader.raw = true //将源码转成二进制
module.exports = loader
# 实现url-loader
url-loader
可以把某一些图片转化为base64,并且会自动判断转化为base64还是使用file-loader
处理
const utils = require('loader-utils')
const mime = require('mime')
function loader(source) {
const {limit} = utils.getOptions(this)
if (limit && limit > source.length) { //如果设置的大小大于文件的大小,要把图片转化为base64
return `module.exports = "data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}"`
} else {
return require('./file-loader').call(this, source)
}
}
loader.raw = true
module.exports = loader
# 实现less-loader
let less = require('less');
function loader(source) {
let css;
less.render(source,function (err,r) { // r.css
css = r.css;
});
return css
}
module.exports = loader;
# 实现css-loader
function loader(source) {
let reg = /url\((.+?)\)/g;
let pos = 0;
let current;
let arr = ['let list = []'];
while (current = reg.exec(source)) { // [matchUrl,g]
let [matchUrl, g] = current;
let last = reg.lastIndex - matchUrl.length;
arr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`);
pos = reg.lastIndex;
// 把 g 替换成 require的写法 => url(require('xxx'))
arr.push(`list.push('url('+require(${g})+')')`);
}
arr.push(`list.push(${JSON.stringify(source.slice(pos))})`)
arr.push(`module.exports = list.join('')`);
return arr.join('\r\n');
}
module.exports = loader;
# 实现style-loader
let loaderUtils = require('loader-utils');
function loader(source) {
// 我们可以在style-loader中导出一个 脚本
let str = `
let style = document.createElement('style');
style.innerHTML = ${JSON.stringify(source)};
document.head.appendChild(style);
`
return str;
}
// 在style-loader上 写了pitch
// style-loader less-loader!css-loader!./index.less
loader.pitch = function (remainingRequest) { // 剩余的请求
// 让style-loader 去处理less-loader!css-loader/./index.less
// require路径 返回的就是css-loader处理好的结果 require('!!css-loader!less-loader!index.less')
let str = `
let style = document.createElement('style');
style.innerHTML = require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)});
document.head.appendChild(style);
`
return str;
}
module.exports = loader;