webpack进阶

loader

loader 用于转换某些类型的模块
loader 用于对某些导入的资源进行特定处理 例如 css image...

原理

loader本质上是一个函数

手写一个 babelLoader

webpack.config.js

const path = require('path');

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babelLoader', // 自定义的 babelLoader
        options: {
          presets: [
            '@babel/preset-env'
          ]
        }
      }
    ]
  },
  // 配置 loader 路径解析规则  babelLoader 先去  node_modules 中找找不到再去 "./loaders" 下找
  resolveLoader: {
    modules: [
      'node_modules',
      path.resolve(__dirname, 'loaders')
    ]
  }
}

babelLoader.js

const { getOptions } = require('loader-utils');
const { validate } = require('schema-utils');
const babel = require('@babel/core');
const util = require('util');

const babelSchema = require('./babelSchema.json');

// babel.transform用来编译代码的方法 是一个普通异步方法
// util.promisify将普通异步方法转化成基于promise的异步方法
const transform = util.promisify(babel.transform);

module.exports = function (content, map, meta) {
  
  // 获取loader的options配置
  const options = getOptions(this) || {};

  // 校验babel的options的配置
  validate(babelSchema, options, {
    name: 'Babel Loader' // 报错的提示名称
  });

  // 创建异步 callback 将内容传给下一个 loader
  const callback = this.async();

  // 使用babel编译代码
  transform(content, options)
    .then(({code, map}) => callback(null, code, map, meta))
    .catch((e) => callback(e))

}

babelSchema.json 验证规则

{
  // 需要验证的参数的类型
  "type": "object",
  "properties": {
    // 参数名称
    "presets": {
      // 参数类型
      "type": "array" 
    }
  },
  // 是否允许添加新的属性
  "addtionalProperties": true
}

Plugins

Plugin 插件可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

原理

Plugin 实例 插件是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在 整个 编译生命周期都可以访问 compiler 对象。

ConsoleLogOnBuildWebpackPlugin.js

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    compiler.hooks.run.tap(pluginName, (compilation) => {
      console.log('webpack 构建正在启动!');
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

compiler 钩子

Compiler 可以看做是 运行时的webpack对象
Compiler.hooks 上有 webpack 运行时生命周期的各个 hook, 这些 hook(extends)自 Tapable 类

Tapable 使用

// tabpable 提供一系列创建钩子和调用钩子的方法  (发布者订阅者模式)
const tapable = require('tapable');

const {
  // 同步执行 串行
  SyncHook,
  // 同步执行 串行 遇到返回值 退出执行
  SyncBailHook,
  // 异步并行 并行行1个钩子出错 不影响其他钩子触发
  AsyncParallelHook,
  // 异步串行 串行1个钩子出错 后面的钩子就不再触发
  AsyncSeriesHook,
} = tapable;

class HookTest {
  constructor() {
    this.hooks = {
      // hooks 的容器
      go: new SyncHook(['arg1', 'arg2']), // 参数数组的长度 是 回调函数的所能接受参数的长度
      asyncGo: new AsyncParallelHook(['arg1']),
    };
  }

  //  向容器里添加对应的钩子函数 (添加订阅者)
  tap() {
    // tap  在 go (SyncHook)容器(相当于一个map) 添加键值--- key -> synchook1, value -> 回调函数
    this.hooks.go.tap('synchook1', (...args) => {
      console.log('synchook1 触发了 args:', args);
      return 123;
    });
    this.hooks.go.tap('synchook2', (...args) => {
      console.log('synchook2 触发了 args:', args);
    });
    // tapAsync 添加一个异步执行的函数  函数内部接受一个 callback , callback 调用代表 函数执行完毕
    this.hooks.asyncGo.tapAsync('asynchook1', (...args) => {
      let callback = args[args.length - 1];
      setTimeout(() => {
        console.log('asynchook1 回调异步 触发了 arg1:', args[0]);
        // 异步函数执行完毕 callback 第一参数 是错误
        callback(null, 'asynchook1');
      }, 3000);
    });
    // tapPromise 添加一个异步执行的函数 函数返回一个 promise
    this.hooks.asyncGo.tapPromise('asynchook2', (arg1) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log('asynchook2  Promise 异步 触发了 arg1:', arg1);
          // 异步函数执行完毕
          resolve('asynchook2');
        }, 1000);
      });
    });
  }
  //   触发所有钩子中的函数 (发布订阅)
  call() {
    this.hooks.go.call('这是同步钩子调用的参数');
    // 最后一个传入的参数 就是回调函数 callback
    // 当所有 钩子都执行完了,(或有一个出错误时) 就会触发这个 函数
    this.hooks.asyncGo.callAsync('这是异步钩子调用的参数', function (err, res) {
      // 代表所有leave容器中的函数触发完了,才触发
      console.log('异步钩子 end~~~');
    });
  }
}

const test = new HookTest();

test.tap();
test.call();

Compilation

Compilation
compilation 由 Compiler 创建, 包含所有模块和对应依赖 , 同样也有各个生命周期的 hook 同 compiler.hooks

手写一个 CopyWebpackPlugin

CopyWebpackPlugin.js

const { validate } = require('schema-utils');
const schema = require('./schema').CopyWebpackPlugin;
const globby = require('globby');
const path = require('path');
const { readFile } = require('fs').promises;
const { RawSource } = require('webpack').sources;

class CopyWebpackPlugin {
  constructor(options) {
    this.options = options;
    validate(schema, options);
    this.compiler = null;
    this.compilation = null;
  }

  apply(compiler) {
    // compiler  初始化 compilation
    this.compiler = compiler;
    compiler.hooks.thisCompilation.tap('CopyWebpackPlugin', (compilation) => {
      this.compilation = compilation;
      // 为 compilation 创建额外 asset
      compilation.hooks.additionalAssets.tapPromise(
        'CopyWebpackPlugin',
        this.hanleCopy.bind(this),
      );
    });
  }
  async hanleCopy() {
    let { from, ignore, to = '.' } = this.options;
    const context = this.compiler.options.context; // process.cwd()
    // 判断是 from 否为绝对路径
    from = path.isAbsolute(from) ? from : path.resolve(process.cwd(), from);
    // window 下有路径问题
    from = from.replace(/\\/g, '/');
    // 1. 获取 from 文件列表
    let files = await globby(from, {
      ignore,
    });
    for (const file of files) {
      // 2. 读取文件
      let filename = path.join(to, path.basename(file));
      let buf = await readFile(file);
      // 3. 写入文件
      let source = new RawSource(buf);
      this.compilation.emitAsset(filename, source);
    }
  }
}

module.exports = CopyWebpackPlugin;

webpack.config.js

const CopyWebpackPlugin = require('../plugins/CopyWebpackPlugin');
module.exports = {
  mode: 'development', // production
  plugins: [
    new CopyWebpackPlugin({
      to: '.',
      from: 'public',
      ignore: ['*/index.html'],
    }),
  ],
};

schema

module.exports = {
  CopyWebpackPlugin: {
    type: 'object', // options 类型
    properties: {
      to: {
        type: 'string',
      },
      from: {
        type: 'string',
      },
      ignore: {
        type: 'array',
      },
    },
    additionalProperties: false, // 可以添加更多key
  },
};

手写简易版 webpack

参考

posted @ 2023-01-10 18:44  __Bowen  阅读(39)  评论(0编辑  收藏  举报