webpack自定义loader
总结
一. webpack源码阅读
二. 自定义Loader
2.1. 认识自定义Loader
2.2. Loader的执行顺序
- pitch
 - normal
 - enforce: pre/post
 
2.3. Loader的同步和异步
- 同步:
- 返回值
 - this.callback()
 
 - 异步:
- this.async()
 
 
2.4. Loader参数获取和校验
- this.getOptions
 - validate
 
2.5. 案例一 - babel-loader
2.6. 案例二 - md-loader
自定义Loader
创建自己的Loader
Loader是用于对模块的源代码进行转换(处理),之前我们已经使用过很多Loader,比如css-loader、style-loader、babel - loader等。
这里我们来学习如何自定义自己的Loader:
- Loader本质上是一个导出为函数的JavaScript模块;
 - loader runner库会调用这个函数,然后将上一个loader产生的结果或者资源文件传入进去;
 
编写一个hy-loader01.js模块这个函数会接收三个参数:
- content:资源文件的内容;
 - map:sourcemap相关的数据;
 - meta:一些元数据;
 

在加载模块时引入loader
- 配置resolveLoader属性;
- resolveLoader是配置loader是支哪个目录查找的,默认是node_modules
 - 我们可以配置成自己的目录,如下
 
 
 resolveLoader: {
    modules: ["node_modules", "./hy-loaders"]
  }
- 注意:传入的路径和context是有关系的
- context配置的是用于从配置中解析入口点(entry point)和loader
 - 默认使用 Node.js 进程的当前工作目录,也就是项目根据目录。
 - 所以这里的
./hy-loaders,就是在项目根目录下的。 
 
loader的执行顺序
- 创建多个Loader使用,它的执行顺序是什么呢?
- 从后向前、从右向左的
 
 
 
pitch-loader
- 定义loader的时候,除了可以返回一个函数,还可以给这个函数对象,添加一个pitch属性,比如:
 
module.exports = function(content, map, meta) {
  console.log("hy_loader01:", content)
  return content
}
module.exports.pitch = function() {
  console.log("loader pitch 01")
}
这里的pitch的执行顺序是和正常loader书写的顺序是一样的。
run-loader在执行的时候,会扫描所有的loader,在扫描的过程中会先执行loader的pitch函数
run-loader先优先执行PitchLoader,在执行PitchLoader时进行loaderIndex++;
run-loader之后会执行NormalLoader,在执行NormalLoader时进行loaderIndex--;
所以loaders的执行顺序是先正常顺序执行每个loader的pitch函数,再反着执行每个loader真正的本体。
修改loader的执行顺序
那么,能不能改变它们的执行顺序呢?
- 我们可以拆分成多个Rule对象,通过enforce来改变它们的顺序;
 
enforce一共有四种方式:
- 默认所有的loader都是normal;
 - 在行内设置的loader是inline(import 'loader1!loader2!./test.js');
 - 也可以通过enforce设置 pre 和 post;
 
在Pitching和Normal它们的执行顺序分别是:
- Pitch-loader
- post, inline, normal, pre;
 
 - Normal-loader
- pre, normal, inline, post;
 
 
- Pitch-loader
 
  module: {
    rules: [
       {
        test: /\.js$/,
        use: "hy_loader02",
        enforce: "post"
      }
    ]
  }
同步和异步的Loader
同步的Loader
什么是同步的Loader呢?
- 默认创建的Loader就是同步的Loader;
 - 这个Loader必须通过 return 或者 this.callback 来返回结果,交给下一个loader来处理;
 - 通常在有错误的情况下,我们会this.callback来传参数错误。
 
this.callback的用法如下:
- 第一个参数必须是 Error 或者 null;
 - 第二个参数是一个 string或者Buffer;
 
异步的Loader
什么是异步的Loader呢?
- 有时候我们使用Loader时会进行一些异步的操作;
 - 我们希望在异步操作完成后,再返回这个loader处理的结果;
 - 这个时候我们就要使用异步的Loader了;
 
loader-runner已经在执行loader时给我们提供了方法,让loader变成一个异步的loader:
- 使用 
this.async(),告诉webpack我这里有个异步操作; 
- 使用 
 
module.exports = function(content) {
  const callback = this.async()
  setTimeout(() => {
    console.log("hy_loader02:", content)
    callback(null, content + "bbbb")
  }, 3000);
}
/** 同步loader */
// module.exports = function(content) {
//   console.log("hy_loader02:", content)
//   return content + "bbbb"
// }
传入和获取参数
方式一: 早期时, 需要单独使用loader-utils(webpack开发)的库来获取参数
npm install loader-utils -D
方式二: 目前, 已经可以直接通过this.getOptions()直接获取到参数
 
module.exports = function(content) {
  // 1.获取使用loader时, 传递进来的参数
  // 方式一: 早期时, 需要单独使用loader-utils(webpack开发)的库来获取参数
  // 方式二: 目前, 已经可以直接通过this.getOptions()直接获取到参数
  const options = this.getOptions()
  console.log(options)
  console.log('hy-loader04:', content)
  return content
}
校验参数
我们可以通过一个webpack官方提供的校验库 schema-utils,安装对应的库:
npm install schema-utils -D
使用json schema来检验数据
//loader04_schema.json
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "请输入名称, 并且是string类型"
    },
    "age": {
      "type": "number",
      "description": "请输入年龄, 并且是number类型"
    }
  }
}
const { validate } = require('schema-utils')
const loader04Schema = require('./schema/loader04_schema.json')
module.exports = function(content) {
  const options = this.getOptions()
  console.log(options)
  // 2.校验参数是否符合规则
  validate(loader04Schema, options)
  console.log('hy-loader04:', content)
  return content
}
babel-loader案例
- 我们知道babel-loader可以帮助我们对JavaScript的代码进行转换,这里我们定义一个自己的babel-loader:
 
 
hymd-loader
- 自定义一个markdown的loader
 
const { marked } = require('marked')
const hljs = require('highlight.js')
module.exports = function(content) {
  // 让marked库解析语法的时候将代码高亮内容标识出来
  marked.setOptions({
    highlight: function(code, lang) {
      return hljs.highlight(lang, code).value
    }
  })
  // 将md语法转化成html元素结构
  const htmlContent = marked(content)
  // console.log(htmlContent)
  // 返回的结果必须是模块化的内容
  const innerContent = "`" + htmlContent + "`"
  const moduleContent = `var code = ${innerContent}; export default code;`
  return moduleContent
}