💡Loader是什么?
Loader在webpack中实际担当的是一个模块转换器。他可以在你import一个模块的时候,对这个模块进行预处理,将文件的内容从不同的语言转换为JavaScript,或者将内联图像转换为dataURL。 Loader实际上是一个函数,接收一个文件的内容,经过一系列处理之后,然后返回新的文件内容。
function Loader(content) {
// do something
const newContent = handleContent(content)
return newContent
}
🙇♂️我们为什么需要Loader? webpack运行在node上,仅仅只能识别JavaScript模块,在对所有的模块处理之前,必须将相应模块转换成JavaScript。例如,在我们编写TypeScript代码时,必须通过对应的ts-loader将 TypeScript文件编译成JavaScript,然后webpack才能进行后续的处理。
💡Loader怎么用?
module.exports = {
entry: './src/index.js',
output: '[name].js',
mode: 'development',
module: {
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader'],
enforce: 'post'
},
{
test: /.js$/,
exclude: /node_modules/
use: [{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
]
],
plugins: [
['@babel/plugin-transform-runtime', {
corejs: 3
}],
]
}
}]
}
]
}
}
test
test接受一个正则表达式,模块如果命中了这个正则表达式,就会交由对应的loader去处理。
use
use规定了当匹配到test中的模块时,应该由哪个loader去处理,可以是一个字符串或者一个数组。
当use接受一个数组时,会按照从右到左,从下往上的顺序去处理。
enforce
enforce规定了同一个模块的loader执行顺序,enforce可接受的值为pre,post,normal(默认)。
分别代表了前置,后置和默认。
💡Loader的执行顺序?
在webpack中,loader的执行分为了两个阶段:
- pitch阶段
- normal阶段
module.export = {
module: {
rules: [
{
test: /\.js&/,
use: ['normal1-loader', 'normal2-loader']
},
{
test: /\.js&/,
use: ['pre1-loader', 'pre2-loader']
},
{
test: /\.js&/,
use: ['post1-loader', 'post2-loader']
},
]
}
}
// 以及在入口文件中,存在一个内联loader
import example from 'inline1-loader!inline2-loader!./index.js';
上述例子的执行顺序为:
loader在执行的时候,会首先经过loader.pitch阶段,pitch阶段结束后才会读取文件内容,然后执行normal阶段。
- 在Pitch阶段,顺序为:Post => Inline => Normal => Pre
- 在Normal阶段,顺序为: Pre => Normal => Inline => Post
在Pitch阶段中,如果某一个Loader的pitch函数中返回了一个非undefined的值时,就会发生**熔断**的效果。
所谓熔断,就是指loader原先的执行顺序会被阻断,会立马掉头执行,从当前的loader.pitch中跳出,然后执行上
一个已经执行的loader的normal阶段。
💡Loader的类型?
同步Loader
所谓的同步loader,就是在loader本身处理一些同步的代码逻辑,并且返回相对应的值
function loader(content) {
return content
}
loader.pitch = function () {
...
}
异步Loader
当我们需要在loader执行的时候,进行一些异步操作,例如Ajax请求,定时器等时,我们需要用到异步loader
function asyncLoader() {
return new Promise(resolve => {
setTimeout(() => {
resolve('test')
}, 1000)
})
}
// 或者使用this.async的方式
function asyncLoader() {
const callback = this.async()
callback('test')
}
Raw Loader
当我们处理一些图片或文件等资源时,我们需要得到的文件内容通常是Buffer类型,这时候需要用到我们的Raw属性。
function loader(content) {
return content
}
loader.raw = true
💡Loader的参数?
Normal Loader
normal loader默认接收一个参数,为需要处理的文件内容,如果存在多个loader,上一个loader的返回值,则会传给下一个loader作为入参,直到最后一个loader处理完毕之后,将文件内容交给Webpack处理。
Pitch Loader
Pitchloader通常接收3个参数,分别是:
- remainingRequest
- previousRequest
- data
remainingRequest表示剩余需要处理的loader的绝对路径,并以!分隔组成的字符串。
另外,remainingRequest与剩余loader有没有pitch属性没有关系。
如上图所示,
对于loader1.pitch而言,remainingRequest的值为xxx/loader2.js!xxx/loader3.js。
:::tips previousRequest则表示已经处理过的loader的绝对路径,并以!分隔组成的字符串。 :::
data属性,默认是一个空对象{}。
data属性可以看做normalLoader和pitchLoader的交互桥梁。
// 在pitch函数里面对data赋值
loader2.pitch = function(remainingRequest, previousRequest, data){
data.age = 12
}
// 在loader中可以获取
loader2 = function (){
console.log(this.data.age)
}
💡Loader在Webpack的什么阶段执行?
1、Webpack首先会合并命令行参数和配置文件参数,得到参数对象。
2、将参数对象传递给webpack,得到一个Compiler对象。
3、执行Compiler.run方法,开始编译,生成一个Compliation对象
4、Compliation会依次执行addEntry方法和buildModule方法,在buildModule方法中会根据不同的模块类型 调用不同的loader进行解析和转译,解析完毕会将结果交给webpack进行后续的操作。
💡简单实现常用Loader
/** babel-core是babel转译代码的核心方法,他可以将我们的代码进行
** 词法分析-语法分析-语义分析,从而生成Ast语法树
*/
const babel = require('@babel/core')
const schema = {
type: 'object',
properties: {
presets: {
type: 'array'
}
},
};
module.exports = function (content) {
const callback = this.async();
const options = this.getOptions(schema);
babel.transform(content,options, function(err, result) {
if(err) callback(err)
else callback(null, result.code)
})
}
function styleLoader (content) {
return content;
}
styleLoader.pitch = function (remainingRequest) {
// 将remainingRequest里的loader的绝对路径转为相对路径, 然后再转为inline-loader
const inlineLoder = remainingRequest.split('!').map(absolutePath => {
return this.utils.contextify(this.context, absolutePath)
}).join('!')
// 借助inline-loader来获取css-loader处理好的css样式
// !!禁止inline-loader之后的所有loader的使用
const script = `
import style from "!!${inlineLoder}"
const styleEl = document.createElement('style');
styleEl.innerHTML = style;
document.head.appendChild(styleEl);
`
/** pitch返回了非undefined的值,触发了熔断,
反向执行Normal阶段的loader,由于style-loader前面没有loader了,
所以这里直接结束了loader的周期
*/
return script
}
module.exports = styleLoader