Webpack5 原理剖析与实现

Webpack5 原理剖析与实现

整体架构设计

Webpack5 采用了事件驱动的插件架构,整个构建过程通过一系列生命周期钩子来协调各个组件的工作。其核心架构可以分为以下几个层次:

架构概览

javascript 复制代码
// Webpack5 核心架构组件
const webpack = require('webpack');

class WebpackCore {
  constructor(options) {
    // 1. 配置处理
    this.options = this.processOptions(options);
    
    // 2. 创建 Compiler 实例
    this.compiler = new Compiler(this.options.context);
    this.compiler.options = this.options;
    
    // 3. 注册内置插件
    this.registerBuiltinPlugins();
    
    // 4. 应用用户插件
    this.applyPlugins();
  }
  
  processOptions(options) {
    // 配置标准化和默认值处理
    const defaultOptions = {
      mode: 'production',
      entry: './src/index.js',
      output: {
        path: path.resolve(process.cwd(), 'dist'),
        filename: '[name].js'
      },
      module: { rules: [] },
      plugins: []
    };
    
    return { ...defaultOptions, ...options };
  }
  
  registerBuiltinPlugins() {
    // 注册内置插件
    new EntryOptionPlugin().apply(this.compiler);
    new RuntimePlugin().apply(this.compiler);
    new InferAsyncModulesPlugin().apply(this.compiler);
  }
}
Webpack5 整体架构流程
graph TD A[Webpack启动] --> B[解析配置选项] B --> C[processOptions 配置标准化] C --> D[创建Compiler实例] D --> E[设置上下文和选项] E --> F[registerBuiltinPlugins 注册内置插件] F --> G[applyPlugins 应用用户插件] G --> H[初始化文件系统] H --> I[创建模块工厂] I --> J[设置解析器] J --> K[准备就绪 - 等待run调用] F --> F1[EntryOptionPlugin] F --> F2[RuntimePlugin] F --> F3[InferAsyncModulesPlugin] G --> G1[遍历plugins数组] G1 --> G2[调用plugin.apply方法] G2 --> G3[插件注册钩子监听器]

核心概念关系

javascript 复制代码
// Webpack5 核心概念及其关系
class WebpackConcepts {
  // 入口:构建依赖图的起点
  static Entry = {
    single: './src/index.js',
    multiple: {
      app: './src/app.js',
      vendor: './src/vendor.js'
    },
    dynamic: () => './src/dynamic-entry.js'
  };
  
  // 输出:打包后的文件配置
  static Output = {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[chunkhash].chunk.js',
    publicPath: '/',
    library: 'MyLibrary',
    libraryTarget: 'umd'
  };
  
  // 模块:Webpack 中一切皆模块
  static Module = {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  };
  
  // 插件:扩展 Webpack 功能
  static Plugins = [
    new HtmlWebpackPlugin(),
    new MiniCssExtractPlugin(),
    new CleanWebpackPlugin()
  ];
}

核心源码

Compiler

Compiler 是 Webpack 的核心控制器,负责整个编译过程的生命周期管理。它继承自 Tapable,提供了丰富的钩子系统。

javascript 复制代码
// lib/Compiler.js 核心实现
const { AsyncSeriesHook, SyncHook, AsyncParallelHook } = require('tapable');

class Compiler extends Tapable {
  constructor(context) {
    super();
    this.context = context;
    this.hooks = {
      // 编译开始前
      beforeRun: new AsyncSeriesHook(['compiler']),
      // 编译开始
      run: new AsyncSeriesHook(['compiler']),
      // 编译完成
      done: new AsyncSeriesHook(['stats']),
      // 编译失败
      failed: new SyncHook(['error']),
      // 监听模式下文件变化
      watchRun: new AsyncSeriesHook(['compiler']),
      // 创建 compilation 前
      beforeCompile: new AsyncSeriesHook(['params']),
      // 创建 compilation
      compile: new SyncHook(['params']),
      // compilation 创建完成
      thisCompilation: new SyncHook(['compilation', 'params']),
      // compilation 完成
      compilation: new SyncHook(['compilation', 'params']),
      // 生成资源
      emit: new AsyncSeriesHook(['compilation']),
      // 资源生成完成
      afterEmit: new AsyncSeriesHook(['compilation'])
    };
    
    this.running = false;
    this.watchMode = false;
    this.fileSystemInfo = new FileSystemInfo();
  }
  
