loader 原理和执行顺序
一、什么是loader Webpack中的loader是用于处理一些非JavaScript`文件的代码转换器,例如将ES6/ES7转换成ES5,将SCSS/Sass/less转换成CSS。loader可以让webpack支持更多的文件类型,并且也可以通过loader将一些资源打包成JS文件中。 loader是针对module级别的,即针对每个待打包的文件进行转换处理,并最终输出打包后的文件。在webpack.config.js文件中配置loader,可以指定不同的文件类型使用不同的loader进行处理,也可以通过链式调用多个loader完成某种特定的处理。 二、常见loader babel-loader: 用于将ES6、ES7等高级语法转换为ES5语法,以便于浏览器兼容。 css-loader: 用于处理CSS文件中的import、url等导入语法。 file-loader: 用于将文件以特定格式打包处理,支持将文件重命名、分离等多种操作。 url-loader: 类似于file-loader, 但将小于指定大小的文件转换为base64格式,以减少请求量。 image-loader: 用于处理图片文件,支持压缩、解析等多种功能。 json-loader: 用于处理JSON格式的文件,支持import导入语法。 xml-loader: 用于处理XML格式的文件。 csv-loader: 用于处理CSV格式的文件。 三、loader分类 Webpack中的loader可以分为两类:普通loader和enforce-loader。普通loader是指在webpack配置文件中没有设置enforce属性的loader,而enforce-loader是指在webpack配置文件中设置了enforce属性的loader。 普通loader执行的顺序是从右向左,从下往上。也就是说,在webpack配置文件中,最后一次加入的loader会最先执行,然后依次向上执行。 而enforce-loader则有更高的优先级,会先于普通loader执行。enforce属性可设置为pre或post,如果设置为pre,表示在普通loader之前执行;如果设置为post,表示在普通loader之后执行。这样就可以确保enforce-loader在所有loader中优先执行,并且能够准确地对文件进行处理。 常用的enforce-loader有以下几种: pre-loader:在执行普通loader之前处理文件,常用于代码检查和格式化等操作; post-loader:在执行普通loader之后处理文件,常用于对文件进行压缩、优化等操作; noParse-loader:当文件匹配指定规则时,忽略指定文件的递归解析和处理,提升webpack性能; ignore-loader:忽略指定文件的解析和处理。 四、普通loader执行顺序举例 Webpack中普通loader的执行顺序是从右到左、从下到上的。也就是说,在webpack配置文件中,loader的引入是按照从右到左的顺序进行的,loader的执行顺序是从下到上的。 举个例子,假设我们在webpack的配置文件中使用了以下loader: module: { rules: [ { test: /.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /.css$/, use: ['style-loader', 'css-loader'] } ]} 从上面的配置代码可以看出,在解析js文件的时候,使用了babel-loader转换成浏览器兼容的代码,然后在加载css文件的时候,使用style-loader和css-loader进行处理。 从loader的配置顺序来看,先解析js文件的babel-loader要放到后面,即在css-loader和style-loader的前面。另外,由于loader的执行顺序是从下到上的,所以css-loader要放在style-loader的前面,才能正确地将css代码转换为JavaScript代码。 五、enforce-loader应用 enforce 可能取值:
- pre 先执行
- post 后执行 如下的 loader,执行顺序为 loader2 -> loader3 -> loader1: pitching loader 通常情况下,一个 loader 默认会导出一个函数,我们在这个函数中对源文件内容进行处理: 上面这个函数的执行顺序就是我们常说的从右向左执行的,实际上,在(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的 pitch 方法。在 pitch 方法中,我们可以修改 request 后面的 元数据(metadata),并且 pitch 方法如果有返回值的话可以忽略后面(右边的) loader 的结果:
/**
* @param {string|Buffer} content 源文件的内容
* @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
* @param {any} [meta] meta 数据,可以是任何内容
*/
module.exports = function webpackLoader(content, map, meta) {
// 你的 webpack loader 代码
};
module.exports.pitch = function (remainingRequest, precedingRequest, meta) {
meta.value = 42;
};
例如对于以下 use 配置:
module.exports = {
// ...
module: {
rules: [
{
// ...
use: ['a-loader', 'b-loader', 'c-loader'],
},
],
},
};
将会发生这些步骤:
|- a-loader pitch
|- b-loader pitch
|- c-loader pitch
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
小结 综合来讲,webpack loader 的执行顺序是从右向左的,但是可以通过 Rule.enforce 来改变 loader 的执行顺序,enforce 为 post 时 loader 会后执行,enforce 为 pre 时 loader 会优先执行。另外还可以通过 inline-loader 的方式使用 loader,其执行时机在正常 loader 之后,但是在 postLoader 之前。所以 loader 的执行顺序为:pre loader -> normal loader -> inline loader -> post loader,同优先级情况下从右向左执行。 另外 loader 本质上是通过导出了一个函数,这个函数中对源文件代码进行处理,这也是我们通常说的 loader。但是 loader 还可以导出一个 pitch 方法,它可以修改 request 后面的元数据(metadata),并且 pitch 方法如果有返回值的话可以忽略上一个(右边的) loader 的结果。 pitching loader 的执行顺序与上面说的执行顺序完全相反。
补充
补充:
- 关键的 loader 分类:
pre(如eslint-loader在普通 loader 之前执行)、post(如css-minimizer在普通 loader 之后执行)、normal(默认)、inline(!loader!file语法)。pitch阶段:每个 loader 除了默认导出的"普通函数"外,还可以导出pitch方法。loader 链先从左到右依次调用 pitch,若 pitch 返回值非空则"熔断"右边的 loader 直接走 normal 阶段。常用于style-loader/css-loader/less-loader这种链式场景。- 同步 vs 异步 loader:同步 loader 直接
return source;异步 loader 调用this.async()获取 callback,或返回 Promise(webpack 5)。- 编写 loader 的最佳实践:单一职责(只做一件事)、保持 无状态(不存模块级缓存)、接收 / 返回都是 UTF-8 字符串、不要写箭头函数(要拿
this上下文)。
来源整理自:我的有道云笔记



