webpack源码剖析之四:plugin

媒介

插件plugin,webpack主要的组成部分。它以事宜流的体式格局让用户能够直接接触到webpack的全部编译历程。plugin在编译的症结处所触发对应的事宜,极大的增强了webpack的扩展性。它的涌现让webpack从一个面向历程的打包东西,变成了一套完全的打包生态系统。

功用分析

Tapable

既然说到了事宜流,那末就得引见Tapable了,Tapable是webpack内里的一个小型库,它许可你自定义一个事宜,并在触发后访问到触发者的上下文。固然他也支撑异步触发,多个事宜同步,异步触发。本次完成用的是较早的v0.1.9版,详细文档可检察tapable v0.19文档

在webpack内运用,如SingleEntryPlugin中

compiler.plugin("make",function(compilation,callback){
   compilation.addEntry(this.context, new SingleEntryDependency({request: this.entry}), this.name, callback);
})

在compiler内部触发。

 this.applyPluginsParallel('make',compilation, err => {
     /* do something */
 })

剖析进口文件时,经由过程EntryOptionPlugin剖析entry范例并实例化SingleEntryPlugin, SingleEntryPlugin在挪用compilation的addEntry函数开启编译。这类观察者形式的设想,解耦了compiler, compilation,并使它们供应的功用越发地道,进而增添扩展性。

流程分别

纵观全部打包历程,能够流程分别为四块。

  1. 初始化
  2. 构建
  3. 封装
  4. 文件写入

模块分别

接入plugin后,webpack对parse,resolve,build,writeSource等功用的大规模重构。
现在拆分模块为

  • Parser模块,担任编译module。
  • Resolver模块,担任对文件途径举行剖析。
  • ModuleFactory模块,担任完成module的实例化。
  • Module模块,担任剖析出modules依靠,chunk依靠。构建出打包后本身module的源码。
  • Template模块,担任供应bundle,chunk模块文件写入的模版。
  • Compilation模块,担任文件编译细节,构建并封装出assets对象供Compiler模块举行文件写入。
  • Compiler模块,担任实例化compilation,bundle文件的写入。监听modules的变化,并重新编译。

中心类关联图

《webpack源码剖析之四:plugin》

功用完成

Parser模块

经由过程exprima将源码剖析为AST树,并拆分statements,以及expression直至Identifier基本模块。

  1. 剖析到CallExpression时触发call事宜。
  2. 剖析到MemberExpression,Identifier时触发expression事宜。
  3. 供应evaluateExpression函数,定阅Literal,ArrayExpression,CallExpression,ConditionalExpression等颗粒化的事宜供evaluateExpression挪用。
 case 'CallExpression':
            //do something
            this.applyPluginsBailResult('call ' + calleeName, expression);
            //do something
            break;
 case 'MemberExpression':
            //do something
            this.applyPluginsBailResult('expression ' + memberName, expression);
            //do something
            break;
 case 'Identifier':
            //do something
            this.applyPluginsBailResult('expression ' + idenName, expression);
               //do something
            break;           
 this.plugin('evaluate Literal', (expr) => {})
 this.plugin('evaluate ArrayExpression', (expr) => {})
 this.plugin('evaluate CallExpression', (expr) => {})
 ...

如须要剖析require(“a”),require.ensure([“b”],function(){})的时刻,注册plugin去定阅”call require”,以及”call require.ensure”,再在回调函数挪用evaluateExpression剖析expression。

Resolver模块

封装在enhanced-resolve库,供应异步剖析文件途径,以及可设置的filestream才能。在webpack用于缓存文件流以及以下三种范例模块的途径剖析。

  • 一般的module模块
  • 带context的module模块
  • loader模块

用法如

ResolverFactory.createResolver(Object.assign({
            fileSystem: compiler.inputFileSystem,
            resolveToContext: true
        }, options.resolve));

详细设置可去检察github文档

ModuleFactory模块

子类有NormalModuleFactory,ContextModuleFactory。经常使用的NormalModuleFactory功用以下

  1. 实例化module之前,挪用Resolver模块剖析出module和preloaders的绝对途径。
  2. 经由过程正则婚配module文件名,婚配出rules内的loaders,并和preloaders兼并。
  3. 实例化module

这里主如果运用async库的parallel函数并行的剖析loaders和module的途径,并整合运转效果。

async.parallel([
                (callback) => {
                    this.requestResolverArray( context, loader, resolver, callback)
                },
                (callback) => {
                    resolver.normal.resolve({}, context, req, function (err, result) {
                        callback(null, result)
                    });
                },
            ], (err, result) => {
                    let loaders = result[0];
                const resource = result[1];
                //do something
            })

async模块是一整套异步编程的处理方案。async官方文档

