【重学webpack系列——webpack5.0】

 


由于webpack基本配置无法满足开发者需求,所以需要借助plugin对webpack构建流程进行修改,来达到改变或优化webpack编译结果的目的。

1.你有没有开发过插件,你是怎么开发开发插件的,你开发了什么插件?(同一类问题面试点)

  • 一般来说,插件是一个类
  • 类上有一个apply方法,一般我们插件的逻辑就写在这个apply方法内。因为apply方法在安装插件时,会被webpack compiler调用,并且将compiler传给apply方法,也就是参数参数是compiler。
  • 由于webpack的事件流是由tapable前后贯穿的,所以webpack在内部提供了很多钩子,我们在apply方法中去注册特定钩子的事件,那么webpack就会在特定的时机来调用这些钩子对应的事件函数。
    • 比如:资源编译结束我需要将文件压缩存档,那么在apply方法中就可以写compiler.hooks.done.tap(‘XXXPlugin’, 压缩存档的回调函数),这个代码的意思就是我在done的这个时机去注册一个事件,tap方法就是注册事件,事件名是第一个参数’xxxPlugin’,事件执行的内容就是压缩存档的回调函数。其实这个done源码中对应的就是tapable的AsyncSeriesHook异步串行钩子,相当于在这个钩子上tap了一个事件。那么等webpack编译完成的时候,就会触发tapable的callAsync函数,这个函数的意思就是异步触发之前done的时候注册的钩子。那么也就是在编译完成的时候回执行压缩存档的回调函数。这就完成了一个插件开发了。
    • 其实,tapable就是一个订阅发布,tap的时候注册一些事件,放到队列中,然后call的时候去一个个触发,只不过tapable在触发的时候根据不同的钩子类型改变了触发的顺序,比如SyncBailHook注册的钩子,触发的时候,一旦有返回值,就不继续执行下个事件函数了。当然源码中也不是直接循环调用这些事件函数的,而是new了一个Function,通过对每个队列构造一个函数,去执行的。
  • 另外,插件有两个重要的对象,一个是compiler,另一个是compilation:
    • compiler,这个webpack编译过程就一个compiler,代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
    • compilation:每次资源构建都会有一个compilation,所以它代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
  • 最后,钩子的使用的话就很简单,在webpack配置文件中,new一个插件这个类,然后传一些配置参数进去就可以了。webpack会自动去调apply方法的。

2.webpack相关问答

1.问:webpack配置文件中,插件的顺序可以随意改吗?

有些可以,有些不可以。

哪些不可以呢?监听的是同一个钩子的,一般不能随意改,因为一旦顺序变换了,钩子一触发,结果也会变化。

如果不是同一个钩子的,一般来说是可以改的。具体也要考虑业务流程。

2.问:你有开发过webpack-plugin吗?

答:有,然后举个例子,要提到apply、compiler的干嘛的、compilation是干嘛的,然后说下例子的思路,思路如下:

开发一个一键式部署的插件:

  1. 一个命令,对应启动webpack的发布插件
  2. 发布插件主要功能包含:
    1. 将代码提交到测试分支/正式分支,打tag,删除分支等git操作
    2. 压缩打包成果物
    3. 将成果物放到指定的SVN目录下
    4. 执行脚本,将成果物在SVN下解压,并且push上去
    5. 通过接口,启动测试环境/正式环境的部署(可根据命令配置)
    6.  
  • 继续问:压缩是怎么实现的?

答:压缩是用jszip库实现的,但是如果让我自己实现的话,也可以。然后把实现原理说一下

  • 继续问:devops相关知识

答:去了解下整个devops流程,,特别是阿里云与华为云的实践

讨论:webpack的差量打包。

 

3.开发的插件举例:

1. 资源文件的产出列表

// 资源文件的产出列表
class AssetPlugin{
  constructor(options){
    this.options = options;
  }
  apply(compiler){ 
    // 这个compiler只有一个,每当监听到文件的变化,就会创建一个新的complication
    // 每当compiler开启一次新的编译,就会创建一个新的compilation,触发一次compilation事件。
    console.log(Object.keys(compiler.hooks))
    compiler.hooks.compilation.tap('AssetPlugin', (compilation) => {
      compilation.hooks.chunkAsset.tap('AssetPlugin', (chunk, filename) => { // chunk代码块对象,filename文件名
        // console.log(chunk, filename)
        console.log(Object.keys(compilation.hooks))
      })
    })

  }
}

