前言
我们平时使用webpack打包编译项目的时候,极大概率会使用loader对模块源代码进行转换。比如用sass-loader/less-loader将sass编译成css语言。
Loader基本概念
- 本质是一个函数
- 支持链式调用
- 可以同步也可以异步
- 按照相反的顺序执行(use数组里面是从下往上执行)
- 其pitch方法,use数组中pitch方法的执行顺序是从上往下执行
- 可通过options对象配置
- 命名规则:xxx-loader(例如style-loader)
准备工作
- 新建webpack.config.js
- npm i webpack webpack-cli -D
代码演示
- loader从下而上的执行顺序
// 使用less-loader/sass-loader
use: [
"style-loader",
"css-loader",
"less-loader",
]
- loader文件
// loader1.js
// 参数:文件内容(可能是文件内容,也可能是上一个 loader 处理结果)、文件sourceMap的映射信息、文件的额外信息(经常用来传递 ast 对象)
module.exports = function (content, map, meta) {
console.log('FE 1111');
return content;
}
// loader2.js
module.exports = function (content, map, meta) {
console.log('FE 222');
return content;
}
// loader3.js
module.exports = function (content, map, meta) {
console.log('FE 333');
return content;
}
- webpack配置
// webpack.config.js
const { resolve } = require('path')
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.js$/,
/*
单个loader:
*/
// 未配置resolve之前的写法
// loader: resolve(__dirname, 'loaders', 'loader1')
// loader: 'loader1'
/*
使用多个loader:
*/
use: [
'loader1',
'loader2',
'loader3'
]
}
]
},
resolve: {},
// 如果仅配置loader的解析,可直接使用resolveLoader,他与 resolve 对象的属性集合相同,但仅用于解析 webpack 的 loader 包
resolveLoader: {
// 告诉 webpack 解析模块时应该搜索的目录
modules: [
'node_modules', // 默认的
resolve(__dirname, 'loaders') // 自定义的
]
}
}
- 使用pitch
如果pitch有返回值,将跳过剩下的 loader
- remainingRequest : 当前 loader 之后的资源请求字符串
- previousRequest : 在执行当前 loader 之前经历过的 loader 列表
- data : 与 Loader 函数的 data 相同,用于传递需要在 Loader 传播的信息
// loader3.js
module.exports = function (content, map, meta) {
console.log('FE 333');
return content;
}
module.exports.pitch = function () {
console.log('FE pitch 333');
// return remainingRequest
}
- 使用addDependency
// less-loader
try {
result = await (options.implementation || less).render(data, lessOptions);
} catch (error) {
// ...
}
const { css, imports } = result;
imports.forEach((item) => {
// ...
this.addDependency(path.normalize(item));
});
代码中首先调用 less 编译文件内容,之后遍历所有 import 数组,调用 this.addDependency 函数将 import 到的其它资源都注册为依赖,之后这些其它资源文件发生变化时都会触发重新编译
- 可同步亦可异步
- 同步
// loader2.js 同步
module.exports = function(content, map, meta) {
console.log("FE 222")
// 使用return
// return content
// 或使用callback函数,返回 undefined
this.callback(null, content, map, meta)
}
- 异步
// loader2.js 异步
module.exports = function (content, map, meta) {
console.log('FE 222');
// 使用async方法
const callback = this.async();
setTimeout(() => {
callback(null, content);
}, 2000)
}
- 获取options
- getOptions:提取给定的 loader 选项,接受一个可选的 JSON schema 作为参数
- tips: 从 webpack 5 开始,this.getOptions 可以获取到 loader 上下文对象。它用来替代来自 loader-utils 中的 getOptions 方法。
- 获取options之后我们可以拿到其中的配置,针对content做一些操作,比如替换字符等
- webpack5之前的用法:
// 1. 安装:
npm i loader-utils schema-utils -D
// 2. 导入:
const { getOptions } = require("loader-utils")
const { validate } = require("schema-utils")
// 3. 使用:
const options = getOptions(this);
validate(schema, options, {
name: 'loader1'
})
ps:我们在升级了sass-loader等loader之后,可能会出现 “TypeError: this.getOptions is not a function” 的报错信息,其实就是loader版本太高与webpack版本不匹配
- 修改loader文件
const schema = require('./schema');
// 基础
// module.exports = function(content, map, meta) {
// console.log("FE 111")
// // console.log(content)
// // 使用 return
// // return content
// // 或使用 callback 函数
// this.callback(null, content, map, meta)
// }
// 校验otions
module.exports = function(content, map, meta) {
console.log("FE 111")
// console.log(content)
/*
getOptions:提取给定的 loader 选项,接受一个可选的 JSON schema 作为参数
*/
const options = this.getOptions(schema)
console.log('loader1 options:', options)
// 使用 return或使用 callback 函数
// return content
this.callback(null, content, map, meta)
}
- schema.json文件
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "名称"
}
},
// 如果设置为true表示除了校验前面写的string类型还可以 接着 校验其余类型,如果为false表示校验了string类型之后不可以再校验其余类型
"additionalProperties": false
}
- 如果otions不符合规范会有报错信息
- 自定义babel-loader
- my-babel-loader.js
// npm i @babel/core @babel/preset-env -D
// 导入Babel核心库
const babel = require('@babel/core');
// 导入promisify
const { promisify } = require('util');
const schema = require('./my-babel-schema.json');
// babel.transform用来编译代码的方法,是一个普通异步方法
// util.promisify将普通异步方法转化成基于promise的异步方法
const transform = promisify(babel.transform);
module.exports = function (content, map, meta) {
// 获取loader的options配置并校验
const options = this.getOptions(schema)
// 创建异步
const callback = this.async();
// 使用babel编译代码
transform(content, options)
.then(({code, map}) => callback(null, code, map, meta))
.catch((e) => callback(e))
// try {
// const result = transform(content, options)
// callback(null, result, map, meta)
// } catch(err) {
// // 第一个参数有值时,其他参数无效
// callback(err)
// }
}
module.exports.raw = true;
- my-babel-schema.json
{
"type": "object",
"properties": {
"presets": {
"type": "array"
}
},
"additionalProperties": false
}
- webpack.config.js
const { resolve } = require('path')
module.exports = {
mode: "production", // development
module: {
rules: [
{
test: /\.js$/,
/*
单个loader:
*/
// 未配置resolve之前的写法
// loader: resolve(__dirname, 'loaders', 'loader1')
// loader: 'loader1'
/*
使用多个loader:
*/
use: [
// 'loader1',
// 配置options
{
loader: 'loader1',
options: {
// name: true // options 会有报错信息
name: 'FE'
}
},
'loader2',
'loader3',
// 使用自定义的babel-loader:
{
loader: 'my-babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
]
}
]
},
resolve: {},
// 如果仅配置loader的解析,可直接使用resolveLoader,他与 resolve 对象的属性集合相同,但仅用于解析 webpack 的 loader 包
resolveLoader: {
// 告诉 webpack 解析模块时应该搜索的目录
modules: [
'node_modules', // 默认的
resolve(__dirname, 'loaders') // 自定义的
]
}
}