编写 webpack loader
webpack loader 是什么
loader就是一个函数,用于编译源代码内部不同类型的模块,同时输出编译后的代码,用来拓展 webpack 的解析能力。
为什么需要 loader
本身 webpack 只能处理.js,·.json文件,对于其他类型的模块代码,webpack 需要借助loader来处理源码,可以把loader直接理解为编译器,因为它主要负责将 webpack 输入的模块代码转换成另一种形式的代码输出。
loader 运行模式
loader接收以下三个参数:
- content:源代码
- map:可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
- meta:meta 数据,可以是任何内容
loader可以直接返回处理后的代码,表示该loader是以同步模式运行的
module.exports = function(content, map, meta) {
  let result = '';
  // 一些处理
  return result;
};
当loader内部具有异步处理时,需要对结果使用callback函数进行包装,callback依次接收四个参数:
- error:第一个参数是错误信息- Error或者- null
- result:第二个参数则是处理过后的代码
- source map:第三个参数是 source map
- meta:第四个参数,会被 webpack 忽略,可以是任何类型的参数
module.exports = function(content, map, meta) {
  // 获取 callback 函数
  var callback = this.async();
  someAsyncOperation(content, function(err, result) {
    if (err) {
      return callback(err);
    }
    callback(null, result, map, meta);
  });
};
loader 的链式调用
module.rules.use这个数组传递的多个loader按照数组索引的顺序从后往前执行,第一个执行的loader会获取模块源代码作为参数,然后其执行结果会传递给下一个执行的loader,最后一个loader执行期望返回JS代码和sourcemap;比较常见的就是less的处理流程
module: {
  rules: [
   	test: /\.less$/,
    use: [
    	"style-loader",
    	"css-loader",
    	"less-loader"
  	]
  ]
}
辅助工具
loader-utils
常用于获取在webpack配置项中传递给loader的参数配置,返回的参数配置项是一个只读的对象
// 在loader内部使用
const loaderUtils = require('loader-utils');
const options = loaderUtils.getOptions(this);
// 通过queryString指定的配置项,例如loader?some¶ms
const params = loaderUtils.parseQuery(this.resourceQuery);
schema-utils
schema-utils可以对loader获取的配置参数进行校验,从schema-utils中获取的validate方法接收三个参数:
- schema:用于定义- options配置项的类型和校验不通过时的描述,也就是校验规则
- options:即外部传递进来的配置项
- configuration:最后一个参数用于补充对于- schema的描述信息
import { getOptions } from 'loader-utils';
import { validate } from 'schema-utils';
// 校验规则
const schema = {
  type: 'object',
  properties: {
    name: {
      type: 'string',
    },
    query: {
      type: 'number',
    },
  },
  additionalProperties: false,
};
function loader(src, map) {
  const options = getOptions(this);
  validate(schema, options, {
    name: 'Loader Name',
    baseDataPath: 'options',
  });
  // Code...
  return `function() {xxx}`;
}
export default loader;
编写 loader 的注意事项
- 功能单一原则:一个loader应该只处理一种类型的源代码
- 禁止写绝对路径