最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Webpack编译流程详解

    正文概述 掘金(WILL)   2021-04-02   701

    1. webpack 运行流程图

    Webpack编译流程详解 跟着流程图走, 弄懂编译流程不用愁~

    2. 研究 webpack 源码的好处

    • 理解 webpack 的运行流程
    • 理解 webpack 插件
    • 学习 webpack 中的技巧和思想

    3. webpack 插件

    一个简单的插件

        class TestPlugin {
          apply(compiler) {
            console.log("compiler", compiler);
            compiler.hooks.compilation.tap("TestPlugin", function() {
              console.log("compilation", compilation);
            });
          }
        }
        module.exports = TestPlugin;
    
    • 写插件的关键: 了解 compile, compilation 具体是做什么的, 了解这两个函数的参数,方法, 运行机制,才能写好插件

    4. webpack 入口

    • webpack 从入口开始, 输入的是什么, 输出的又是什么呢?

      • 输入: 配置文件+ 源代码
      • 输出: 打包后的文件
    • webpack 的入口是一个 webpack 的函数, 下面我们来看看这个函数具体做了什么, 主要流程如下:

        webpack = options => {
          // 1. 整合options
          // options就是配置, 包括自己的配置加webpack的默认配置, 指导webpack后续的运行
          // 2. 实例化compile
          const compile = new Compile(options.context);
          // 3. 实例化所有的插件, 调用他们的apply方法
    
          // 4. 返回compile 示例
          return compile;
        };
    
    • 以下模拟 webpack-cli 中的代码, 相当于在命令行输入 webpack
        // webpack函数核心是生成compile函数, 在外部执行它
        const options = require("./webpack.config.js"); // 所有配置文件
        const compile = webpack(options); // 执行webpack函数
        compile.run(); // 执行
    
    • webpack 函数第三步的代码示例
        // 插件是否被实例化,是可以进行控制的, 见以下代码
        // WebpackOptionsApply.js
        // options.optimization.removeAvailableModules为true, 才会实例化插件
        if (options.optimization.removeAvailableModules) {
          const RemoveParentModulesPlugin = require("./optimize/RemoveParentModulesPlugin");
          new RemoveParentModulesPlugin().apply(compiler);
        }
    
        // 对于某些插件, 是必须要进行实例化的
        // 如NodeEnvironmentPlugin, 在实例化compile后, 会立即执行new NodeEnvironmentPlugin(compile).run()
        // 以下为本插件的代码:
    
        class NodeEnvironmentPlugin {
          apply(compile) {
            compiler.inputFileSystem = new CachedInputFileSystem(
              new NodeJsInputFileSystem(),
              60000
            );
            const inputFileSystem = compiler.inputFileSystem;
            compiler.outputFileSystem = new NodeOutputFileSystem();
            compiler.watchFileSystem = new NodeWatchFileSystem(
              compiler.inputFileSystem
            );
            compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
              if (compiler.inputFileSystem === inputFileSystem)
                inputFileSystem.purge();
            });
          }
        }
        // 本插件赋予了compile对象文件存储的能力, 这是必须插件
    
    对于webpack插件分为两种, 一种是必须的, 一种是优化插件
    

    5. compile

    从 webpack 函数中, 我们知道了实例化 compile 对象后, 会执行 compile.run 方法, 以下我们来看看 compile 函数

       // 先看compile中的run方式
        // compile.js
        class compile extends Tapable {
          constructor(context) {
            // 注册钩子
            this.hooks = {
              ...beforeRun,
              run
            };
          }
          run() {
            this.hooks.beforeRun.callAsync(this, err => {
              this.hooks.run.callAsync(this, err => {
                this.compile(onCompiled); // 传入onCompiled回调
              });
            });
          }
        }
    
    执行步骤  beforeRun(此钩子在上例NodeEnvironmentPlugin注册) => run => 调用compile函数, 传入onCompiled回调
    
        // 以下为compolie函数示例
    
        class compile extends Tapable {
          createCompilation() {
            return new Compilation(this); // 实例化Compilation
          }
          newCompilation(params) {
            const compilation = this.createCompilation();
            this.hooks.thisCompilation.call(compilation, params); // thisCompilation钩子
            this.hooks.compilation.call(compilation, params); // thisCompilation钩子
            return compilation;
          }
    
          compile(callback) {
            this.hooks.beforeCompile.callAsync(params, err => {
              this.hooks.compile.call(params);
              const compilation = new newCompilation(params);
              this.hooks.make.callAsync(compilation, err => {
                compilation.finish(err => {
                  compilation.seal(err => {
                    this.hooks.afterCompile.callAsync(compilation, err => {
                      return callback(null, compilation);
                    });
                  });
                });
              });
            });
          }
        }
    
    执行步骤: beforeCompile钩子 => compile钩子 => 实例化compilation(1. 调用thisCompilation => 2. compilation) => make钩子(执行完成后, 模块转化完成)
    => finish => seal封装 => afterCompile钩子(编译结束)
    
        const onCompiled = (err, compilation) => {
          if (this.hooks.shouldEmit.call(compilation) === false) {
            this.hooks.done.callAsync(status, err => {});
            return;
          }
          this.emitAssets(compilation, err => {
            this.hooks.done.callAsync(status, err => {});
            return;
          });
        };
    
    
    // 步骤: shouldEmit钩子 ? 成功调用emitAssets => done钩子, 完成一次输出 : 编译不成功调用done钩子
    
    • compile 总结
      • compile 的调用栈如下: run => compile => onCompiled
      • run 函数中触发的钩子:beforeRun,run
      • compile 函数中触发的钩子:beforeCompile,compile,thisCompilation,compilation,make,afterCompile
      • onCompiled 函数中触发的钩子: should-emit,emit,done

    6. compilation

    • 对象职责: 构建模块和 chunk, 并利用插件优化构建过程
    • 首先来理解 compilation 对象, moduleFactory, module 三者之间的关系吧

    Webpack编译流程详解

    compilation 编译模块的入口

    要从 compiler 的 make 钩子看起,从上面的 compile 的方法内看到,实例化 compilation 对象后,并没有对它做什么操作,而是直接调用了 make 钩子,在钩子挂载的入口相关的插件中,操作了 compilation,我们来看一下:

        class SingleEntryPlugin {
          constructor(context, entry, name) {
            this.context = context;
            this.entry = entry;
            this.name = name;
          }
          apply(compiler) {
            compiler.hooks.compilation.tap(
              "SingleEntryPlugin",
              (compilation, { normalModuleFactory }) => {
                compilation.dependencyFactories.set(
                  SingleEntryDependency,
                  normalModuleFactory
                );
              }
            );
    
            compiler.hooks.make.tapAsync(
              "SingleEntryPlugin",
              (compilation, callback) => {
                const { entry, name, context } = this;
    
                const dep = SingleEntryPlugin.createDependency(entry, name);
                compilation.addEntry(context, dep, name, callback);
              }
            );
          }
    
          static createDependency(entry, name) {
            const dep = new SingleEntryDependency(entry);
            dep.loc = { name };
            return dep;
          }
        }
    
    

    这里使用 SingleEntryPlugin 作为例子,配置单入口时会使用此插件,插件往 make 钩子会挂载了回调函数。它不但挂载了 make 钩子,还挂载了 compilation 钩子,这个钩子先于 make 钩子调用,为 compilation 对象的 dependencyFactories 中添加了值,这值是一个 key-value 对,key 是 SingleEntryDependency,值是 normalModuleFactory,normalModuleFactory 就是一种 modulefactory,我们后面在构建模块中用到。

    compilation 编译步骤如下:

    执行 make 钩子 => 执行 compilation 下的 addEntry 方法 => _addModuleChain (根据依赖, 递归所有模块, 拿到所有的模块链) => compilation.seal 方法, seal 方法下调用各种钩子, 生成 chunk 对象 => 控制权给到 compile, 执行回调 onCompiled => 调用 done 钩子, 一次构建完成

    7. 修改代码后再次编译

    当我们修改业务代码再次编译时, webpack-cli 会再次调用 compile.run(), 此时不会再次调用 webpack 函数

    Webpack编译流程详解


    起源地下载网 » Webpack编译流程详解

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元