webpack打包流程梳理

123 阅读3分钟

webpack打包主要流程

  1. webpack合并shell命令的参数和# webpack.config.js的配置得到最终Options参数
  2. 循环遍历plugins,加载plugin
  3. 初始化Compile,传入上面得到的最终Options参数
  4. 运行Compile.run,Compile.run中初始化Compalation,并传入callback(编译完成后出来文件写入)
  5. Compalation内部根据入口文件,开始使用loader编译,
  6. 再找出该模块依赖的模块递归遍历依赖,经过loader编译。
  7. 根据入口模块和依赖关系,生成包含多个模块的chunk
  8. 根据生成的chunk,确定输出文件,写入文件,并监听所有依赖文件的变化,当发生编译,重新编译
  9. webpack会在编译的过程中抛出事件,plugins执行hook钩子,hook可以调用Webpack提供的API改变Webpack的运行结果

1.webpack入口文件,如cra里的start.js

const webpack = require('webpack');

const configFactory = require('../config/webpack.config');

2. 引入webpack和webpack.config.js, 调用webpack方法得到compiler,传入options配置,合并shell语句和配置文件的参数,得到最终的配置对象。同时加载配置中的plugins

const { plugins = [] } = finalOptions;

for (const plugin of plugins) {
    
    plugin.apply(compiler)

}

3. 用上一步得到的配置参数,初始化Compiler对象

  const compiler = new Compiler(finalOptions);

4. 加载配置里面的插件, 循环调用plugin的apply方法

  const { plugins } = finalOptions;
  for (let plugin of plugins) {
    plugin.apply(compiler);
  }

//   plugin
class RunPlugin{
  apply(compiler) {
    //在此插件里可以监听run这个钩子
    compiler.hooks.run.tap('RunPlugin', () => {
      console.log('run1:开始编译');
    });
  }
}

4. 执行Compiler的run方法,初始化Compilation 开始执行编译

compiler.run((err, stats) => {});

// Compiler 类(伪代码)

class Compiler{
  constructor(options) {
    this.options = options;
    this.hooks = {
      run: new SyncHook(),//在开始编译之前调用
      done:new SyncHook() //在编译完成时执行
    }
  }
  run(callback) {
    // 在编译的开始调用开始钩子
    this.hooks.run.call();
    
    //在编译的过程中会收集所有的依赖的模块或者说文件
    //stats指的是统计信息 modules chunks  files=bundle assets指的是文件名和文件内容的映射关系
    const onCompiled = (err, stats, fileDependencies) => {
        /**  省略部分代码*/
        
    //在编译完成时触发done钩子执行  
      this.hooks.done.call();
    }
    //调用compile方法进行编译
    this.compile(onCompiled);
  }
  //开启一次新的编译
  compile(callback) {
    //每次编译 都会创建一个新的Compilation实例,
    let compilation = new Compilation(this.options,this);
    //  compilation.build 内部实现: 
    // 读取入口文件
    // 文件根据role匹配来使用对应的loader去处理文件
    // 递归查找依赖,所有文件都经过编译后。组装成包含多个文件的chunk
    // 根据对应关系输出文件列表, 在callback中写入文件
    compilation.build(callback);
    
    // build完成后调用onCompiled

  }
}

5. 在Compiler里面onCompiled处理文件写入和依赖文件的监听, 最后调用完成钩子

 const onCompiled = (err, stats, fileDependencies) => {}
    //调用compile方法进行编译
  his.compile(onCompiled);

compile(callback) {

const compilation = new Compilation(this.options);

//调用compilation的build方法开始编译

compilation.build(callback);

}

6. Compilation中查找入口文件,loader编译入口文件,递归遍历入口文件的依赖,保证所有的依赖模块都进过loader编译。 根据入口模块和模块的依赖生成chunk。

const chunk = {

name: entryName,//代码块的名称就是入口的名乐

entryModule,//入口的模块

modules: this.modules.filter(module => module.names.includes(entryName))

}

7.遍历完成后,调用onCompiled去写入文件到output到目录中,并监听依赖文件的变化,当依赖文件变化后,开始新的编译

const onCompiled = (err, stats, fileDependencies) => {
      console.log('stats',stats);
      console.log('fileDependencies',fileDependencies);
      //在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
      for (let filename in stats.assets) {
        let filePath = path.join(this.options.output.path, filename);
        fs.writeFileSync(filePath,stats.assets[filename],'utf8');
      }
      callback(err, { toJson: () => stats });//是这里面传给run方法的回调 compiler.run((err, stats) => {});

      for (let fileDependency of fileDependencies) {
         //监听依赖的文件变化,如果依赖的文件变化后会开始一次新的编译
        fs.watch(fileDependency,()=>this.compile(onCompiled));
      }
      this.hooks.done.call();//在编译完成时触发done钩子执行
    }

8. 在打包编译过程中,webpack会抛出一些事件,执行一些对应的钩子