webpack的loader其实就是一个函数,通过获取上文的代码字符串,对其进行处理来达到需求,一个loader对应一个功能符合单一职责原则
同步loader
//第一种写法
module.exports= function (content,map,meta) {
console.log('test1')
console.log('content=============>',content)
console.log('meta=============>',meta)
return content
}
//第二种写法
module.exports= function (content,map,meta) {
//callback在原型上
/**
* 参数1 err, 代表是否有错误
* 参数2 content 文件内容
* 参数3 sourcemap 继续传递sourcemap 保证不中断
* 参数4 meta 给下一个loader 传递参数
*/
this.callback(null,content,map,{name:'xiaoming'})
}
异步loader
module.exports= function (content,map,meta) {
console.log('test2')
//调用async
const callback = this.async()
setTimeout(() => {
callback(null,content,map,{name:'张三'})
}, 1000);
}
loader在执行完逻辑后 会默认调用callback函数,如果在异步逻辑中直接使用this.callback而不是通过async获取
首先webpack会认为你loader返回为undefined,下一个loader将收不到文件内容,其次因为默认已经调用了callback,而异步逻辑完成后调用this.callback的做法会报错 callback was already called
raw loader(二进制内容)
把module.exports.raw 改为true
module.exports= function (content,map,meta) {
console.log('content=============>',content)
return content
}
//把raw属性改为true就可以了 一般用于图片媒体等的处理,
module.exports.raw =true
pitch loader(熔断机制)
当为一个loader添加上pitch方法,且方法有了返回值就有了熔断机制
module.exports.pitch =function(){
console.log('pitch loader test3')
}
执行顺序
当有pitch方法但没有返回时,loader的排序为
use:['test3-loader.js','test2-loader.js','test1-loader.js']
执行顺序为 test3的pitch->test2的pitch->test1的pitch->test1->test2->test3
当test2的pitch方法有了return
执行顺序为 test3的pitch->test2的pitch->test3
loader的api
更多api查询webpack官网loader 的api webpack.docschina.org/api/loaders…
自定义loader
清除consolo.log的loader
module.exports=function (content,map,meta) {
content=content.replace(/(console.log()(.*)())/g,'')
this.callback(null,content,map,meta)
}
添加作者注释loader
const schema = require( './schema.json')
module.exports= function (content,map,meta) {
const {author} = this.getOptions(schema)
const prefix = `
/*
* Author: ${ author.join(',')}
*/
`;
return prefix+content
}
schema.json
{
"type": "object",
"properties": {
"author": {
"type": "array"
}
},
"additionalProperties": false
}
自定义babel-loader
const babel = require('@babel/core')
const schema = require('./schema.json')
module.exports = function (content,map,meta) {
const call = this.async()
const options = this.getOptions(schema)
babel.transform(content, options, function(err, result) {
if(err)return call (err)
const {code }= result; // => { code, map, ast }
call(null,code)
});
}
file-loader
const loaderUtils= require('loader-utils')
module.exports= function (content,map , meta) {
// console.log('content=============>',content)
//根据文件内容得到hash名
let hashName = loaderUtils.interpolateName(
this,
'[hash].[ext][query]',
{content}
);
hashName=`images/${hashName}`
//将文件输出出去
this.emitFile(hashName, content);
return `module.exports = "${hashName}"`
}
module.exports.raw = true
style.loader
module.exports = function (content) {
}
module.exports.pitch= function (remainingRequest) {
//remainingRequest包含了还未执行的loader路径和对应被处理的文件的路径
//将remainingRequest里的绝对路径改为相对,后续处理只能用相对
console.log('remainingRequest=============>',remainingRequest)
const relativePath = remainingRequest.split('!').map((absulotePath)=>{
//返回absulotePath路径相对于this.context(当前执行文件的路径)的相对路径
return this.utils.contextify(this.context,absulotePath)
}).join('!')
console.log('relativePath=============>',relativePath)
//import style from "!!${relativePath}" 是inline loader的用法
const script = `
import style from "!!${relativePath}"
const styleEl= document.createElement('style')
styleEl.innerHTML= style
document.head.appendChild(styleEl)
`
return script
}