深入了解Webpack---编写loader

如何编写一个 Loader

官方说明文档:

loader 只是一个导出为函数的 JavaScript 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文访问。loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件(resource file)传入进去。函数的 this 上下文将由 webpack 填充。

注意:loader就是一个函数,不能写成箭头函数module.exports = () => {},而用声明式module.exports = function(){},否则this指向会有问题

简单案例:编写loader实现将world替换为camille:

//1、index.js: 
console.log('hello world');

//2、webpack.common.js:
module: {
      rules: [
          {
              test: /\.js$/,
              //include: path.resolve(__dirname, '../src'),
              loader: path.resolve(__dirname, '../loaders/replaceLoader.js')
          }
     ]
}

//3、replaceLoader.js
module.exports = function(source) {  //source引入文件的源代码(内容)
    console.log('-------', source);
    return source.replace('world', 'camille');   //return source;
}

输出如下:

1、loader添加配置

获取配置有两种方式:

1)this.query:loader使用时配置options,处理的loader通过this.query获取这个options对象(注意上面loader定义要使用函数式)。

  1. 如果这个 loader 配置了 options 对象的话,this.query 就指向这个 option 对象。
  2. 如果 loader 中没有 options,而是以 query 字符串作为参数调用时,this.query 就是一个以 ? 开头的字符串。

2)loader-utils:除了this.query获取options对象,官方还推荐使用 loader-utils 中提供的 getOptions 方法 来提取给定 loader 的 options。

//1、webpack.commmon.js:
{
    test: /\.js$/,
    loader: path.resolve(__dirname, '../loaders/replaceLoader.js'),
    options: {
        name: "Camille"
    }
}

//2、replaceLoader.js
//1)this.query
module.exports = function(source) {
    console.log('-------', this.query);
    return source.replace('world', this.query.name);
}

//2)、loader-utils
//npm install loader-utils --save-dev
const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    console.log('-------', options);

    return source.replace('world', options.name);
}

输出如下:

2、this.callback:一个可以同步或者异步调用的可以返回多个结果的函数。

return只能返回一个资源,有的时候可能会把sourcemap或其他信息一并返回。即单个处理结果,可以在同步模式中直接return返回。如果有多个处理结果,则必须调用 this.callback()。

this.callback(
  err: Error | null,   //第一个参数必须是 Error 或者 null
  content: string | Buffer,  //第二个参数是一个 string 或者 Buffer。
  sourceMap?: SourceMap,  //可选的:第三个参数必须是一个可以被这个模块解析的 source map
  meta?: any  //可选的:第四个选项,会被 webpack 忽略,可以是任何东西(例如一些元数据)
)

module.exports = function(content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);
  return; // 当调用 callback() 时总是返回 undefined
};

修改上面的案例:

//replaceLoader.js:
const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    const result = source.replace('world', options.name);
    
    console.log("-----result-----", result);

    this.callback(null, result);  // return source.replace('world', options.name);
}

3、this.async

修改上面的案例并改名为replaceLoaderAsync.js:

//replaceLoaderAsync.js:
const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    setTimeout(() => {
        const result = source.replace('dell', options.name);
        this.callback(null, result);  //return result;
    }, 1000)
}

运行结果直接报错:

解决:在异步模式中,必须调用 this.async(),来指示 loader runner 等待异步结果,它会返回 this.callback() 回调函数--使用 this.async 来获取 callback 函数。

this.async: 告诉 loader-runner 这个 loader 将会异步地回调。返回 this.callback。

const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    const callback = this.async();
    setTimeout(() => {
        const result = source.replace('world', options.name);
        console.log("-----result-----", result);

        callback(null, result);  
    }, 1000)
}

上面只是1s,打包时间如下:

修改上面异步的时间为5000,打包所耗用时间如下:

几乎是5倍,即使用this.async()会指示 loader runner 等待异步结果。

4、多个loader顺序问题:

同时运用以上两个loader:同步、异步loader

use: [
          {
            loader: path.resolve(__dirname, "../loaders/replaceLoader.js"),
            options: {
              name: "Camille"
            }
          },
          {
            loader: path.resolve(__dirname, "../loaders/replaceLoaderAsync.js"),
            options: {
              name: "Vina"
            }
          }
]

看最终输出如下:异步loader处理完,将处理后的结果交给了同步的loader,同步loader接收source是上一个loader处理后的而不是最初的

优化:以上loader引入路径多处重复,匹配(test)多个 loaders,可以使用 resolveLoader.modules 配置,webpack 将会从这些目录中搜索这些 loaders。

module.exports = {
    resolveLoader: {
      modules: ["node_modules", "./loaders"]  //注意:优先级从前到后
    },
      module: {
        rules: [
          {
            test: /\.js$/,
            include: path.resolve(__dirname, "../src"),
            use: [
              {
                loader: "replaceLoader",
                options: {
                  name: "camille"
                }
              },
              {
                loader: "replaceLoaderAsync",
                options: {
                  name: "vina"
                }
              }
            ]
          }
        ]
      }
}

5、自定义loader使用场景:

1、不需要修改业务代码,就可以添加try catch错误捕获,如下loader处理的业务代码中遇到function就给添加try catch包裹

module.exports = function () {
  try {
    function func() {
      /**.... */
    }
  } catch (e) {}
};

2、中英文切换,如下获取全局变量来获取语言,从而用相应内容替换占位符:

猜你喜欢

转载自blog.csdn.net/CamilleZJ/article/details/117116767