  // 运行编译
  run(callback) {
    if (this.running) {
      return callback(new Error('Compiler is already running'));
    }
    
    this.running = true;
    
    const onCompiled = (err, compilation) => {
      if (err) return callback(err);
      
      if (this.hooks.shouldEmit.call(compilation) === false) {
        const stats = new Stats(compilation);
        this.hooks.done.callAsync(stats, () => {
          this.running = false;
          callback(null, stats);
        });
        return;
      }
      
      this.emitAssets(compilation, err => {
        if (err) return callback(err);
        
        this.hooks.afterEmit.callAsync(compilation, err => {
          if (err) return callback(err);
          
          const stats = new Stats(compilation);
          this.hooks.done.callAsync(stats, () => {
            this.running = false;
            callback(null, stats);
          });
        });
      });
    };
    
    this.hooks.beforeRun.callAsync(this, err => {
      if (err) return callback(err);
      
      this.hooks.run.callAsync(this, err => {
        if (err) return callback(err);
        
        this.compile(onCompiled);
      });
    });
  }
  
  // 核心编译方法
  compile(callback) {
    const params = this.newCompilationParams();
    
    this.hooks.beforeCompile.callAsync(params, err => {
      if (err) return callback(err);
      
      this.hooks.compile.call(params);
      
      const compilation = this.newCompilation(params);
      
      this.hooks.make.callAsync(compilation, err => {
        if (err) return callback(err);
        
        compilation.finish(err => {
          if (err) return callback(err);
          
          compilation.seal(err => {
            if (err) return callback(err);
            
            this.hooks.afterCompile.callAsync(compilation, err => {
              if (err) return callback(err);
              
              callback(null, compilation);
            });
          });
        });
      });
    });
  }
}
Compiler 生命周期
graph TD A[创建 Compiler] --> B[beforeRun] B --> C[run] C --> D[beforeCompile] D --> E[compile] E --> F[thisCompilation] F --> G[compilation] G --> H[make] H --> I[afterCompile] I --> J[shouldEmit] J --> K[emit] K --> L[afterEmit] L --> M[done] H --> H1[解析入口] H1 --> H2[构建模块] H2 --> H3[完成编译] J -->|false| N[跳过输出] N --> M

Compilation

Compilation 负责具体的编译工作,每次编译都会创建一个新的 Compilation 实例。它管理着模块的构建、依赖解析和代码生成。

javascript 复制代码
// lib/Compilation.js 核心实现
class Compilation extends Tapable {
  constructor(compiler) {
    super();
    this.compiler = compiler;
    this.hooks = {
      // 构建模块
      buildModule: new SyncHook(['module']),
      // 重新构建模块
      rebuildModule: new SyncHook(['module']),
      // 模块构建失败
      failedModule: new SyncHook(['module', 'error']),
      // 模块构建成功
      succeedModule: new SyncHook(['module']),
      // 添加入口
      addEntry: new SyncHook(['entry', 'options']),
      // 依赖引用
      dependencyReference: new SyncWaterfallHook(['dependencyReference', 'dependency', 'module']),
      // 优化模块
      optimizeModules: new SyncBailHook(['modules']),
      // 优化块
      optimizeChunks: new SyncBailHook(['chunks', 'chunkGroups']),
      // 优化树
      optimizeTree: new AsyncSeriesHook(['chunks', 'modules']),
      // 额外资源处理
      additionalAssets: new AsyncSeriesHook([]),
      // 创建模块资源
      createModuleAssets: new SyncHook([]),
      // 优化块资源
      optimizeChunkAssets: new AsyncSeriesHook(['chunks']),
      // 优化资源
      optimizeAssets: new AsyncSeriesHook(['assets'])
    };
    
    this.modules = new Set();
    this.chunks = new Set();
    this.assets = {};
    this.dependencyFactories = new Map();
    this.dependencyTemplates = new Map();
  }
  
  // 添加入口模块
  addEntry(context, dependency, options, callback) {
    const slot = {
      name: options.name,
      includeDependencies: [dependency],
      options
    };
    
    this.hooks.addEntry.call(dependency, options);
    
    this._addModuleChain(context, dependency, (module) => {
      this.entries.set(options.name, module);
    }, callback);
  }
  
  // 构建模块链
  _addModuleChain(context, dependency, onModule, callback) {
    const moduleFactory = this.dependencyFactories.get(dependency.constructor);
    
    moduleFactory.create({
      context,
      dependencies: [dependency],
      contextInfo: { issuer: '' }
    }, (err, module) => {
      if (err) return callback(err);
      
      this.buildModule(module, (err) => {
        if (err) return callback(err);
        
        onModule(module);
        this.processModuleDependencies(module, callback);
      });
    });
  }
  
  // 构建模块
  buildModule(module, callback) {
    this.hooks.buildModule.call(module);
    
    module.build(this.options, this, this.resolverFactory.get('normal'), this.inputFileSystem, (err) => {
      if (err) {
        this.hooks.failedModule.call(module, err);
        return callback(err);
      }
      
      this.hooks.succeedModule.call(module);
      callback();
    });
  }
  
  // 处理模块依赖
  processModuleDependencies(module, callback) {
    const dependencies = [];
    
    // 收集所有依赖
    module.dependencies.forEach(dependency => {
      dependencies.push({
        factory: this.dependencyFactories.get(dependency.constructor),
        dependency,
        originModule: module
      });
    });
    
    // 并行处理依赖
    async.forEach(dependencies, (item, callback) => {
      this.handleModuleCreation(item, callback);
    }, callback);
  }
  
