Loader 用于对模块的源代码进行转换,本质上就是一个导出函数的 JavaScript 模块。loader runner
库会调用导出的函数,并且将上一个 Loader 产生的结果或者资源文件作为参数传进去。
自定义 Loader:
- 新建
src/index.js
文件,并编写代码。// src/index/.js console.log('index')
- 新建
src/custom-loaders/js-loader.js
文件,并编写自定义 loader 的代码。// src/custom-loaders/js-loader.js // 导出一个函数,默认接收三个参数:第一个参数是 Webpack 读取到的要加载的模块的内容,会放到 content 中;第二个参数是 sourcemap 映射;第三个参数是 meta 元数据 module.exports = function (content, sourcemap, meta) { // 必须返回处理后的结果 return content + ';' }
- 在
webpack.config.js
中进行配置。// webpack.config.js module.exports = { // 为了能更明确地看到效果,使用 development 模式,防止 Webpack 进行某些默认的优化 mode: 'development', module: { rules: [ // 使用自定义的 js-loader 来处理 JavaScript 文件 { test: /\.js$/, // 默认会去 node_modules 下查找要使用的 loader,因为此处使用的是自定义 loader,在 node_modules 中没有,因此要明确告知 Webpack 去哪里找 loader loader: './src/custom-loaders/js-loader', // 也可以结合下面的 resolveLoader,那样的话就不需要明确写出路径了 // loader: 'js-loader', } ] }, // resolveLoader: { // 配置去哪里查找 loader // modules: ['node_modules', './src/custom-loaders'], // } }
- 运行
webpack
命令进行打包,会发现,使用js-loader
处理 JavaScript 文件成功了。
自定义不同功能的 Loader:
同步 Loader:
同步 Loader 在函数执行完之前必须返回内容。
// 同步 Loader,在函数执行完之前必须返回内容
module.exports = function (content) {
// 可以通过 return 返回
return content
// 也可以通过 this.callback() 函数返回。其中 this 是 Loader 的上下文对象,callback() 函数接收四个参数,分别是错误信息、content 内容、sourcemap、meta 元数据
// this.callback(null, content)
}
异步 Loader:
如果想在 Loader 中执行一些异步操作之后再返回内容的话,使用同步 Loader 就会报错。
// 同步 Loader
module.exports = function (content) {
// 执行一些异步操作之后再返回内容
setTimeout(() => {
return content
}, 1000)
}
可以使用异步 Loader。
// 异步 Loader
module.exports = function (content) {
// 只要调用 this.async(),这个 Loader 就会变成异步 Loader。会返回 callback,允许在之后的某个时刻再调用 callback() 返回内容
const callback = this.async()
// 执行一些异步操作之后,再返回内容
setTimeout(() => {
callback(null, content+';')
}, 1000)
}
Row loader
:
默认情况下,Webpack 会把资源文件转化为 JavaScript 语法格式的字符串后传递给 Loader;可以设置 raw 属性为 true,将会把原始的 Buffer 二进制数据传递给 Loader。
每一个 Loader 都可以用 String 或者 Buffer 的形式传递它的处理结果,Complier 编译器会把它们在 Loader 之间相互转换。
module.exports = function (content) {
// content 是一个 Buffer 数据
return content
}
module.exports.raw = true
Pitching Loader
:
设置 pitch 方法后,在实际从后往前执行 Loader 之前,会先从前往后调用 Loader 上的 pitch 方法。
如果任何 pitch 有返回值,那么 Loader 链被阻断,Webpack 会跳过后面所有的 pitch 和 Loader,直接返回上一个 Loader。
// js-loader1
module.exports = function (content) {
console.log('js-loader1')
return content
}
module.exports.pitch = function () {
console.log('Pitching Loader1')
}
// js-loader2
module.exports = function (content) {
console.log('js-loader2')
return content
}
module.exports.pitch = function () {
console.log('Pitching Loader2')
}
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.js$/,
// 使用多个 loader
use: ['js-loader1','js-loader2'],
},
],
},
resolveLoader: {
modules: ['node_modules', './src/custom-loaders'],
}
}
获取传入自定义 Loader 中的参数:
- 在
webpack.config.js
配置文件中给 Loader 传入参数。module.exports = { mode: 'development', module: { rules: [ { test: /\.js$/, use: { loader: 'js-loader', // 传入 Loader 的参数 options: { name: 'Lee', age: 18, } } }, ], }, resolveLoader: { modules: ['node_modules', './src/custom-loaders'], } }
- 在自定义 Loader 中获取传入的参数。
module.exports = function (content) {
// 获取传入 Loader 的参数。其中,this 是 Loader 的上下文对象
const options = this.getOptions()
console.log(options)
return content
}
- 运行
webpack
命令进行打包,会发现,成功获取到了传入的参数。
对传入自定义 Loader 中的参数进行校验:
可以使用 schema 来对参数进行校验。
- 安装
schema-utils
库:npm install schema-utils --save-dev
。 - 编写
src/custom-loaders/schema.json
校验文件。{ // options 是一个对象 "type": "object", // 参数 "properties": { "name": { // name 是一个字符串,如果错误的话提示需要是一个字符串 "type": "string", "description": "需要是一个字符串" }, "age": { "type": "number", "description": "需要是一个数字" } }, // 除了 properties 中指定的参数外,是否还允许传入其他附加的参数 "additionalProperties": true, }
- 在
webpack.config.js
配置文件中给 Loader 传入参数。module.exports = { mode: 'development', module: { rules: [ { test: /\.js$/, use: { loader: 'js-loader', // 传入 Loader 的参数 options: { name: 'Lee', age: '18', } } }, ], }, resolveLoader: { modules: ['node_modules', './src/custom-loaders'], } }
- 在自定义 Loader 中对传入的参数进行校验。
const {validate} = require('schema-utils') const schema = require('./schema.json') module.exports = function (content) { // 获取传入 Loader 的参数 const options = this.getOptions() // 对参数进行校验 validate(schema, options) return content }
- 运行
webpack
命令进行打包,会发现,传入的 age 参数类型报错了。
自定义的 babel-loader
:
babel-loader
本质上是使用 babel 来转换代码的,因此可以自己使用 babel 来实现一个 babel-loader
。
- 新建
src/index.js
文件,并编写代码。const name = 'Lee' console.log(name)
- 在
webpack.config.js
配置文件中配置使用自定义的babel-loader
。module.exports = { mode: 'development', module: { rules: [ { test: /\.js$/, use: { // 使用自定义的 babel-loader loader: './src/custom-babel-loader', options: { presets: [ '@babel/preset-env', ] } } }, ], }, }
- 安装 babel 的核心
@babel/core
:npm install @babel/core --save-dev
。 - 新建
src/custom-babel-loader
,编写自定义的babel-loader
代码。const babel = require('@babel/core') module.exports = function (content) { // 设置为异步的 Loader const callback = this.async() // 获取传入 Loader 的参数 const options = this.getOptions() // 对源代码进行转换 babel.transform(content, options, (err, result) => { if (err) { callback(err) } else { callback(null, result.code) } }) }
- 运行
webpack
命令进行打包,会发现,JavaScript 代码被 babel 转换了。
Loader 的执行顺序:
如果对一个模块使用了多个 Loader,默认情况下会从后往前执行 Loader。
Loader 最终其实是由
loader-runner
库来处理的。执行顺序具体的实现逻辑可查看loader-runner
库的lib/LoaderRunner.js
文件。
- 新建
src/index.js
文件,并编写代码。// src/index/.js console.log('index')
- 在
webpack.config.js
中进行配置。module.exports = { mode: 'development', module: { rules: [ { test: /\.js$/, // 使用多个 loader use: ["js-loader1", "js-loader2"], }, ], }, resolveLoader: { modules: ['node_modules', './src/custom-loaders'], } }
- 新建
src/custom-loaders/js-loader1.js
文件和src/custom-loaders/js-loader2.js
,并编写自定义 loader 的代码。// src/custom-loaders/js-loader1.js module.exports = function (content) { console.log('js-loader1') return content }
src/custom-loaders/js-loader2.js // Normal Loader module.exports = function (content) { console.log('js-loader1') return content }
- 运行
webpack
命令进行打包,会发现,对一个模块使用了多个 loader,会从后往前执行 Loader。
通过配置选项改变 Loader 的执行顺序:
可以通过配置 enforce 选项来改变 Loader 的执行顺序。enforce 选项用来指定 Loader 的种类,属性值有:
- normal:普通 Loader。所有的 Loader 默认都是普通 Loader。
- inline:内联 Loader。
在
import/require
语句中指定要使用的 loader。例如:在导入 CSS 文件时,可以通过import 'style-loader!css-loader!../css/component.css'
指定使用style-loader
和'css-loader
转换 CSS 模块。 - pre:前置 Loader。
- post:后置 Loader。
这四种 Loader 的执行顺序为 pre -> normal -> inline -> post
;如果种类相同,那么执行顺序为从后往前。
测试代码:
- 在
webpack.config.js
配置文件中,首先需要将多个 Loader 分别拆分到单独的 rule 对象中,然后配置 enforce 选项。module.exports = { mode: 'development', module: { // 将处理 JavaScript 模块的多个 loader 分别拆分到单独的 rule 中 rules: [ { test: /\.js$/, loader: "js-loader1", // 配置 enforece enforce: 'pre', }, { test: /\.js$/, loader: "js-loader2", }, ], }, resolveLoader: { modules: ['node_modules', './src/custom-loaders'], } }
- 运行
webpack
命令进行打包,会发现,js-loader1
被最先执行了。