美文网首页
编写一个webpack的loader(1)

编写一个webpack的loader(1)

作者: laibao | 来源:发表于2019-06-04 20:38 被阅读0次

    开始之前,先介绍一下loader,下面是webpack中文网loader的描述

    所谓 loader 只是一个导出为函数的 JavaScript 模块。loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件(resource file)传入进去。函数的 this 上下文将由 webpack 填充,并且 loader runner 具有一些有用方法,可以使 loader 改变为异步调用方式,或者获取 query 参数。

    loader的参数以及返回值(同样摘抄自webpack中文网)

    第一个 loader 的传入参数只有一个:资源文件(resource file)的内容。compiler 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String 或者 Buffer(被转换为一个 string) ,代表了模块的 JavaScript 源码。另外还可以传递一个可选的 SourceMap 结果(格式为 JSON 对象)。

    loader在webpack中执行的时机

    本图片源于网络

    loader执行时机.png

    loader的分类

    loader分为同步loader异步loaderloader本质是一个函数。所以这两种loader的区别,可以理解为只是返回值不同(只是为了方便理解的类比)。实际操作并不是简单的两种返回值。

    同步loader

    同步loader比较简单,无论是 return 还是 调用this.callback 都可以同步地返回转换后的 content 内容。

    1. 使用return
      这种比较直观,就一个普通函数,执行完成返回一个值。例如:
    function loader(source) {
      // do anything
      return source;
    }
    
    1. 使用this.callback
      this.callback的函数参数类型是
    // 后两个参数是非必传的
    this.callback(
      err: Error | null,
      content: string | Buffer,
      sourceMap?: SourceMap,
      meta?: any
    );
    

    这种比return方式更灵活,可以返回多个值。例如:

    function loader(source) {
      // do anything
       this.callback(null, source, map, meta);
    }
    

    注意: 函数中调用callback后,return的返回值将被忽略。

    异步loader

    异步loader只有一种处理方式。对于异步 loader,使用 this.async 来获取 callback 函数。这里的callback函数,和同步loader里面的参数是一样的。

    function loader (source) {
        console.log('--- loader begin ----');
        console.log(source);
        console.log('--- loader end ----\n');
        const callback = this.async();
        // do anything
        callback(null, source);
    }
    
    module.exports = loader;
    

    注意几种特殊情况:

    1. 函数中调用this.async后,return的返回值将被忽略。
    2. 函数中调用this.async后,再调用this.callback,则会将当前loader当做同步loader来处理。
    3. 函数中调用this.async后,再调用this.callback且立即调用this.async返回的callback。程序会报错。

    至此,我们就能开始着手写一个简单的loader了。下面便开始,着手写几个loader

    banner-loader

    这个loader的功能是给每一个文件头部,添加一行段注释,标明作者。例如:

    /**
    ** @author laibao101
    ** @time 2019-06-04 19:57:20
    **/
    // source code
    

    功能分析:这个loader,并没有任何的异步操作,所以我们使用同步loader即可。注释方面,暂且先写死,通过参数配置的,后续再做。

    function loader (source) {
        const time = new Date().toLocaleString()
        return `
            /**
            ** @author laibao101
            ** @time ${time}
            **/
            ${source}
        `
    }
    

    使用模板字符串,将注释代码写进去,返回即可。这样一个简单的loader就完成了。

    异步的banner-loader

    这个loader是为了写一个异步loader而写的。功能是从一个文件中读取模板,给文件添加banner

    // banner-loader
    const path = require('path');
    const fs = require('fs');
    const baseUrl = process.cwd();
    const configTextPath = "bannerConfig.txt";
    // 拼接完整的配置文件路径
    const fullConfigPath = path.resolve(baseUrl, configTextPath);
    // 根据模板中的占位符,来获取数据
    const configItemMap = {
        author: "laibao101",
        time: new Date().toLocaleString()
    };
    // 匹配占位符
    const reg = /{(\w+)}/gi;
    
    function loader (source) {
        const callback = this.async();
        fs.readFile(fullConfigPath, (err, data) => {
            if (err) {
                // 如果读取文件失败,返回错误
                callback(err);
            }
            // 获取文件内容
            const template = data.toString();
            // 根据模板,修改占位符数据,完成banner
            const banner = template.replace(reg, (match, key) => {
                return configItemMap[key] || key;
            });
            // 拼接返回值
            const ret = `
                ${banner}
                ${source}
            `
            callback(null, ret);
        });
    }
    
    module.exports = loader;
    
    
    // bannerConfig.txt
    
    /**
    ** @author {author}
    ** @time {time}
    **/
    

    可配置参数的同步banner-loader

    前面写到的banner-loader,参数都是loader内部定的。如果需要从webpack.config.js中获取参数,则可以使用webpack提供的this.query参数来获取loader配置中的options参数。这在loader中使用的很频繁。

    例如:
    babel-loader

    module: {
      rules: [
        {
          test: /\.m?js$/,
          exclude: /(node_modules|bower_components)/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        }
      ]
    }
    
    

    url-loader

    module.exports = {
      module: {
        rules: [
          {
            test: /\.(png|jpg|gif)$/i,
            use: [
              {
                loader: 'url-loader',
                options: {
                  limit: 8192
                }
              }
            ]
          }
        ]
      }
    }
    

    所以,我们将上面的banner-loader,修改一下,将参数改为从loader配置中获取

    // banner-loader
    function loader (source) {
        const time = new Date().toLocaleTimeString();
        const { author = '' } = this.query || {}
        return `
            /**
            ** @author ${author}
            ** @time ${time}
            **/
            ${source}
        `
    }
    
    module.exports = loader;
    
    
    // webpack.config.js
    {
      ...
      options: {
        author: "laibao101"
      }
    }
    

    这样,我们就可以实现参数从配置文件中获取的loader了。

    完整代码

    完整代码

    下一篇

    介绍 raw-loaderpitch-loader。并各自编写一个demo

    相关文章

      网友评论

          本文标题:编写一个webpack的loader(1)

          本文链接:https://www.haomeiwen.com/subject/ederxctx.html