Module模块

  1. 运转loaders数组内的函数,支撑同步,异步loaders,获得编译前源码。
  2. 源码交由Parser举行剖析,分析出modules依靠和blocks切割文件依靠
  3. 供应替代函数,将源码替代,如require(‘./a’)替代为__webpack_require__(1)

一个编译好的module对象包括modules依靠ModuleDependency和blocks依靠RequireEnsureDependenciesBlock,loaders,源码_source,其数据结构以下:

{
  chunks: [],
  id: null,
  parser: 
   Tapable {
     _plugins: 
      { 'evaluate Literal': [Array],
        'evaluate ArrayExpression': [Array],
        'evaluate CallExpression': [Array],
        'call require': [Array],
        'call require:commonjs:item': [Array],
        'call require.ensure': [Array] },
     options: {},
     scope: { declarations: [] },
     state: { current: [Circular], module: [Circular] },
     _currentPluginApply: undefined },
  fileDependencies: 
   [ '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js' ],
  dependencies: 
   [ ModuleDependency {
       request: './module!d',
       range: [Array],
       class: [Function: ModuleDependency],
       type: 'cms require' },
     ModuleDependency {
       request: './assets/test',
       range: [Array],
       class: [Function: ModuleDependency],
       type: 'cms require' } ],
  blocks: 
   [ RequireEnsureDependenciesBlock {
       blocks: [],
       dependencies: [Array],
       requires: [Array],
       chunkName: '',
       beforeRange: [Array],
       afterRange: [Array] } ],
  loaders: [],
  request: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js',
  fileName: 'a.js',
  requires: [ [ 0, 7 ], [ 23, 30 ] ],
  context: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example',
  built: true,
  _source: 
   RawSource {
     _result: 
      { source: 'require(\'./module!d\');\nrequire(\'./assets/test\');\nrequire.ensure([\'./e\',\'./b\'], function () {\n    console.log(1)\n    console.log(1)\n    console.log(1)\n    console.log(1)\n    require(\'./m\');\n    require(\'./e\');\n});\n' },
     _source: 'require(\'./module!d\');\nrequire(\'./assets/test\');\nrequire.ensure([\'./e\',\'./b\'], function () {\n    console.log(1)\n    console.log(1)\n    console.log(1)\n    console.log(1)\n    require(\'./m\');\n    require(\'./e\');\n});\n' 
             } 
     }

Compilation模块

  1. 经由过程entry和context,猎取到进口module对象,并建立进口chunk。
  2. 经由过程module的modules依靠和blocks切割文件构建出含有chunk和modules包括关联的chunk对象。
  3. 给modules和chunks的排序并天生id,触发一系列optimize相干的事宜(如CommonsChunkPlugin就是运用optimize-chunks事宜举行开辟),终究构建出有文件名和源码映照关联的assets对象

一个典范的含有切割文件的多进口entry的assets对象数据结构以下:

assets: 
   { '0.bundle.js': 
      Chunk {
        name: '',
        parents: [Array],
        modules: [Array],
        id: 0,
        source: [Object] },
     'main.bundle.js': 
      Chunk {
        name: 'main',
        parents: [],
        modules: [Array],
        id: 1,
        entry: true,
        chunks: [Array],
        blocks: true,
        source: [Object] },
     'multiple.bundle.js': 
      Chunk {
        name: 'multiple',
        parents: [],
        modules: [Array],
        id: 2,
        entry: true,
        chunks: [Array],
        source: [Object] } 
  }

Compiler模块

  1. 剖析CLI, webpack设置猎取options对象,初始化resolver,parser对象。
  2. 实例化compilation对象,触发make 并行事宜挪用compilation对象的addEntry开启编译。
  3. 猎取到assets对象,经由过程触发before-emit事宜开启文件写入。经由过程JsonMainTemplate模版完成主进口bundle文件的写入,JsonpChunkTemplate模版完成chunk切割文件的写入。 运用async.forEach治理异步多文件写入的效果。
  4. 监听modules的变化,并重新编译。

考虑到多进口entry的能够,make挪用的是并行异步事宜

this.applyPluginsParallel('make', compilation, err => {
    //do something
    compilation.seal(err=>{})
    //do something
}

代码完成

本人的简易版webpack完成simple-webpack

总结

置信人人都有设想过营业/开源代码,许多状况是越往后写,越难保护。一次次的定制化的需求,将原有的设想改的四分五裂。这个时刻能够尝尝自创webpak的头脑,充足思索并笼统出稳固的基本模块,分别生命周期,将模块之间的营业逻辑,特别需求交由插件去处理。

完。

    原文作者:朱建
    原文地址: https://segmentfault.com/a/1190000015836947
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