  // 封装构建结果
  seal(callback) {
    this.hooks.seal.call();
    
    // 创建块
    this.createChunks();
    
    // 优化
    this.optimize();
    
    // 生成代码
    this.createModuleAssets();
    
    this.hooks.additionalAssets.callAsync(err => {
      if (err) return callback(err);
      
      this.summarizeDependencies();
      this.createChunkAssets();
      callback();
    });
  }
  
  // 创建块
  createChunks() {
    const chunkGraph = new ChunkGraph();
    
    for (const [name, module] of this.entries) {
      const chunk = this.addChunk(name);
      const entrypoint = new Entrypoint(name);
      entrypoint.setRuntimeChunk(chunk);
      entrypoint.addOrigin(null, name, module.request);
      
      chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
    }
    
    this.chunkGraph = chunkGraph;
  }
}
Compilation 工作流程
graph TD A[创建 Compilation] --> B[addEntry] B --> C[构建入口模块] C --> D[解析依赖] D --> E[递归构建依赖模块] E --> F[完成模块构建] F --> G[seal 封装] G --> H[创建 Chunk] H --> I[优化处理] I --> J[生成代码] J --> K[输出资源] E --> E1[模块工厂创建] E1 --> E2[Loader 处理] E2 --> E3[AST 解析] E3 --> E4[依赖收集] I --> I1[模块优化] I1 --> I2[Chunk 优化] I2 --> I3[资源优化]

Module

Module 是 Webpack 中的基本单位,代表一个模块文件。所有类型的文件都会被转换为 Module 对象。

javascript 复制代码
// lib/Module.js 核心实现
class Module extends DependenciesBlock {
  constructor(type, context) {
    super();
    this.type = type;
    this.context = context;
    this.debugId = debugId++;
    this.hash = undefined;
    this.renderedHash = undefined;
    this.resolveOptions = EMPTY_RESOLVE_OPTIONS;
    this.factoryMeta = {};
    this.warnings = [];
    this.errors = [];
    this.buildMeta = {};
    this.buildInfo = {};
    this.presentationalDependencies = [];
  }
  
  // 模块标识符
  identifier() {
    throw new Error('Module.identifier() must be implemented');
  }
  
  // 可读的标识符
  readableIdentifier(requestShortener) {
    return requestShortener.shorten(this.identifier());
  }
  
  // 构建模块
  build(options, compilation, resolver, fs, callback) {
    this.buildTimestamp = Date.now();
    this.built = true;
    this._source = null;
    this._buildHash = '';
    this.buildMeta = {};
    this.buildInfo = {
      cacheable: true,
      parsed: true,
      fileDependencies: new LazySet(),
      contextDependencies: new LazySet(),
      missingDependencies: new LazySet(),
      buildDependencies: new LazySet(),
      assets: undefined,
      assetsInfo: undefined
    };
    
    return this.doBuild(options, compilation, resolver, fs, err => {
      if (err) return callback(err);
      
      const handleParseError = e => {
        const source = this._source.source();
        const error = new ModuleParseError(source, e);
        this.markModuleAsErrored(error);
        this._initBuildHash(compilation);
        return callback(error);
      };
      
      const handleParseResult = result => {
        this.dependencies.length = 0;
        this.blocks.length = 0;
        this.variables.length = 0;
        this.buildMeta = result.buildMeta || {};
        this.buildInfo = result.buildInfo || {};
        
        this._initBuildHash(compilation);
        return callback();
      };
      
      try {
        const result = this.parser.parse(this._ast || this._source.source(), {
          current: this,
          module: this,
          compilation: compilation,
          options: options
        });
        
        handleParseResult(result);
      } catch (e) {
        handleParseError(e);
      }
    });
  }
  
  // 实际构建逻辑
  doBuild(options, compilation, resolver, fs, callback) {
    throw new Error('Module.doBuild() must be implemented');
  }
  
  // 获取源码
  source(dependencyTemplates, runtimeTemplate, type = 'javascript') {
    const source = this.generator.generate(this, {
      dependencyTemplates,
      runtimeTemplate,
      type
    });
    return source;
  }
  
  // 计算哈希
  updateHash(hash, chunkGraph, runtime) {
    hash.update(this.buildMeta.exportsType || '');
    hash.update(`${this.buildMeta.strictHarmonyModule}`);
    hash.update(`${this.buildMeta.exportsArgument}`);
    hash.update(`${this.buildMeta.async}`);
    super.updateHash(hash, chunkGraph, runtime);
  }
}

