玩转webpack之loader开发

2019-12-04 16:22:55 浏览数 (1)

本文作者:IMWeb hx856082 原文出处:IMWeb社区 未经同意,禁止转载

webpack提倡一切皆模块,所有类型的文件都可以经过文件加载器处理变成我们可加载的模块,那么这个文件加载器便是loader。

那么我们如何开发一个webpack loader呢,让我们一起探索探索吧~

一、loader执行顺序 在开发loader之前,我们先了解一下webpack loader的执行顺序。

webpack是支持loader的链式调用的,即一个文件可以经多个loader处理。当一个文件使用多个loader处理时,他的处理顺序是倒序,即传入loader数组的从右到左执行。

例如,对于scss文件,我们的配置如下,那么它的执行顺序是sass-loader -》 css-loader -》 postcss-loader -》style-loader:

module: { rules: [ { test: /.scss|.css/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, }, }, 'postcss-loader', loader: 'sass-loader', ], } } 二、loader开发 那么如何来开发一个loader呢?让我们慢慢来揭开webpack loader 的什么面纱~

loader其实是一个导出为函数的 JavaScript模块,是不是看起来很简单?实际呢,loader开发也很简单。即

test.loader.js内容如下:

module.exports = function(content) { return transform(content); // 对content进行处理并返回给webpack } 1、loader的传入参数 既然我们说了所谓 loader 只是一个导出为函数的 JavaScript模块,那么它的传入是什么呢?

content: string | Buffer, // 文件内容 sourceMap?: SourceMap, // 上一个loader解析完后生成的 source map meta?: any // 会被 webpack 忽略,可以是任何东西(例如一些元数据) 显然loader就是对文件进行处理的,那么这里的content便是文件内容。

默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw,loader 可以接收原始的 Buffer。我们常用的file-loader就设置了raw为true,以告诉webpack传入原始的二进制数据

module.exports.raw = true; 需要注意的是:第一个 loader 的传入参数只有一个:资源文件的内容content。其他都是经过loader处理后可选择传递给下一个loader的。

2、loader处理结果 loader返回的处理结果应该和传入一样是 String 或者 Buffer。 上面有讲到除了content,loader其实还接受两个可选的入参,返回也一样。所以当我们如果是单个处理结果,可以在函数中直接返回。但是如果有多个处理结果,我们则必须通过this.callback()将处理结果传递给下一个loader。

module.exports = function(content) { const newContent = transform(content); // 对content进行处理 this.callback(null,newContent, sourceMaps, meta); return; // 当使用this.callback时函数应该return undefined } 这里的this既不是webpack实例,也不是compiler、compilation、normalModule等这些实例。而是loader-runner构造的loaderContext对象,提供了各种loader API(具体API可见 https://webpack.js.org/api/loaders/ )。

对于meta参数,一般传入抽象语法树(abstract syntax tree - AST),这样可以在多个 loader 之间共享通用的 AST,这样做有助于加速编译时间。

所以总结来说,loader的工作流程是:

最后的 loader 最早调用,将会传入原始资源内容。 第一个 loader 最后调用,期望值是传出 JavaScript和 source map(可选)。 中间的 loader 执行时,会传入前一个 loader 传出的结果。 3、获取用户自定义参数 到这里基本已经清楚了loader的整个工作流程。我们在使用loader时,经常会传入一些自定义的options,那么loader怎么获取这些options呢?

webpack 提供了loader-utils包和schema-utils 包。loader-utils提供了许多有用的工具,但最常用的一种工具是获取传递给 loader 的选项。schema-utils 包配合 loader-utils,用于保证 loader 选项,进行与 JSON Schema 结构一致的校验。

const loaderUtils = 'loader-utils'; module.exports = function(content) { const options = loaderUtils.getOptions(this); // 用户传入的options return transform(content); // 对content进行处理并返回给webpack } 4、控制loader执行 前面讲到过,webpack的loader执行顺序是从后往前。有些时候我们希望选择性的越过后续loader执行,webpack给每个loader提供了pitch方法进行设置。

根据webpack官网给出的案例,对于下面的配置:

module.exports = { //... module: { rules: [ { //... use: [ 'a-loader', 'b-loader', 'c-loader' ] } ] } }; webpack 在实际(从右到左)执行 loader 之前,会先从左到右调用 loader 上的 pitch 方法。所以实际执行顺序如下:

|- 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 pitch方法若有返回值,则会跳过后续的loader。比如上面如果 b-loader 的 pitch 方法有返回值,那么此时loader的执行流程是:

|- a-loader pitch |- b-loader pitch returns a module |- a-loader normal execution 三、举个栗子 接下来我们来开发一个自己的loader

比如现在有个场景,要求我们给所有的apng 请求url加上参数?nowebp=1。loader代码如下:

apng-url-resolve.js

module.exports = function(content) { return content.replace(/.apng(.*.png)?/, '.apng$1?nowebp=1') } webpack loader 配置:

const path = require('path'); modules: { rules: [ { test: /.js$/ use: path.resolve(__dirname, 'build/loaders/apng-url-resolve.js' ) } ] }

0 人点赞