Skip to content
TOC

loader

loader的定义:loader 只是一个导出为函数的JavaScript 模块

下面是一个最简单的loader:

js
module.exports = function(source) {
	return source;
};

loader-runner

使用loader-runner来测试loader,避免每次需安装webpack进行测试。

js
const { runLoaders } = require("loader-runner");
const path = require("path");
const fs = require("fs");

console.log('==>',path.join(__dirname, "./demo.txt"))

runLoaders(
  {
    resource: path.join(__dirname, "./demo.txt"), 
    // 字符串:资源的绝对路径(可以选择包含查询字符串)
    loaders: [
      path.join(__dirname, "./raw-loader.js")
    ],
    // String[]:loader的绝对路径(可选地包括查询字符串)
    // {loader, options}[]: 带有选项对象的加载器的绝对路径
    context: { minimize: true },
    // 用作基本上下文的附加加载器上下文
    readResource: fs.readFile.bind(fs),
    // 可选:读取资源的函数
    // 仅当未提供 'processResource' 时使用
    // 必须有签名 function(path, function(err, buffer))
    // 默认使用 fs.readFile
  },
  function (err, result) {
    if (err) {
      console.log(err);
    } else {
      console.log(result);
    }
  }
);

loader其他配置

1、传参和获取

loader通过options进行传参

js
//...
module.exports = {
  module: {
    rules: [
      {
		test: /.(png|jpg|gif|jpeg)$/,
		use: [
			{
				loader: 'url-loader',
				options: {
					limit: 10240
				}
			}
		]
	},
    ],
  },
};

通过 loader-utils 的 getOptions 方法获取参数:

js
const loaderUtils = require("loader-utils");

module.exports = function(content) {
	const options = loaderUtils.getOptions(this);
};

2、loader异常处理

  • 通过throw抛出异常
  • 通过 this.callback 传递错误
js
this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);

通过callback而不是return 的方式,可以传多个内容:

js
module.exports = function(source) {
  // return source
  this.callback(null, source, 11, 22, 33)
}

输出结果为:

{
  result: [ '你好', 11, 22, 33 ],
  resourceBuffer: <Buffer e4 bd a0 e5 a5 bd>,
  cacheable: true,
  fileDependencies: [
    '/Users/daotin/Desktop/github/fe-series-code/geektime-webpack-course/demo/loader-runner/demo.txt'
  ],
  contextDependencies: [],
  missingDependencies: []
}

3、loader异步处理

通过 this.async 来返回一个异步函数callback,第一个参数是 Error,第二个参数是处理的结果。

js
module.exports = function(source) {
	const callback = this.async();
	callback(null, source);
};

4、loader缓存

js
this.cacheable(false) //关掉缓存

5、loader文件输出

使用this.emitFile 进行文件写入(仅在webpack中有效,单独使用loader-runner无效

js
const loaderUtils = require("loader-utils");
const path = require('path')

module.exports = function(source) {
	const url = path.join(__dirname, 'dist/index.js');
	
	this.emitFile(url, source);
	
	this.callback(null, source)
}

plugin

plugin的编写没有类似loader-runner的独立环境,必须在webpack的环境中编写。

插件的基本结构

  • 插件名称
  • apply方法(参数为compiler)(可以查看自定义插件
  • 插件监听的hooks
  • 插件处理流程

plugin其他配置

1、插件中如何获取传递的参数?

通过插件的构造函数进行获取。

js
module.exports = class MyPlugin {
	constructor(options) {
		this.options = options;
	}
	apply() {
		console.log("apply", this.options);
	}
};

2、插件的错误处理

参数校验阶段可以直接 throw 的方式抛出

js
throw new Error("Error")

如果是在hooks处理阶段,通过 compilation 对象的 warnings和 errors 数组接收。

js
compilation.warnings.push("warning");
compilation.errors.push("error");

3、文件写入磁盘

将内容设置到compilation.assets对象上即可。

示例:编写一个压缩构建资源为zip包的插件

要求:

  • 生成的 zip 包文件名称可以通过插件传入
  • 需要使用 compiler 对象上的特定的 hooks进行资源生成,而不能使用fs

分析:

  • 资源的压缩使用 jszip (https://www.npmjs.com/package/jszip)
  • emit 生成文件阶段,读取的是 compilation.assets 对象的值,讲zip 资源包设置到 compilation.assets 对象上即可
  • 设置到compilation.assets 对象上时候,需要通过webpack-sources库中的RawSource类进行封装,因为Webpack 期望的是一个符合特定接口的对象,该对象需要包含至少 source()size() 方法。RawSource 类正是为了这个目的而设计的,它提供了这些方法,使得 Webpack 能够正确处理和输出文件内容

具体zip-plugin.js插件代码:

js
const JSZip = require("jszip");
const { RawSource } = require("webpack-sources");
const zip = new JSZip();

module.exports = class ZipPlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    console.log("zip plugin执行");

    compiler.hooks.emit.tapAsync("myZipPlugin", (compilation, callback) => {
      // console.log(compilation.options.output.path); // dist绝对路径

      // 创建一个目录
      const zip = new JSZip();
      const folder = zip.folder(this.options.filename || "filename");

      // 目录写入文件
      for (let filename in compilation.assets) {
        const source = compilation.assets[filename].source(); // 打包后的文件内容
        folder.file(filename, source);
      }

      // 生成压缩文件
      zip.generateAsync({ type: "nodebuffer" }).then((content) => {
        // console.log(new RawSource(content));
        const filename = this.options.filename + ".zip";
        compilation.assets[filename] = new RawSource(content); // 通过RawSource包裹一层

        callback();
      });
    });
  }
};

webpack配置文件中使用:

js
'use strict';

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ZipPlugin = require('./plugins/zip-plugin')

module.exports = {
  entry: './src/index.js',
  mode: 'production',
  devtool: false,
  output: {
      path: path.join(__dirname, 'dist'),
      filename: 'bundle.js'
  },
  plugins: [
    new CleanWebpackPlugin(),
    new ZipPlugin({
      filename: 'daotin'
    })
  ]
};

Released under the CC BY-NC-ND 3.0