// NormalModule 继承自 Module,处理普通的 JS 模块
class NormalModule extends Module {
  constructor({
    type,
    request,
    userRequest,
    rawRequest,
    loaders,
    resource,
    parser,
    generator,
    resolveOptions
  }) {
    super(type, getContext(resource));
    this.request = request;
    this.userRequest = userRequest;
    this.rawRequest = rawRequest;
    this.loaders = loaders;
    this.resource = resource;
    this.parser = parser;
    this.generator = generator;
    this.resolveOptions = resolveOptions;
  }
  
  // 实际构建
  doBuild(options, compilation, resolver, fs, callback) {
    const loaderContext = this.createLoaderContext(resolver, options, compilation, fs);
    
    runLoaders({
      resource: this.resource,
      loaders: this.loaders,
      context: loaderContext,
      readResource: fs.readFile.bind(fs)
    }, (err, result) => {
      if (err) return callback(err);
      
      this._source = this.createSource(options.context, result.result[0], result.map);
      this._sourceSize = null;
      this._ast = result.result.length >= 1 ? result.result[1] : null;
      
      callback();
    });
  }
  
  // 创建源码对象
  createSource(context, content, sourceMap) {
    if (Buffer.isBuffer(content)) {
      return new RawSource(content);
    }
    
    if (!sourceMap) {
      return new RawSource(content);
    }
    
    return new SourceMapSource(content, this.identifier(), sourceMap);
  }
}
NormalModule 构建流程
graph TD A[模块构建开始] --> B[调用build方法] B --> C[设置buildInfo和buildMeta] C --> D[调用doBuild] D --> E[创建LoaderContext] E --> F[runLoaders执行Loader链] F --> G[获取转换后的源码] G --> H[创建Source对象] H --> I[Parser解析AST] I --> J[依赖收集] J --> K[更新buildMeta] K --> L[计算模块hash] L --> M[构建完成] F --> F1[匹配Loader规则] F1 --> F2[从右到左执行Loader] F2 --> F3[生成最终源码] I --> I1[生成AST] I1 --> I2[遍历AST节点] I2 --> I3[识别依赖语句] J --> J1[require语句] J --> J2[import语句] J --> J3[动态import] J --> J4[资源引用]

Dependency

Dependency 表示模块间的依赖关系,Webpack 通过解析这些依赖来构建完整的依赖图。

javascript 复制代码
// lib/Dependency.js 核心实现
class Dependency {
  constructor() {
    this._parentModule = undefined;
    this._parentDependenciesBlock = undefined;
    this.weak = false;
    this.optional = false;
  }
  
  // 依赖类型
  getResourceIdentifier() {
    return null;
  }
  
  // 获取引用的模块
  getReference(moduleGraph) {
    throw new Error('Dependency.getReference() must be implemented');
  }
  
  // 获取导出
  getExports(moduleGraph) {
    return undefined;
  }
  
  // 获取警告
  getWarnings(moduleGraph) {
    return null;
  }
  
  // 获取错误
  getErrors(moduleGraph) {
    return null;
  }
  
  // 序列化
  serialize(context) {
    const { write } = context;
    write(this.weak);
    write(this.optional);
  }
  
  // 反序列化
  deserialize(context) {
    const { read } = context;
    this.weak = read();
    this.optional = read();
  }
}

// ModuleDependency 模块依赖基类
class ModuleDependency extends Dependency {
  constructor(request) {
    super();
    this.request = request;
    this.userRequest = request;
  }
  
  getResourceIdentifier() {
    return `module${this.request}`;
  }
  
  createIgnoredModule(context) {
    return new RawModule('/* (ignored) */', `ignored|${context}|${this.request}`, `${this.request} (ignored)`);
  }
  
  serialize(context) {
    const { write } = context;
    write(this.request);
    write(this.userRequest);
    super.serialize(context);
  }
  
  deserialize(context) {
    const { read } = context;
    this.request = read();
    this.userRequest = read();
    super.deserialize(context);
  }
}

// CommonJS require 依赖
class CommonJsRequireDependency extends ModuleDependency {
  constructor(request, range) {
    super(request);
    this.range = range;
  }
  
  get type() {
    return 'cjs require';
  }
  
  getReference(moduleGraph) {
    const module = moduleGraph.getModule(this);
    if (!module) return null;
    
    return {
      module,
      weak: this.weak,
      importedNames: true
    };
  }
}

// ES6 import 依赖
class HarmonyImportSideEffectDependency extends HarmonyImportDependency {
  constructor(request, sourceOrder) {
    super(request, sourceOrder);
  }
  
  get type() {
    return 'harmony side effect evaluation';
  }
  
  getReference(moduleGraph) {
    return {
      module: moduleGraph.getModule(this),
      weak: false,
      importedNames: false
    };
  }
}
依赖解析流程
graph TD A[模块解析] --> B[AST 解析] B --> C[依赖收集] C --> D[依赖分类] D --> E[依赖工厂处理] E --> F[模块创建] F --> G[递归解析] C --> C1[require 语句] C --> C2[import 语句] C --> C3[动态 import] C --> C4[资源引用] D --> D1[CommonJS 依赖] D --> D2[ES6 模块依赖] D --> D3[AMD 依赖] D --> D4[资源依赖] E --> E1[NormalModuleFactory] E --> E2[ContextModuleFactory] E --> E3[DllModuleFactory]

