手写webpack前奏:拆解loader

395 阅读1分钟

解析loader:

webpack.config.js:

module.exports = {
  module:{
    rules:[
      {
        test: /\.js$/
        use:[
        	'loader1',
        	'laoder2',
        	{
        		loader: 'loader3',
        		options: {}
      		}
        ]
      }
    ]
  }
}

loader本质是一个函数:

module.exports = function (content, map, data){
  return content
}
module.exports.pitch = function(prevPath, nextPath){
  console.log(prevPath, nextPath)
}

众所周知,loader执行顺序是从后向前,其实它还可以挂载一个pitch方法,执行顺序是从前向后,接收的参数是执行本loader之前和之后的参数的绝对路径。

同步loader写法:2种

module.exports = function (content){
  // 1.return
  return content
  // 2.this.callback
  this.callback(null, content)
}

异步loader写法:1种

module.exports = function(content){
  const callback = this.async()
  setTimeout(()=>{
    callback(null, content)
  },1000)
}

推荐使用异步loader,可以处理一些事情

获取传给loader的options:loader-utils

验证传给loader的options:schema-utils,校验规则是配置schema.json

Loader3.js:

const {getOptions} = require('loader-utils')
const {validate} = require('schema-utils')
const schema = require('./schema')
module.exports = function(content){
  //	获取传入的optionss
  const options = getOptions(this)
  //	规则,参数,规则报错时的提示
  validate(schema, options, {
    name:'loader3'
  })
}

schema.json

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "名称"
    }
  }
  //	配置时是否可以添加除了name外的其他属性
  "additionalProperties": false
}

loader实战:

先安装:npm i loader-utils schema-utils @babel/core @babel/preset-env -D

// 获取传入的option
const { getOptions } = require("loader-utils");
// 校验options
const { validate } = require("schema-utils");
// 本loader核心:编译js
const babel = require("@babel/core");
// nodejs内置工具函数,使用其promisify将普通异步函数转为promise方式
const util = require("util");
// 校验规则
const schema = require("./babelSchema");
// babel的转换规则
const transform = util.promisify(babel.transform);
module.exports = function (content) {
  // 获取options
  const options = getOptions(this);
  validate(schema, options, {
    name: "loader4",
  });
  const callback = this.async();
  transform(content, options)
    .then(({ code, map }) => {
      callback(null, code, map);
    })
    // 第一个参数有就不继续执行后面的参数
    .catch((code) => callback(code));
};

config配置:

···
rules: [
  {
    test: /\.js$/,
    use: [
      {
        loader: "loader4",
        options: {
          presets: ["@babel/preset-env"],
        },
      },
    ],
  },
],
···