module.exports = AssetPlugin;
/*
  compiler 一般用来监听编译的流程,比如开始、结束、emit等
  compilation 一般用来监听编译过程的一些资源,比如chunkAsset等
  那么compiler一般能监听的流程(compiler.hooks.后面的属性)可以通过`Object.keys(compiler.hooks)`来查看
  那么compilation一般能监听的资源(compilation.hooks.后面的属性)可以通过`Object.keys(compilation.hooks)`来查看
  https://webpack.docschina.org/api/compilation-hooks/
  
compiler.hooks下面的属性:  
[ 'initialize',
  'shouldEmit',
  'done',
  'afterDone',
  'additionalPass',
  'beforeRun',
  'run',
  'emit',
  'assetEmitted',
  'afterEmit',
  'thisCompilation',
  'compilation',
  'normalModuleFactory',
  'contextModuleFactory',
  'beforeCompile',
  'compile',
  'make',
  'finishMake',
  'afterCompile',
  'watchRun',
  'failed',
  'invalid',
  'watchClose',
  'shutdown',
  'infrastructureLog',
  'environment',
  'afterEnvironment',
  'afterPlugins',
  'afterResolvers',
  'entryOption' ]
compilation.hooks下面的属性:
[ 'buildModule',
  'rebuildModule',
  'failedModule',
  'succeedModule',
  'stillValidModule',
  'addEntry',
  'failedEntry',
  'succeedEntry',
  'dependencyReferencedExports',
  'executeModule',
  'prepareModuleExecution',
  'finishModules',
  'finishRebuildingModule',
  'unseal',
  'seal',
  'beforeChunks',
  'afterChunks',
  'optimizeDependencies',
  'afterOptimizeDependencies',
  'optimize',
  'optimizeModules',
  'afterOptimizeModules',
  'optimizeChunks',
  'afterOptimizeChunks',
  'optimizeTree',
  'afterOptimizeTree',
  'optimizeChunkModules',
  'afterOptimizeChunkModules',
  'shouldRecord',
  'additionalChunkRuntimeRequirements',
  'runtimeRequirementInChunk',
  'additionalModuleRuntimeRequirements',
  'runtimeRequirementInModule',
  'additionalTreeRuntimeRequirements',
  'runtimeRequirementInTree',
  'runtimeModule',
  'reviveModules',
  'beforeModuleIds',
  'moduleIds',
  'optimizeModuleIds',
  'afterOptimizeModuleIds',
  'reviveChunks',
  'beforeChunkIds',
  'chunkIds',
  'optimizeChunkIds',
  'afterOptimizeChunkIds',
  'recordModules',
  'recordChunks',
  'optimizeCodeGeneration',
  'beforeModuleHash',
  'afterModuleHash',
  'beforeCodeGeneration',
  'afterCodeGeneration',
  'beforeRuntimeRequirements',
  'afterRuntimeRequirements',
  'beforeHash',
  'contentHash',
  'afterHash',
  'recordHash',
  'record',
  'beforeModuleAssets',
  'shouldGenerateChunkAssets',
  'beforeChunkAssets',
  'additionalChunkAssets',
  'additionalAssets',
  'optimizeChunkAssets',
  'afterOptimizeChunkAssets',
  'optimizeAssets',
  'afterOptimizeAssets',
  'processAssets',
  'afterProcessAssets',
  'processAdditionalAssets',
  'needAdditionalSeal',
  'afterSeal',
  'renderManifest',
  'fullHash',
  'chunkHash',
  'moduleAsset',
  'chunkAsset',
  'assetPath',
  'needAdditionalPass',
  'childCompiler',
  'log',
  'processWarnings',
  'processErrors',
  'statsPreset',
  'statsNormalize',
  'statsFactory',
  'statsPrinter',
  'normalModuleLoader' ]


*/

2.将产出文件打包的插件

const Jz = require('jszip');
const { RawSource } = require("webpack-sources");
class JsZip{
  constructor(options){
    this.options = options; // filename是压缩完成后的压缩包的名字,不带后缀
  }
  apply(compiler){
    compiler.hooks.emit.tapAsync('JsPlugin', (compilation, callback) => {
      console.log(compilation.assets)
      var zip = new Jz();
      for (let filename in compilation.assets) {
        let source = compilation.assets[filename].source();
        zip.file(filename, source);
      }
      zip.generateAsync({type:"nodebuffer"})
      .then((content) => {
          compilation.assets[this.options.filename + '.zip'] = new RawSource(content);
          callback();
      });
    })
  }
}

module.exports = JsZip;