loader原理
概念
帮助webpack将不同类型的文件转换为webpack可识别的模块。
执行顺序
- 分类 除内联外添加enforce属性设置
- pre: 前置loader
- normal: 普通loader
- inline: 内联loader
import Styles form 'style-loader!css-loader?modules!./index.css'; // 可以通过加不同前缀 跳过其他类型loader // ! 跳过normal loader import Styles form '!style-loader!css-loader?modules!./index.css'; // -! 跳过跳过pre normal loader import Styles form '-!style-loader!css-loader?modules!./index.css'; // !! 跳过pre normal post loader import Styles form '!!style-loader!css-loader?modules!./index.css'; - post: 后置loader
- 执行顺序
- 优先级:pre > normal > inline > post
- 相同优先级 从右到左 从下到上
第一个loader
/*
loader就是一个函数,当webpack解析资源时,会调用相应的loader去处理
loader接收文件内容作为参数,并返回内容出去
content 文件内容
map SourceMap
meta 别的loader传递的数据
*/
module.exports = function(content, map, meta){
return content
}
loader种类
- 同步loader
<!-- module.exports = function(content){ return content } --> /* 第一个参数 error 代表是否有错误 第二个参数 content 处理后的内容 第三个参数 source-map 继续传递 source-map 第四个参数 meta 给下一个loader传递参数 */ module.exports = function(content, map, meta){ this.callback(null, content, map, meta); } - 异步loader
module.exports = function(content, map, meta){ const callback = this.async(); setTimeout(() => { callback(null, content, map, meta); }, 1000) } - raw loader
// raw loader接收到的content是Buffer数据(二进制) module.exports = function(content){ return content } module.exports.raw = true; - pitch loader
module.exports = function(content){ return content } // pitch方法会从前到后先执行 一旦return 后面pitch不执行 之执行上一个loader的正常方法 再从后往前执行正常方法 module.exports.pitch = function(){}
loader API
- this.async()
异步回调loader,返回this.callback。const callback = this.async(); - this.callback()
可以同步或异步调用的并返回多个结果的函数 - this.getOptions(schema)
获取loader的options - this.emitFile(name, content, sourceMap)
产生一个文件 - this.utils.contextify(context, request)
返回一个相对路径 - this.utiles.absolutify(context, request)
返回一个绝对路径
手写clean-log-loader
module.exports = function CleanLogLoader(content){
return content.replace(/console\.log\(*\);?/g, "");
}
自定义banner-loader
const schema = {
"type": "object",
"properties": {
"author": "string"
},
"addtionalProperties": false
}
module.exports = functin(content){
// schema 对options的验证规则
// schema符合JSON Schema的规则
const options = this.getOptions(schema)
const prefix = `
/*
* Author: ${options.author}
*/
`;
return prefix + content
}
自定义babel-loader
// 下载 @babel/core @babel/preset-env
const babel = require('@babel/core');
module.exports = function (content) {
const schema = {
"type": "object",
"properties": {
"presets": "array"
},
"addtionalProperties": true,
}
const callback = tihs.async();
const options = getOptions(schema)
// 使用babel对代码进行编译
babel.transform(code, options, function (err, result) {
if (err) callback(err)
else callback(null, result.code)
})
}
自定义file-loader
将资源原样输出 type: asset
// 下载loader-utils
const loaderUtils = require('loader-utils');
module.exports = function (content) {
// 根据文件内容生成hash值文件名
// 将文件输出到dist
// return `module.exports = ${interpolatedName}`
let interpolatedName = loaderUtils.interpolateName(
this, // loaderContext,
"[hash].[ext][query]",
{
content
}
)
interpolatedName = `images/${interpolatedName}`
this.emitFile(interpolatedName, content);
return `module.exports = ${interpolatedName}`
}
// 需要处理字体图片
module.exports.raw = true;
// 配置loader时可以 type: javascript/auto 阻止webpack默认处理图片资源
自定义style-loader
module.exports = function (content) {
/*
1. 直接使用style-loader,只能处理样式,不能处理样式中引入的其他资源
2. 借助css-loader解决样式中引入的其他资源的问题
问题是css-loader暴露了一段js代码,style-loader需要执行js代码,得到返回值,
再动态创建style标签,插入到页面中,不好操作
*/
// const script = `
// const styleEl = document.createElement('style');
// styleEl.innerHTML = ${JSON.stringify(content)};
// document.head.appendChild(styleEL);
// `
}
module.exports.pitch = function (remainingRequest) {
// remainingRequest 剩下还需要处理的loader
// 1. 将remainingRequest中绝对路径改成相对路径(因为后面只能使用相对路径)
const relativePath = remainingRequest.split('!').map((absolutePath) => {
return this.utils.contextify(this.context, absolutePath)
}).join('!');
// 2. 引入css-loader处理后的资源
// 3. 创建style,将内容引入到页面中生效
const script = `
import style from "!!${relativePath}";
const styleEl = document.createElement('style');
styleEl.innerHTML = style;
document.head.appendChild(styleEL);
`
// 终止后面loader执行
return script;
}