Loader

Loader 是 Webpack 的核心功能之一,负责将各种类型的文件转换为 JavaScript 模块。Loader 本质上是一个函数,接收源码作为输入,返回转换后的代码。

javascript 复制代码
// Loader 的基本结构
function myLoader(source, map, meta) {
  // source: 源文件内容
  // map: 可以是 SourceMap 数据
  // meta: meta 数据,可以是任何内容
  
  // 获取 loader 的配置选项
  const options = this.getOptions();
  
  // 获取 callback 函数
  const callback = this.async();
  
  // 异步处理
  setTimeout(() => {
    // 进行代码转换
    const result = transform(source, options);
    
    // 调用 callback 返回结果
    // callback(error, transformedSource, sourceMap, meta)
    callback(null, result, map, meta);
  }, 100);
}

// 处理 raw 内容
myLoader.raw = true;

module.exports = myLoader;
Loader 工作流程
graph TD A[模块需要转换] --> B[匹配Loader规则] B --> C[确定Loader链] C --> D[创建LoaderContext] D --> E[开始执行Loader链] E --> F[Pitch阶段: 从左到右] F --> G[读取原始文件内容] G --> H[Normal阶段: 从右到左] H --> I[执行第一个Loader] I --> J{是否异步?} J -->|同步| K[直接返回结果] J -->|异步| L[调用callback返回] K --> M[传递给下一个Loader] L --> M M --> N{还有Loader?} N -->|是| I N -->|否| O[获得最终转换结果] O --> P[返回给模块构建流程] I --> I1[获取options配置] I1 --> I2[处理source内容] I2 --> I3[生成sourceMap] I3 --> I4[返回转换结果]
Loader Context API
javascript 复制代码
// Loader 上下文提供的 API
class LoaderContext {
  constructor() {
    // 当前处理的文件路径
    this.resourcePath = '';
    this.resourceQuery = '';
    this.resourceFragment = '';
    
    // 请求字符串
    this.request = '';
    this.userRequest = '';
    this.rawRequest = '';
    
    // Loader 配置
    this.loaders = [];
    this.loaderIndex = 0;
    
    // 编译上下文
    this.context = '';
    this.rootContext = '';
    
    // 文件系统相关
    this.fs = null;
    
    // 缓存相关
    this.cacheable = true;
    
    // 依赖收集
    this.addDependency = (file) => {
      this.dependencies.push(file);
    };
    
    this.addContextDependency = (context) => {
      this.contextDependencies.push(context);
    };
    
    this.addMissingDependency = (missing) => {
      this.missingDependencies.push(missing);
    };
  }
  
  // 获取 loader 选项
  getOptions(schema) {
    const options = this.query;
    
    if (schema && typeof options === 'object') {
      validate(schema, options, {
        name: 'Loader',
        baseDataPath: 'options'
      });
    }
    
    return options || {};
  }
  
  // 异步回调
  async() {
    if (this.callback) {
      throw new Error('already called');
    }
    this.callback = this.callback || function() {};
    return this.callback;
  }
  
  // 同步返回
  return(content, sourceMap, meta) {
    if (this.callback) {
      this.callback(null, content, sourceMap, meta);
    } else {
      this.result = [content, sourceMap, meta];
    }
  }
  
  // 缓存控制
  cacheable(flag = true) {
    this.cacheable = flag;
  }
  
  // 解析请求
  resolve(context, request, callback) {
    this.resolver.resolve({}, context, request, {}, callback);
  }
  
  // 加载模块
  loadModule(request, callback) {
    this.compilation.buildModule(
      this.compilation.moduleGraph.getModule(this.dependency),
      callback
    );
  }
}
常用 Loader 实现示例
javascript 复制代码
// Babel Loader 简化实现
function babelLoader(source, inputSourceMap) {
  const options = this.getOptions();
  const callback = this.async();
  
  // 如果没有配置,直接返回源码
  if (!options.presets && !options.plugins) {
    return callback(null, source, inputSourceMap);
  }
  
  // 使用 Babel 转换
  babel.transform(source, {
    ...options,
    filename: this.resourcePath,
    inputSourceMap: inputSourceMap || undefined,
  }, (err, result) => {
    if (err) return callback(err);
    
    callback(null, result.code, result.map);
  });
}

