一、loader概念
loader用来帮助webpack将不同类型的文件转换为webpack可识别的模块
二、loader执行顺序
-
loader分类
- pre:前置loader
- normal:普通loader
- inline:内联loader
- post:后置loader
-
执行顺序
- 优先级:pre > normal > inline > post
- 相同优先级的loader执行顺序:从右到左,从下到上
此时loader执行顺序:loader3 -> loader2 -> loader1
rules: [
{ test: /\.js$/, loader: 'loader1' },
{ test: /\.js$/, loader: 'loader2' },
{ test: /\.js$/, loader: 'loader3' }
]
此时loader执行顺序:loader1 -> loader2 -> loader3
rules: [
{ enforce: 'pre', test: /\.js$/, loader: 'loader1' },
{ test: /\.js$/, loader: 'loader2' }, // enforce默认值为normal
{ enforce: 'post', test: /\.js$/, loader: 'loader3' }
]
-
使用loader的方式
- 配置方式:在webpack.config.js中指定loader的enforce
- 内联方式:在每个import语句中显示指定loader(inline loader)
-
inline loader
用法:import Styles from 'style-loader!css-loader?modules!./styles.css'
含义:
- 使用css-loader和style-loader处理styles.css文件
- 通过
!将loader分开
inline loader可以通过添加不同前缀,跳过其他类型的loader:
!跳过normal loader:import Styles from '!style-loader!css-loader?modules!./styles.css'-!跳过pre和normal loader:import Styles from '-!style-loader!css-loader?modules!./styles.css'!!跳过pre、normal和post loader:import Styles from '!!style-loader!css-loader?modules!./styles.css'
三、一个简单的loader
loaders/test-loader.js
/*
loader就是一个函数,当webpack解析资源时,会调用相应的loader去处理,loader接收到文件内容作为参数,返回内容出去
content:文件内容
map:sourceMap
meta:其他数据
*/
module.exports = function (content, map, meta) {
console.log(content);
return content;
};
四、loader分类
同步loader
module.exports = function (content, map, meta) {
return content;
};
this.callback更灵活,允许传递多个参数,而不仅是content
module.exports = function (content, map, meta) {
this.callback(null, content, map, meta);
return; // 当调用callback函数时,总是返回undefined
};
异步loader
由于同步loader计算过于耗时,在nodejs这样的单线程运行环境下进行此操作并不是好的方案,尽可能使loader异步化。但如果计算量很小,使用同步loader也是可以的
module.exports = function (content, map, meta) {
const callback = this.async();
setTimeout(() => {
callback(null, result, map, meta);
}, 1000);
};
raw loader
默认情况下,资源文件会被转化为utf-8字符串,然后传给loader。通过设置raw为true,loader可以接收原始的buffer
module.exports = function (content, map, meta) {
// content是一个buffer数据
return content;
};
module.exports.raw = true; // 开启raw loader
pitching loader
webpack会先从左到右执行loader链中每个loader上的pitch方法(如果有),然后再从右到左执行loader链中每个loader上的普通loader方法
在这个过程中,如果某个pitch有返回值,则loader链中断,webpack会跳过后面的pitch和loader,直接进入上一个loader
module.exports = function (content, map, meta) {
return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
console.log('do somethings');
};
五、loader API
| 方法名 | 含义 | 用法 |
|---|---|---|
| this.async | 异步回调loader,返回this.callback | const callback = this.async() |
| this.callback | 可以同步或者异步调用,并返回多个结果的函数 | this.callback(err, content, sourceMap?, meta?) |
| this.getOptions(schema) | 获取loader配置项中的options | this.getOptions(schema) |
| this.emitFile | 产生一个文件 | this.emitFile(name, content, sourceMap) |
| this.utils.contextify | 返回一个相对路径 | this.utils.contextify(context, request) |
| this.utils.absolutify | 返回一个绝对路径 | this.utils.absolutify(context, request) |
六、手写clean-log-loader
作用:清除console.log()
loaders/clean-log-loader.js
module.exports = function (content) {
return content.replace(/console\.log\(.*\);?/g, '');
};
main.js
console.log('main')
webpack.config.js
rules: [
{
test: /\.js$/,
use: ['./loaders/clean-log-loader']
}
]
执行npx webpack,打包后的js文件中的console.log(...)会被替换为''
七、手写banner-loader
作用:给js代码添加文本注释
loaders/banner-loader/sehema.json
规则:
- options是一个对象
- options中有个属性author,并且属性值为字符串
- 不能再有其他属性
{
"type": "object",
"properties": { "author": { "type": "string" } },
"additionalProperties": false
}
loaders/banner-loader/index.js
- this.getOptions用于获取loader配置项中的options属性
- schema是这个属性按照什么规则去写
const schema = require('./schema.json');
module.exports = function (content) {
const options = this.getOptions(schema);
const prefix = `
/*
author: ${options.author}
年龄: ${options.age}
*/
`;
return `${prefix} \n ${content}`;
};
webpack.config.js
{
test: /\.js$/,
loader: './loaders/banner-loader/index',
options: { author: '吴小明' }
}
效果:
八、手写babel-loader
作用:将es6+编译成es5-
下载包
cnpm i @babel/core @babel/preset-env -D
loaders/babel-loader/schema.js
module.exports = {
type: 'object',
properties: { presets: { type: 'array' } },
additionalProperties: true
};
loaders/babel-loader/index.js
const schema = require('./schema');
const babel = require('@babel/core');
module.exports = function (content) {
const options = this.getOptions(schema);
const callback = this.async();
babel.transform(content, options, function (err, res) {
callback(err || null, res.code);
});
};
main.js
const sum = (...rest) => {
return rest.reduce((a, b) => a + b, 0);
};
console.log(sum(1, 2, 3));
webpack.config.js
{
test: /\.js$/,
loader: './loaders/babel-loader/index',
options: { presets: ['@babel/preset-env'] }
}
效果:
九、手写file-loader
作用:将文件原封不动输出出去
下载包
cnpm i loader-utils -D
loaders/file-loader.js
const loaderUtils = require('loader-utils');
const schema = {
type: 'object',
properties: { filePath: { type: 'string' } }
};
function fileLoader(content) {
const options = this.getOptions(schema);
// 根据文件内容生成文件名
let filename = loaderUtils.interpolateName(this, '[hash].[ext]', {
content
});
filename = `${options.filePath}/${filename}`; // 输出到指定目录下
this.emitFile(filename, content); // 输出文件
return `module.exports = '${filename}'`;
}
fileLoader.raw = true; // 图片是buffer数据
module.exports = fileLoader;
webpack.config.js
{
test: /\.(png|jpe?g|gif)$/,
loader: './loaders/file-loader',
options: { filePath: 'images' },
type: 'javascript/auto' // 阻止webpack默认处理图片资源,只使用file-loader处理
}
main.js
import './images/1.jpeg'
import './images/2.png'
import './images/3.gif'
执行npx webpack,dist目录下会多出3张图片资源
十、手写style-loader
作用:动态创建style标签,插入js中的样式代码,使样式生效
下载包
cnpm i css-loader -D
loaders/style-loader.js
const styleLoader = () => {};
styleLoader.pitch = function (remainingRequest) {
// remainingRequest值:D:\desktop\forward\webpack5\loader\node_modules\_css-loader@6.8.1@css-loader\dist\cjs.js!D:\desktop\forward\webpack5\loader\src\css\index.css
const relativeRequest = remainingRequest // 如果在配置项中不写css-loader,这里remainingRequest就拿不到css-loader的路径
.split('!')
.map(n => this.utils.contextify(this.context, n))
.join('!');
// 处理后:../../node_modules/_css-loader@6.8.1@css-loader/dist/cjs.js!./index.css
const script = `
import style from '!!${relativeRequest}' // relativeRequest是css-loader处理后的资源,!!跳过pre、normal和post loader
const styleEl = document.createElement('style')
styleEl.innerHTML = style
document.head.appendChild(styleEl)
`;
return script; // 终止css-loader、style-loader中normal方法的执行
};
module.exports = styleLoader;
webpack.config.js
{
test: /\.css$/,
// use: ['style-loader', 'css-loader']
use: ["./loaders/style-loader", "css-loader"],
}
效果: