webpack 之 loader 学习

开篇

loader不难,loader不难,loader不难。默念三遍,而后开始。javascript

loader 简介

loader 这个东西配置 webpack 的时候常常用到,刚开始会让人以为有一种神秘感。css

看了文档以后才发现,loader 只是一个导出为函数的 JavaScript 模块。对,不要怕它,它只是个函数模块。java

webpack内部用了loader-runner 这个开源库,在webpack项目下lib\NormalModule.js中的doBuild方法中调用。loader-runner具体的内部逻辑还不是很清晰,这里先不说了。webpack

用法

咱们常常用 loader 来对模块的源代码进行转换。例如ES6+ES5,sasscss等等。git

loader 的常见用法以下:github

rules: [{
    test: 'xxx',
    use: ['a-loader', 'b-loader', 'c-loader']
}]

rules: [{
    test: 'xxx',
    use: 'xx-loader'
}]

rules: [{
    test: 'xxx',
    user: [
        { loader: 'a-loader' },
        {
            loader: 'b-loader',
            options: {}
        }
    ]
}]
复制代码

loader的运行顺序是从右到左,后面的先执行,是反着来的。web

下一个执行的loader会接收上一个执行的loader后的返回值做为参数继续处理。相似gulp中的pipe管道流npm

到这里应该能理解为何css相关的loader,都是style-loader放在最前面吧。gulp

loader 前置函数

每一个 loader 都支持加一个 Pitch 函数,我这里把它理解成前置函数。api

loader自己是按相反顺序执行,但在(从右到左)执行 loader 以前,会先从左到右调用 loader 上的 pitch 方法。

能够参考官网给出的例子:

若是是这样的配置的loader

rules: [{
    test: 'xxx',
    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
复制代码

注意:若是某个loader 的 pitch 方法有返回值,那么webpack会忽略 当前loader 和 剩下的loader,直接执行以前已经调用过 pitch 方法的loader

关于 pitch 的详细解析,能够参考webpack中文文档;

写个简单的loader

咱们已经知道了 loader 只是一个 javascript函数模块而已,并且有固定的参数格式(官网有介绍):

module.exports = function( content, // 资源文件的内容,多是 String 或 Buffer map, // sourceMap,能够不写 meta // 能够是任何东西(例如元数据),能够不写 ){
        // do something
    }
复制代码

接下来咱们就能够本身写个简单的loader玩一下,

这里我写一个my-img-loader,用于将图片转为base64编码。

先安装mime工具包,用来获取资源类型

npm i -D mime
复制代码

而后在项目根目录下新建一个 loaders 文件夹,而后新建一个 my-img-loader.js,编写代码:

// 用来获取资源的类型
const mime = require('mime');

module.exports = function (content, map, meta) {

    // 将资源内容转换为 Buffer
    if (typeof content === 'string') {
        content = Buffer.from(content);
    }

    // this.resourcePath 表示当前资源的绝对路径
    let mimetype = mime.getType(this.resourcePath);

    // 生成 base64 码
    let base64 = `data:${mimetype || ''};base64,${content.toString('base64')}`;

    // 若是是处理顺序排在最后一个的 loader,那么它的返回值将最终交给 webpack 的 require
    // 也就是说这里要返回一串 CommonJs 规范的 JS 代码
    // 由于我这里只用到一个loader,因此须要这样设置,否则的话,css里面会出现 background: url([object Ojbect])
    // 若是不是最后一个 loader,就不须要 `module.exports` 了
    return `module.exports = ${JSON.stringify(base64)}`;
}

// 设置用 Buffer 格式 来传递处理结果,也就是二进制数据
// 我不清楚这里为何要用 Buffer 格式来传递,不设置的话,显示不了base64图片
// 有知道的读者请告诉下我
module.exports.raw = true;
复制代码

而后设置 webpack.config.jsrules

{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,                
    exclude: [],
    use: ['./loaders/my-img-loader']
    // use: {
    // loader: 'url-loader', 
    // options: {
    // limit: 10000,
    // name: 'img/[name].[hash:7].[ext]'
    // }
    // }
}
复制代码

为了方便起见,读者能够下载我以前提交的spa-webpack-demo来更改。

亦或者,直接把代码复制到已有的项目尝试。

而后 npm run dev 看效果。

代码结构以下:

若是想要给 loader 添加 options 参数,能够用 loader-utils工具库来处理。

最后说下 loader 的类型
同步 loader
module.exports = function(content, map, meta) {
    // 作一些同步的操做
    let result = someSyncOperation(content);
    
    // 将资源返回
    return result;
};
复制代码
异步 loader

异步loader须要先调用this.async(),执行这个方法后,loader-runner内部会将loader识别为异步的,并返回一个callback

module.exports = function(content, map, meta) {
    // 执行 this.async(),告诉 webpack 这是个异步的 loader
    let callback = this.async();
    // 作一些异步的操做
    someAsyncOperation(content, function(err, result) {
        if (err) return callback(err);
        
        // 告诉 webpack,loader执行完毕,并把 result 传入,供下一个loader 使用
        callback(null, result, map, meta);
    });
};
复制代码

写在最后

但愿本文能对读者有帮助。

若是有错误的地方,还请指出。

谢谢阅读。

参考

webpack document

webpack 中文文档

url-loader

手把手教你撸一个 Webpack Loader

webpack loader机制源码解析

相关文章
相关标签/搜索