// CSS Loader 简化实现
function cssLoader(source) {
  const options = this.getOptions();
  const callback = this.async();
  
  // 解析 CSS
  postcss([
    require('postcss-import')(),
    require('autoprefixer')()
  ])
  .process(source, {
    from: this.resourcePath,
    map: { inline: false }
  })
  .then(result => {
    // 转换为 JavaScript 模块
    const jsCode = `
      const css = ${JSON.stringify(result.css)};
      export default css;
    `;
    
    callback(null, jsCode, result.map);
  })
  .catch(callback);
}

// File Loader 简化实现
function fileLoader(content) {
  const options = this.getOptions();
  const context = options.context || this.rootContext;
  
  // 生成文件名
  const url = interpolateName(this, options.name || '[hash].[ext]', {
    context,
    content,
    regExp: options.regExp
  });
  
  // 输出文件
  this.emitFile(url, content);
  
  // 返回文件路径
  return `module.exports = ${JSON.stringify(url)};`;
}

fileLoader.raw = true; // 处理二进制内容

Plugin

Plugin 是 Webpack 的另一个核心概念,通过监听编译过程中的事件钩子来扩展 Webpack 的功能。

javascript 复制代码
// Plugin 的基本结构
class MyPlugin {
  constructor(options = {}) {
    this.options = options;
  }
  
  // apply 方法是插件的入口
  apply(compiler) {
    const pluginName = MyPlugin.name;
    
    // 监听 compilation 钩子
    compiler.hooks.compilation.tap(pluginName, (compilation) => {
      // 监听 Compilation 的钩子
      compilation.hooks.optimizeAssets.tapAsync(pluginName, (assets, callback) => {
        // 处理资源
        for (const assetName in assets) {
          const asset = assets[assetName];
          const source = asset.source();
          
          // 进行一些处理
          const processedSource = this.processAsset(source);
          
          // 更新资源
          assets[assetName] = {
            source: () => processedSource,
            size: () => processedSource.length
          };
        }
        
        callback();
      });
    });
    
    // 监听其他钩子
    compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => {
      // 在输出资源前执行
      console.log('Assets will be emitted:', Object.keys(compilation.assets));
      callback();
    });
  }
  
  processAsset(source) {
    // 处理资源的逻辑
    return source.replace(/console\.log/g, '// console.log');
  }
}

module.exports = MyPlugin;
Plugin 工作流程
graph TD A[Webpack启动] --> B[实例化Compiler] B --> C[读取配置中的plugins] C --> D[遍历plugins数组] D --> E[判断plugin类型] E --> F{是函数还是对象?} F -->|函数| G[直接调用plugin函数] F -->|对象| H[调用plugin.apply方法] G --> I[传入compiler实例] H --> I I --> J[Plugin注册钩子监听器] J --> K[编译过程开始] K --> L[触发各阶段钩子] L --> M[执行对应Plugin回调] M --> N[Plugin处理逻辑] N --> O[修改compilation或assets] O --> P[继续下一个钩子] J --> J1[compiler.hooks.xxx.tap] J --> J2[compiler.hooks.xxx.tapAsync] J --> J3[compiler.hooks.xxx.tapPromise] L --> L1[beforeRun钩子] L --> L2[compilation钩子] L --> L3[emit钩子] L --> L4[done钩子] N --> N1[资源处理] N --> N2[文件生成] N --> N3[代码优化] N --> N4[数据收集]
常用 Plugin 实现示例
javascript 复制代码
// HTML Webpack Plugin 简化实现
class HtmlWebpackPlugin {
  constructor(options = {}) {
    this.options = {
      template: 'src/index.html',
      filename: 'index.html',
      inject: true,
      ...options
    };
  }
  
  apply(compiler) {
    const pluginName = HtmlWebpackPlugin.name;
    
    compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => {
      // 获取入口 chunks
      const entryNames = Array.from(compilation.entrypoints.keys());
      const assets = this.getAssets(compilation, entryNames);
      
      // 读取模板
      const templateContent = compilation.inputFileSystem.readFileSync(
        this.options.template,
        'utf8'
      );
      
      // 生成 HTML
      const html = this.generateHtml(templateContent, assets);
      
      // 添加到输出资源
      compilation.assets[this.options.filename] = {
        source: () => html,
        size: () => html.length
      };
      
      callback();
    });
  }
  
  getAssets(compilation, entryNames) {
    const assets = { js: [], css: [] };
    
    entryNames.forEach(entryName => {
      const entrypoint = compilation.entrypoints.get(entryName);
      const chunks = entrypoint.getFiles();
      
      chunks.forEach(chunk => {
        if (chunk.endsWith('.js')) {
          assets.js.push(chunk);
        } else if (chunk.endsWith('.css')) {
          assets.css.push(chunk);
        }
      });
    });
    
    return assets;
  }
  
  generateHtml(template, assets) {
    let html = template;
    
    // 注入 CSS
    const cssLinks = assets.css.map(css => 
      `<link rel="stylesheet" href="${css}">`
    ).join('\n');
    
    // 注入 JS
    const jsScripts = assets.js.map(js => 
      `<script src="${js}"></script>`
    ).join('\n');
    
    // 替换占位符
    html = html.replace('</head>', `${cssLinks}\n</head>`);
    html = html.replace('</body>', `${jsScripts}\n</body>`);
    
    return html;
  }
}

