探索 webpack

672 阅读3分钟

在团队内部分享的 webpack 基本使用和原理,没有了解过 webpack 的同学可以一读

介绍

webpack 是一个现代 JavaScript 应用程序的静态模块打包器
webpack 将模块化开发方式编写的代码,编译为浏览器可识别的代码
当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

核心概念

entry:指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始
output:output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件
loader:webpack 只能处理 JS 文件,loader 将非 JS 文件,编译为 JS 文件供 webpack 使用
plugins:loader 被用于转换某些类型的模块,而 plugins 则可以用于执行范围更广的任务,比如打包优化、压缩

流程概述

Loader

loader 可以理解为 webpack 的编译器,用于对模块的源代码进行转换。它使得 webpack 可以处理一些非 JS 文件,如 png、css、json
实现一个简单的 loader,功能是替换 console.log、去除换行符、在文件结尾处增加一行自定义内容

const loaderUtils = require('loader-utils')
​
/** 过滤console.log和换行符 */
module.exports = function (source) {
  // 获取loader配置项
  const options = loaderUtils.getOptions(this)
  
  console.log('loader配置项:', options)
  
  const result = source
    .replace(/console.log\(.*\);?/g, "")
    .replace(/\n/g, "")
    .concat(`console.log("${options.message || '没有配置项'}");`)
      
    return result
}

Tapable

tapable 和 EventEmitter 一样,是一系列事件的生成和管理工具,典型的发布订阅模式
tapable 实现了 webpack 的事件流机制,将各个插件串联起来
tapable 暴露出很多钩子类,这些类可以用来为插件创建钩子函数

以 SyncHook 为例,用法:

const { SyncHook } = require("tapable")
let queue = new SyncHook(['name'])  //所有的构造函数都接收一个可选的参数,这个参数是一个字符串的数组。// 订阅
queue.tap('1', function (name) {  // tap 的第一个参数用来标识订阅的函数
    console.log(name, name2, 1)
    return '1'
})
queue.tap('2', function (name) {
    console.log(name, 2);
})
queue.tap('3', function (name) {
    console.log(name, 3);
})
​
// 发布
queue.call('webpack')  // 发布的时候触发订阅的函数 同时传入参数// 执行结果:
/* 
webpack 1
webpack 2
webpack 3
*/

简化的原理:

class SyncHook{
    constructor(){
        this.hooks = [];
    }
​
    // 订阅事件
    tap(name, fn){
        this.hooks.push(fn);
    }
​
    // 发布
    call(){
        this.hooks.forEach(hook => hook(...arguments));
    }
}

Plugin

plugin 是带有 apply 方法的 class
实现一个简单的 plugin

// 1、Plugin名称
const MY_PLUGIN_NAME = "MyBasicPlugin";
​
class MyBasicPlugin {
  // 2、在构造函数中获取插件配置项
  constructor(option) {
    this.option = option;
  }
  // 3、在原型对象上定义一个 apply 函数供 webpack 注册
  apply(compiler) {
    // 4、注册 webpack 事件监听函数
    compiler.hooks.emit.tapAsync(MY_PLUGIN_NAME, (compilation, asyncCallback) => {
      // 5、操作 Or 改变 compilation 内部数据
      console.log(compilation);
​
      // 6、如果是异步钩子,结束后需要执行异步回调
      asyncCallback();
    });
  }
}

// 7、模块导出
module.exports = MyBasicPlugin;

在webpack.config.js 中 require 并实例化进行使用

const MyPlugin = require('./plugins/myplugin.js')
​
module.exports = {
    ......,
    plugins: [
        new MyPlugin("Plugin is instancing.")
    ]
}

webpack 中注册插件的代码

lib/webpack.js
JavaScript
if (Array.isArray(options.plugins)) {
  for (const plugin of options.plugins) {
    if (typeof plugin === "function") {
      plugin.call(compiler, compiler)
    } else {
      plugin.apply(compiler)
    }
  }
}

参考

webpack官方文档
Webpack漫谈
浅析webpack
Webpack揭秘——走向高阶前端的必经之路
Webpack源码解读:理清编译主流程