// Mini CSS Extract Plugin 简化实现
class MiniCssExtractPlugin {
  constructor(options = {}) {
    this.options = {
      filename: '[name].css',
      chunkFilename: '[id].css',
      ...options
    };
  }
  
  apply(compiler) {
    const pluginName = MiniCssExtractPlugin.name;
    
    compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
      compilation.hooks.processAssets.tap({
        name: pluginName,
        stage: compilation.PROCESS_ASSETS_STAGE_EXTRACT
      }, () => {
        const chunks = compilation.chunks;
        
        chunks.forEach(chunk => {
          const cssModules = [];
          
          // 收集 CSS 模块
          compilation.chunkGraph.getChunkModulesIterable(chunk).forEach(module => {
            if (module.type === 'css/mini-extract') {
              cssModules.push(module);
            }
          });
          
          if (cssModules.length > 0) {
            // 合并 CSS 内容
            const cssContent = cssModules.map(m => m.content).join('\n');
            
            // 生成文件名
            const filename = compilation.getPath(this.options.filename, {
              chunk,
              contentHash: this.getContentHash(cssContent)
            });
            
            // 添加到资源
            compilation.assets[filename] = {
              source: () => cssContent,
              size: () => cssContent.length
            };
            
            // 更新 chunk 文件列表
            chunk.files.add(filename);
          }
        });
      });
    });
  }
  
  getContentHash(content) {
    return crypto.createHash('md5').update(content).digest('hex').slice(0, 8);
  }
}

Loader 与 Plugin 执行过程分析

Loader 的执行

Loader 的执行是在模块构建阶段,按照从右到左、从下到上的顺序执行。

javascript 复制代码
// Loader 执行器实现
class LoaderRunner {
  constructor(options) {
    this.resource = options.resource;
    this.loaders = options.loaders;
    this.context = options.context;
    this.readResource = options.readResource;
  }
  
  run(callback) {
    let loaderIndex = this.loaders.length - 1;
    let resourceBuffer = null;
    
    // 创建 loader 上下文
    const loaderContext = this.createLoaderContext();
    
    // 递归执行 loader
    const iterateLoaders = (options, callback) => {
      if (loaderIndex < 0) {
        // 所有 loader 执行完毕,读取文件
        return this.readResource(this.resource, (err, buffer) => {
          if (err) return callback(err);
          resourceBuffer = buffer;
          loaderIndex = this.loaders.length - 1;
          iterateNormalLoaders(options, callback);
        });
      }
      
      const currentLoader = this.loaders[loaderIndex--];
      
      // 执行 loader
      this.runSyncOrAsync(currentLoader, loaderContext, [resourceBuffer], (err, ...results) => {
        if (err) return callback(err);
        
        resourceBuffer = results[0];
        iterateLoaders(options, callback);
      });
    };
    
    // 开始执行
    iterateLoaders({}, callback);
  }
  
  createLoaderContext() {
    const context = {
      version: 2,
      resource: this.resource,
      resourcePath: this.resource.split('?')[0],
      resourceQuery: this.resource.includes('?') ? this.resource.split('?')[1] : '',
      
      async: () => {
        const callback = context.callback;
        context.callback = null;
        return callback;
      },
      
      getOptions: (schema) => {
        const loader = this.loaders[loaderIndex];
        return loader.options || {};
      },
      
      emitFile: (name, content, sourceMap) => {
        this.compilation.assets[name] = {
          source: () => content,
          size: () => content.length
        };
      }
    };
    
    return context;
  }
  
  runSyncOrAsync(loader, context, args, callback) {
    let isSync = true;
    let isDone = false;
    
    // 设置异步回调
    context.callback = (...results) => {
      if (isDone) return;
      isDone = true;
      callback(null, ...results);
    };
    
    // 执行 loader 函数
    try {
      const result = loader.apply(context, args);
      
      if (isSync) {
        isDone = true;
        if (result === undefined) {
          return callback();
        }
        return callback(null, result);
      }
    } catch (err) {
      if (isDone) return;
      isDone = true;
      return callback(err);
    }
    
    isSync = false;
  }
}
Loader 执行流程
graph TD A[模块构建开始] --> B[确定 Loader 链] B --> C[创建 LoaderContext] C --> D[从右到左执行 Loader] D --> E[读取原始文件] E --> F[应用第一个 Loader] F --> G[应用下一个 Loader] G --> H{还有 Loader?} H -->|是| G H -->|否| I[返回转换结果] I --> J[AST 解析] J --> K[依赖收集] F --> F1[同步执行] F --> F2[异步执行] F1 --> F3[直接返回结果] F2 --> F4[调用 callback] D --> D1[Pitch 阶段] D1 --> D2[Normal 阶段] D2 --> D3[从左到右] D3 --> D4[从右到左]
Plugin 的执行

Plugin 通过监听 Webpack 的生命周期钩子来执行,可以在编译的各个阶段介入处理。

javascript 复制代码
// Plugin 执行过程分析
class PluginExecutor {
  constructor(compiler) {
    this.compiler = compiler;
    this.plugins = [];
  }
  
  // 应用插件
  applyPlugins(plugins) {
    plugins.forEach(plugin => {
      if (typeof plugin === 'function') {
        plugin.call(this.compiler, this.compiler);
      } else {
        plugin.apply(this.compiler);
      }
    });
  }
  
  // 触发钩子
  triggerHook(hookName, ...args) {
    const hook = this.compiler.hooks[hookName];
    
    if (!hook) return;
    
    switch (hook.constructor.name) {
      case 'SyncHook':
        hook.call(...args);
        break;
        
      case 'AsyncSeriesHook':
        hook.callAsync(...args, (err) => {
          if (err) throw err;
        });
        break;
        
      case 'AsyncParallelHook':
        hook.callAsync(...args, (err) => {
          if (err) throw err;
        });
        break;
        
      case 'SyncWaterfallHook':
        return hook.call(...args);
        
      case 'AsyncSeriesWaterfallHook':
        hook.callAsync(...args, (err, result) => {
          if (err) throw err;
          return result;
        });
        break;
    }
  }
}
Plugin 执行流程
graph TD A[Webpack 启动] --> B[实例化 Compiler] B --> C[应用 Plugin] C --> D[注册事件监听] D --> E[开始编译] E --> F[触发 beforeRun] F --> G[触发 run] G --> H[触发 beforeCompile] H --> I[触发 compile] I --> J[创建 Compilation] J --> K[触发 thisCompilation] K --> L[触发 compilation] L --> M[触发 make] M --> N[模块构建] N --> O[触发 seal] O --> P[优化处理] P --> Q[触发 emit] Q --> R[输出文件] R --> S[触发 done] C --> C1[遍历插件数组] C1 --> C2[调用 apply 方法] C2 --> C3[绑定钩子监听器] P --> P1[optimizeModules] P1 --> P2[optimizeChunks] P2 --> P3[optimizeTree] P3 --> P4[optimizeAssets]
完整的编译流程总览
graph TD A[Webpack 启动] --> B[解析配置] B --> C[创建 Compiler] C --> D[注册插件] D --> E[开始编译] E --> F[创建 Compilation] F --> G[解析入口] G --> H[模块构建] H --> I[Loader 处理] I --> J[AST 解析] J --> K[依赖收集] K --> L[递归构建依赖] L --> M[模块完成] M --> N[生成 Chunk] N --> O[优化处理] O --> P[代码生成] P --> Q[Plugin 处理资源] Q --> R[输出文件] R --> S[编译完成] I --> I1[匹配 Loader 规则] I1 --> I2[创建 LoaderContext] I2 --> I3[执行 Loader 链] Q --> Q1[emit 钩子] Q1 --> Q2[处理资源] Q2 --> Q3[生成最终文件]

通过以上深入的源码分析,我们可以看到 Webpack5 的强大之处在于其灵活的插件架构和完善的生命周期钩子系统。Compiler 负责整体的编译流程控制,Compilation 处理具体的编译任务,Module 和 Dependency 构成了依赖图的基础,而 Loader 和 Plugin 则提供了强大的扩展能力。

理解这些核心概念和执行流程,对于前端工程师深度定制构建流程、性能优化以及问题排查都具有重要意义。在实际项目中,我们可以基于这些原理来开发自定义的 Loader 和 Plugin,以满足特定的业务需求。

相关推荐
程序猿师兄6 分钟前
若依框架前端调用后台服务报跨域错误
前端
前端小巷子10 分钟前
跨标签页通信(三):Web Storage
前端·面试·浏览器
工呈士10 分钟前
TCP 三次握手与四次挥手详解
前端·后端·面试
BillKu11 分钟前
Vue3 + TypeScript + Element Plus + el-input 输入框列表按回车聚焦到下一行
前端·javascript·typescript
复苏季风12 分钟前
前端程序员unity学习笔记01: 从c#开始的入门,using命名空间,MonoBehaviour,static,public
前端
阿古达木15 分钟前
沉浸式改 bug,步步深入
前端·javascript·github
stoneSkySpace24 分钟前
react 自定义状态管理库
前端·react.js·前端框架
堕落年代36 分钟前
SpringAI1.0的MCPServer自动暴露Tool
前端
南囝coding1 小时前
一篇文章带你了解清楚,Google Cloud 引发全球互联网服务大面积故障问题
前端·后端
Humbunklung1 小时前
DeepSeek辅助写一个Vue3页面
前端·javascript·vue.js