webpack插件

plugin

插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。创建插件比创建 loader 更加高级,因为你将需要理解一些 webpack 底层的内部特性来做相应的钩子

为什么需要一个插件

  • webpack 基础配置无法满足需求
  • 插件几乎能够任意更改 webpack 编译结果
  • webpack 内部也是通过大量内部插件实现的

可以加载插件的常用对象

对象钩子
Compilerrun,compile,compilation,make,emit,done
CompilationbuildModule,normalModuleLoader,succeedModule,finishModules,seal,optimize,after-seal
Module FactorybeforeResolver,afterResolver,module,parser
Module
Parserprogram,statement,call,expression
Templatehash,bootstrap,localVars,render

创建插件

  • 插件是一个类
  • 类上有一个apply的实例方法
  • apply的参数是compiler
class DonePlugin {
    constructor(options) {
        this.options = options;
    }
    apply(compiler) {

    }
}
module.exports = DonePlugin;

Compiler 和 Compilation

在插件开发中最重要的两个资源就是compilercompilation对象。理解它们的角色是扩展 webpack 引擎重要的第一步。

  • compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
  • compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。

基本插件架构

  • 插件是由「具有 apply 方法的 prototype 对象」所实例化出来的
  • 这个 apply 方法在安装插件时,会被 webpack compiler 调用一次
  • apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象

使用插件代码

  • [使用插件]https://github.com/webpack/webpack/blob/master/lib/webpack.js#L60-L69)
if (options.plugins && Array.isArray(options.plugins)) {
  for (const plugin of options.plugins) {
    plugin.apply(compiler);
  }
}

Compiler 插件

同步

class DonePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    compiler.hooks.done.tap("DonePlugin", (stats) => {
      console.log("Hello ", this.options.name);
    });
  }
}
module.exports = DonePlugin;

异步

class DonePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    compiler.hooks.done.tapAsync("DonePlugin", (stats, callback) => {
      console.log("Hello ", this.options.name);
      callback();
    });
  }
}
module.exports = DonePlugin;

使用插件

  • 要安装这个插件,只需要在你的 webpack 配置的 plugin 数组中添加一个实例
const DonePlugin = require("./plugins/DonePlugin");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve("build"),
    filename: "bundle.js",
  },
  plugins: [new DonePlugin({ name: "hs" })],
};

compilation 插件

  • 使用 compiler 对象时,你可以绑定提供了编译 compilation 引用的回调函数,然后拿到每次新的 compilation 对象。这些 compilation 对象提供了一些钩子函数,来钩入到构建流程的很多步骤中

webpack-assets-plugin.js

plugins\webpack-assets-plugin.js

//编写个Compilation插件,用来打印本次产出的代码块和文件
class WebpackAssetsPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    //每当webpack开启一次新的编译 ,就会创建一个新的compilation
    compiler.hooks.compilation.tap('WebpackAssetsPlugin', (compilation) => {
      //每次根据chunk创建一个新的文件后会触发一次chunkAsset
      compilation.hooks.chunkAsset.tap('WebpackAssetsPlugin', (chunk, filename) => {
        console.log(chunk.name || chunk.id, filename);
      });
    });
  }
}
module.exports = WebpackAssetsPlugin;

打包 zip

webpack-archive-plugin.js

plugins\webpack-archive-plugin.js

const jszip = require('jszip');
const { RawSource } = require('webpack-sources');
const { Compilation } = require('webpack');
/**
 * 1.如何获取打出后的文件名和文件内容
 * 2.如何打压缩包 
 * 3.如何向目标目录输出压缩包
 */
class WebpackArchivePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('WebpackAssetsPlugin', (compilation) => {
      //当确定好文件,当你处理每个资源的时候处执行
      compilation.hooks.processAssets.tapPromise({ name: 'WebpackArchivePlugin' }, (assets) => {
        const zip = new jszip();
        for (const filename in assets) {
          const sourceObj = assets[filename];
          const sourceCode = sourceObj.source();
          zip.file(filename, sourceCode);
        }
        return zip.generateAsync({ type: 'nodebuffer' }).then(zipContent => {
          assets[`archive_${Date.now()}。zip`] = new RawSource(zipContent);
        });
      });
    });
  }
}
module.exports = WebpackArchivePlugin;

webpack.config.js

webpack.config.js

const WebpackArchivePlugin = require('./plugins/webpack-archive-plugin');
  plugins: [
+   new WebpackArchivePlugin({
+     filename:'[timestamp].zip'
+   })
]

自动外链

自动外链

使用外部类库

  • 手动指定 external
  • 手动引入 script

能否检测代码中的 import 自动处理这个步骤?

{
  externals:{
    //key jquery是要require或import 的模块名,值 jQuery是一个全局变量名
  'jquery':'$'
}, 
  module:{}
}

思路

  • 解决 import 自动处理externalscript的问题,需要怎么实现,该从哪方面开始考虑
  • 依赖 当检测到有importlibrary时,将其设置为不打包类似exteral,并在指定模版中加入 script,那么如何检测 import?这里就用Parser
  • external依赖 需要了解 external 是如何实现的,webpack 的 external 是通过插件ExternalsPlugin实现的,ExternalsPlugin 通过tap NormalModuleFactory 在每次创建 Module 的时候判断是否是ExternalModule
  • webpack4 加入了模块类型之后,Parser获取需要指定类型 moduleType,一般使用javascript/auto即可

使用 plugins

plugins: [
  new HtmlWebpackPlugin({
            template:'./src/index.html'
  }),
  new AutoExternalPlugin({
      jquery:{//自动把jquery模块变成一个外部依赖模块
          variable:'jQuery',//不再打包,而是从window.jQuery变量上获取jquery对象
          url:'https://cdn.bootcss.com/jquery/3.1.0/jquery.js'//CDN脚本
      },
      lodash:{//自动把jquery模块变成一个外部依赖模块
          variable:'_',//不再打包,而是从window.jQuery变量上获取jquery对象
          url:'https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.js'//CDN脚本
      }
  })
];

AutoExternalPlugin

AsyncSeriesBailHook factorize

let { AsyncSeriesBailHook } = require("tapable");
let factorize = new AsyncSeriesBailHook(['resolveData']);
//最后总得返回一个模块吧 switch case default 
factorize.tapAsync('factory1', (resolveData, callback) => {
  if (resolveData === 'jquery') {
    callback(null, {
      id: resolveData,
      type: '外部模块',
      source: 'window.jQuery'
    });
  } else {
    callback(null);
  }
});
//生成正常模块的工厂函数最后一个工厂函数了,
factorize.tapAsync('factory2', (resolveData, callback) => {
  callback(null, { id: resolveData, type: '正常模块', source: 'webpack打包后的内容' });
});

factorize.callAsync('jquery', (err, module) => {
  console.log(module);
});
factorize.callAsync('lodash', (err, module) => {
  console.log(module);
});

plugins\auto-external-plugin.js

const { ExternalModule } = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
/**
 * 1.需要向输出html文件中添加CDN脚本引用
 * 2.在打包生产模块的时候,截断正常的打包逻辑,变成外部依赖模块
 */
class AutoExternalPlugin{
    constructor(options){
        this.options = options;
        this.externalModules = Object.keys(this.options);//['jquery'] 进行自动外键的模块
        this.importedModules = new Set();//存放着所有的实际真正使用到的外部依赖模块
    }
    apply(compiler){
        //每种模块会对应一个模块工厂 普通模块对应的就是普通模块工厂
        //https://webpack.docschina.org/api/normalmodulefactory-hooks/
        compiler.hooks.normalModuleFactory.tap('AutoExternalPlugin',(normalModuleFactory)=>{
            //https://webpack.docschina.org/api/parser/#root
            normalModuleFactory.hooks.parser
            .for('javascript/auto')//普通 的JS文件对应的钩子就是'javascript/auto'
            .tap('AutoExternalPlugin',parser=>{
                //在parser遍历语法的过程中,如果遍历到了import节点
                //https://webpack.docschina.org/api/parser/#import
                parser.hooks.import.tap('AutoExternalPlugin',(statement,source)=>{
                    if(this.externalModules.includes(source)){
                        this.importedModules.add(source);
                    }
                });
                //https://webpack.docschina.org/api/parser/#call
                //call=HookMap key方法名 值是这个方法对应的钩子
                parser.hooks.call.for('require').tap('AutoExternalPlugin',(expression)=>{
                    let value = expression.arguments[0].value;
                    if(this.externalModules.includes(value)){
                        this.importedModules.add(value);
                    }
                });
            })
            //2.改造模块的生产过程,如果是外链模块,就直接生产一个外链模块返回
            //https://webpack.docschina.org/api/normalmodulefactory-hooks/
            normalModuleFactory.hooks.factorize.tapAsync('AutoExternalPlugin',(resolveData,callback)=>{
                let {request} = resolveData;//模块名 jquery lodash
                if(this.externalModules.includes(request)){
                    let {variable} = this.options[request];
                    //request=jquery window.jQuery
                    callback(null,new ExternalModule(variable,'window',request));
                }else{
                    callback(null);//如果是正常模块,直接向后执行。走正常的打包模块的流程
                }
            });
        });
        //是往输出的HTML中添加一个新的CDN Script标签
        compiler.hooks.compilation.tap('AutoExternalPlugin',(compilation)=>{
            HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('AutoExternalPlugin',(htmlData,callback)=>{
                //console.log(JSON.stringify(htmlData,null,2));
                Reflect.ownKeys(this.options).filter(key=>this.importedModules.has(key)).forEach(key=>{
                    //jquery
                    htmlData.assetTags.scripts.unshift({
                        tagName:'script',
                        voidTag:false,
                        attributes:{
                            defer:false,
                            src:this.options[key].url
                        }
                    });
                })
                callback(null,htmlData);
            });
        });

    }
}
module.exports = AutoExternalPlugin;
/**
 * Node {
  type: 'ImportDeclaration',
  start: 0,
  end: 23,
  loc: SourceLocation {
    start: Position { line: 1, column: 0 },
    end: Position { line: 1, column: 23 }
  },
  range: [ 0, 23 ],
  specifiers: [
    Node {
      type: 'ImportDefaultSpecifier',
      start: 7,
      end: 8,
      loc: [SourceLocation],
      range: [Array],
      local: [Node]
    }
  ],
  source: Node {
    type: 'Literal',
    start: 14,
    end: 22,
    loc: SourceLocation { start: [Position], end: [Position] },
    range: [ 14, 22 ],
    value: 'jquery',
    raw: "'jquery'"
  }
}
jquery
 */

AsyncQueue

AsyncQueue

let AsyncQueue = require('webpack/lib/util/AsyncQueue');
let AsyncQueue = require('./AsyncQueue');
function processor(item, callback) {
    setTimeout(() => {
        console.log('process',item);
        callback(null, item);
    }, 3000);
}
const getKey = (item) => {
    return item.key;
}
let queue  = new AsyncQueue({
    name:'createModule',parallelism:3,processor,getKey
});
const start = Date.now();
let item1 = {key:'module1'};
queue.add(item1,(err,result)=>{
    console.log(err,result);
    console.log(Date.now() - start);
});
queue.add(item1,(err,result)=>{
    console.log(err,result);
    console.log(Date.now() - start);
});
queue.add({key:'module2'},(err,result)=>{
    console.log(err,result);
    console.log(Date.now() - start);
});
queue.add({key:'module3'},(err,result)=>{
    console.log(err,result);
    console.log(Date.now() - start);
});
queue.add({key:'module4'},(err,result)=>{
    console.log(err,result);
    console.log(Date.now() - start);
});

use.js

use.js

const QUEUED_STATE = 0;//已经 入队,待执行
const PROCESSING_STATE = 1;//处理中
const DONE_STATE = 2;//处理完成
class ArrayQueue {
    constructor() {
        this._list = [];
    }
    enqueue(item) {
        this._list.push(item);//[1,2,3]
    }
    dequeue() {
        return this._list.shift();//移除并返回数组中的第一个元素
    }
}
class AsyncQueueEntry {
    constructor(item, callback) {
        this.item = item;//任务的描述
        this.state = QUEUED_STATE;//这个条目当前的状态
        this.callback = callback;//任务完成的回调
    }
}
class AsyncQueue {
    constructor({ name, parallelism, processor, getKey }) {
        this._name = name;//队列的名字
        this._parallelism = parallelism;//并发执行的任务数
        this._processor = processor;//针对队列中的每个条目执行什么操作
        this._getKey = getKey;//函数,返回一个key用来唯一标识每个元素
        this._entries = new Map();
        this._queued = new ArrayQueue();//将要执行的任务数组队列 
        this._activeTasks = 0;//当前正在执行的数,默认值1
        this._willEnsureProcessing = false;//是否将要开始处理
    }
    add = (item, callback) => {
        const key = this._getKey(item);//获取这个条目对应的key
        const entry = this._entries.get(key);//获取 这个key对应的老的条目
        if (entry !== undefined) {
            if (entry.state === DONE_STATE) {
                process.nextTick(() => callback(entry.error, entry.result));
            } else if (entry.callbacks === undefined) {
                entry.callbacks = [callback];
            } else {
                entry.callbacks.push(callback);
            }
            return;
        }
        const newEntry = new AsyncQueueEntry(item, callback);//创建一个新的条目
        this._entries.set(key, newEntry);//放到_entries
        this._queued.enqueue(newEntry);//把这个新条目放放队列
        if (this._willEnsureProcessing === false) {
            this._willEnsureProcessing = true;
            setImmediate(this._ensureProcessing);
        }
    }
    _ensureProcessing = () => {
        //如果当前的激活的或者 说正在执行任务数行小于并发数
        while (this._activeTasks < this._parallelism) {
            const entry = this._queued.dequeue();//出队 先入先出
            if (entry === undefined) break;
            this._activeTasks++;//先让正在执行的任务数++
            entry.state = PROCESSING_STATE;//条目的状态设置为执行中
            this._startProcessing(entry);
        }
        this._willEnsureProcessing = false;
    }
    _startProcessing = (entry) => {
        this._processor(entry.item, (e, r) => {
            this._handleResult(entry, e, r);
        });
    }
    _handleResult = (entry, error, result) => {
        const callback = entry.callback;
        const callbacks = entry.callbacks;
        entry.state = DONE_STATE;//把条目的状态设置为已经完成
        entry.callback = undefined;//把callback
        entry.callbacks = undefined;
        entry.result = result;//把结果赋给entry
        entry.error = error;//把错误对象赋给entry
        callback(error, result);
        if (callbacks !== undefined) {
            for (const callback of callbacks) {
                callback(error, result);
            }
        }
        this._activeTasks--;
        if (this._willEnsureProcessing === false) {
            this._willEnsureProcessing = true;
            setImmediate(this._ensureProcessing);
        }
    }
}
module.exports = AsyncQueue;

参考

钩子集合

收集

        Object.keys(this.hooks).forEach(hookName => {
            const hook = this.hooks[hookName];
            if (hook instanceof HookMap) {
                for (let key of hook._map.keys()) {
                    hook.for(key).tap('flow', () => {
                        console.log(`|JavascriptParser|${hookName}|${hook.for(key).constructor.name}|${hook._args}|`);
                    });
                }
            } else {
                hook.tap('flow', () => {
                    console.log(`|JavascriptParser|${hookName}|${hook.constructor.name}|${hook._args}|`);
                });
            }
        });

触发时机

对象钩子名称类型参数含义
CompilerenvironmentSyncHook在编译器准备环境时调用,时机就在配置文件中初始化插件之后
CompilerafterEnvironmentSyncHook
CompilerentryOptionSyncBailHookcontext,entry
CompilerafterPluginsSyncHookcompiler
CompilerafterResolversSyncHookcompiler
CompilerinitializeSyncHook
CompilerbeforeRunAsyncSeriesHookcompiler
CompilerrunAsyncSeriesHookcompiler
CompilerinfrastructureLogSyncBailHookorigin,type,args
CompilerreadRecordsAsyncSeriesHook
CompilernormalModuleFactorySyncHooknormalModuleFactory
CompilercontextModuleFactorySyncHookcontextModuleFactory
CompilerbeforeCompileAsyncSeriesHookparams
CompilercompileSyncHookparams
CompilerthisCompilationSyncHookcompilation,params
CompilercompilationSyncHookcompilation,params
CompilermakeAsyncParallelHookcompilation
CompilationaddEntrySyncHookentry,options
NormalModuleFactorybeforeResolveAsyncSeriesBailHookresolveData
NormalModuleFactoryfactorizeAsyncSeriesBailHookresolveData
NormalModuleFactoryresolveAsyncSeriesBailHookresolveData
NormalModuleFactoryafterResolveAsyncSeriesBailHookresolveData
NormalModuleFactorycreateModuleAsyncSeriesBailHookcreateData,resolveData
NormalModuleFactorymoduleSyncWaterfallHookmodule,createData,resolveData
CompilationbuildModuleSyncHookmodule
CompilationnormalModuleLoaderSyncHookloaderContext,module
JavascriptParserprogramSyncBailHookast,comments
JavascriptParserpreStatementSyncBailHookstatement
JavascriptParserpreStatementSyncBailHookstatement
JavascriptParserblockPreStatementSyncBailHookdeclaration
JavascriptParserpreDeclaratorSyncBailHookdeclarator,statement
JavascriptParserblockPreStatementSyncBailHookdeclaration
JavascriptParserstatementSyncBailHookstatement
JavascriptParserdeclaratorSyncBailHookdeclarator,statement
JavascriptParserstatementSyncBailHookstatement
JavascriptParserfinishSyncBailHookast,comments
CompilationsucceedModuleSyncHookmodule
NormalModuleFactorybeforeResolveAsyncSeriesBailHookresolveData
NormalModuleFactoryfactorizeAsyncSeriesBailHookresolveData
NormalModuleFactoryresolveAsyncSeriesBailHookresolveData
NormalModuleFactoryafterResolveAsyncSeriesBailHookresolveData
NormalModuleFactorycreateModuleAsyncSeriesBailHookcreateData,resolveData
NormalModuleFactorymoduleSyncWaterfallHookmodule,createData,resolveData
CompilationbuildModuleSyncHookmodule
CompilationnormalModuleLoaderSyncHookloaderContext,module
JavascriptParserprogramSyncBailHookast,comments
JavascriptParserpreStatementSyncBailHookstatement
JavascriptParserblockPreStatementSyncBailHookdeclaration
JavascriptParserstatementSyncBailHookstatement
JavascriptParserfinishSyncBailHookast,comments
CompilationsucceedModuleSyncHookmodule
CompilationsucceedEntrySyncHookentry,options,module
CompilationlogSyncBailHookorigin,logEntry
CompilerfinishMakeAsyncSeriesHookcompilation
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationfinishModulesAsyncSeriesHookmodules
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationsealSyncHook
CompilationoptimizeDependenciesSyncBailHookmodules
CompilationlogSyncBailHookorigin,logEntry
CompilationafterOptimizeDependenciesSyncHookmodules
CompilationlogSyncBailHookorigin,logEntry
CompilationbeforeChunksSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationafterChunksSyncHookchunks
CompilationlogSyncBailHookorigin,logEntry
CompilationoptimizeSyncHook
CompilationoptimizeModulesSyncBailHookmodules
CompilationafterOptimizeModulesSyncHookmodules
CompilationoptimizeChunksSyncBailHookchunks,chunkGroups
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationafterOptimizeChunksSyncHookchunks,chunkGroups
CompilationoptimizeTreeAsyncSeriesHookchunks,modules
CompilationafterOptimizeTreeSyncHookchunks,modules
CompilationoptimizeChunkModulesAsyncSeriesBailHookchunks,modules
CompilationafterOptimizeChunkModulesSyncHookchunks,modules
CompilationshouldRecordSyncBailHook
CompilationreviveModulesSyncHookmodules,records
CompilationbeforeModuleIdsSyncHookmodules
CompilationmoduleIdsSyncHookmodules
CompilationoptimizeModuleIdsSyncHookmodules
CompilationafterOptimizeModuleIdsSyncHookmodules
CompilationreviveChunksSyncHookchunks,records
CompilationbeforeChunkIdsSyncHookchunks
CompilationchunkIdsSyncHookchunks
CompilationoptimizeChunkIdsSyncHookchunks
CompilationafterOptimizeChunkIdsSyncHookchunks
CompilationlogSyncBailHookorigin,logEntry
CompilationrecordModulesSyncHookmodules,records
CompilationrecordChunksSyncHookchunks,records
CompilationoptimizeCodeGenerationSyncHookmodules
CompilationlogSyncBailHookorigin,logEntry
CompilationbeforeModuleHashSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationafterModuleHashSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationbeforeCodeGenerationSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationafterCodeGenerationSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationbeforeRuntimeRequirementsSyncHook
CompilationadditionalModuleRuntimeRequirementsSyncHookmodule,runtimeRequirements,context
CompilationadditionalModuleRuntimeRequirementsSyncHookmodule,runtimeRequirements,context
CompilationlogSyncBailHookorigin,logEntry
CompilationadditionalChunkRuntimeRequirementsSyncHookchunk,runtimeRequirements,context
CompilationlogSyncBailHookorigin,logEntry
CompilationadditionalTreeRuntimeRequirementsSyncHookchunk,runtimeRequirements,context
CompilationlogSyncBailHookorigin,logEntry
CompilationafterRuntimeRequirementsSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationbeforeHashSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationchunkHashSyncHookchunk,chunkHash,ChunkHashContext
CompilationcontentHashSyncHookchunk
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationfullHashSyncHookhash
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationafterHashSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationrecordHashSyncHookrecords
CompilationlogSyncBailHookorigin,logEntry
CompilationbeforeModuleAssetsSyncHook
CompilationlogSyncBailHookorigin,logEntry
CompilationshouldGenerateChunkAssetsSyncBailHook
CompilationbeforeChunkAssetsSyncHook
CompilationrenderManifestSyncWaterfallHookresult,options
CompilationassetPathSyncWaterfallHookpath,options,assetInfo
CompilationchunkAssetSyncHookchunk,filename
CompilationlogSyncBailHookorigin,logEntry
CompilationadditionalChunkAssetsObjectundefined
CompilationadditionalAssetsObjectundefined
CompilationoptimizeAssetsAsyncSeriesHookassets
CompilationprocessAssetsAsyncSeriesHookassets
CompilationoptimizeChunkAssetsObjectundefined
CompilationafterOptimizeChunkAssetsObjectundefined
CompilationafterOptimizeAssetsSyncHookassets
CompilationafterProcessAssetsSyncHookassets
CompilationlogSyncBailHookorigin,logEntry
CompilationrecordSyncHookcompilation,records
CompilationneedAdditionalSealSyncBailHook
CompilationafterSealAsyncSeriesHook
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilerafterCompileAsyncSeriesHookcompilation
CompilationlogSyncBailHookorigin,logEntry
CompilershouldEmitSyncBailHookcompilation
CompileremitAsyncSeriesHookcompilation
CompilationassetPathSyncWaterfallHookpath,options,assetInfo
CompilerassetEmittedAsyncSeriesHookfile,info
CompilerafterEmitAsyncSeriesHookcompilation
CompilationlogSyncBailHookorigin,logEntry
CompilationneedAdditionalPassSyncBailHook
CompileremitRecordsAsyncSeriesHook
CompilationlogSyncBailHookorigin,logEntry
CompilerdoneAsyncSeriesHookstats
CompilationlogSyncBailHookorigin,logEntry
CompilationlogSyncBailHookorigin,logEntry
CompilershutdownAsyncSeriesHook
CompilationstatsNormalizeSyncHookoptions,context
CompilationstatsFactorySyncHookstatsFactory,options
CompilationstatsPrinterSyncHookstatsPrinter,options
CompilationprocessErrorsSyncWaterfallHookerrors
CompilationprocessWarningsSyncWaterfallHookwarnings
CompilerafterDoneSyncHookstats
CompilerinfrastructureLogSyncBailHookorigin,type,args

工作流

初始化阶段
事件名解释代码位置
读取命令行参数从命令行中读取用户输入的参数require(“./convert-argv”)(argv)
实例化 Compiler1.用上一步得到的参数初始化 Compiler 实例 2.Compiler 负责文件监听和启动编译 3.Compiler 实例中包含了完整的 Webpack 配置,全局只有一个 Compiler 实例。compiler = webpack(options);
加载插件1.依次调用插件的 apply 方法,让插件可以监听后续的所有事件节点。 同时给插件传入 compiler 实例的引用,以方便插件通过 compiler 调用 Webpack 提供的 API。plugin.apply(compiler)
处理入口读取配置的 Entrys,为每个 Entry 实例化一个对应的 EntryPlugin,为后面该 Entry 的递归解析工作做准备new EntryOptionPlugin().apply(compiler) new SingleEntryPlugin(context, item, name) compiler.hooks.make.tapAsync
编译阶段
事件名解释代码位置
run启动一次新的编译this.hooks.run.callAsync
compile该事件是为了告诉插件一次新的编译将要启动,同时会给插件传入compiler 对象。compile(callback)
compilation当 Webpack 以开发模式运行时,每当检测到文件变化,一次新的 Compilation 将被创建。 一个 Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。 Compilation 对象也提供了很多事件回调供插件做扩展。newCompilation(params)
make一个新的 Compilation 创建完毕主开始编译this.hooks.make.callAsync
addEntry即将从 Entry 开始读取文件compilation.addEntry this._addModuleChain
moduleFactory创建模块工厂const moduleFactory = this.dependencyFactories.get(Dep)
create创建模块moduleFactory.create
factory开始创建模块factory(result, (err, module) resolver(result this.hooks.resolver.tap(“NormalModuleFactory”
resolveRequestArray解析loader路径resolveRequestArray
resolve解析资源文件路径resolve
userRequest得到包括loader在内的资源文件的绝对路径用!拼起来的字符串userRequest
ruleSet.exec它可以根据模块路径名,匹配出模块所需的loaderthis.ruleSet.exec
_run它可以根据模块路径名,匹配出模块所需的loader_run
loaders得到所有的loader数组[results0].concat(loaders, results[1], results[2])
getParser获取AST解析器this.getParser(type, settings.parser)
buildModule开始编译模块this.buildModule(module buildModule(module, optional, origin, dependencies, thisCallback)
build开始真正编译入口模块build(options
doBuild开始真正编译入口模块doBuild
执行loader使用loader进行转换runLoaders runLoaders
iteratePitchingLoaders开始递归执行pitch loaderiteratePitchingLoaders
loadLoader加载loaderloadLoader
runSyncOrAsync执行pitchLoaderrunSyncOrAsync
processResource开始处理资源processResource options.readResource iterateNormalLoaders iterateNormalLoaders
createSource创建源代码对象this.createSource
parse使用parser转换抽象语法树this.parser.parse
parse解析抽象语法树parse(source, initialState)
acorn.parse解析语法树acorn.parse(code, parserOptions)
ImportDependency遍历并添加添加依赖parser.state.module.addDependency(clearDep)
succeedModule生成语法树后就表示一个模块编译完成this.hooks.succeedModule.call(module)
processModuleDependencies递归编译依赖的模块this.processModuleDependencies(module processModuleDependencies(module, callback) this.addModuleDependencies buildModule
make后结束makethis.hooks.make.callAsync(compilation, err => {}
finish编译完成compilation.finish();
结束阶段
事件名解释代码位置
seal封装compilation.seal seal(callback)
addChunk生成资源addChunk(name)
createChunkAssets创建资源this.createChunkAssets()
getRenderManifest获得要渲染的描述文件getRenderManifest(options)
render渲染源码source = fileManifest.render();
afterCompile编译结束this.hooks.afterCompile
shouldEmit所有需要输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要。this.hooks.shouldEmit
emit确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。this.emitAssets(compilation this.hooks.emit.callAsync const emitFiles = err this.outputFileSystem.writeFile
this.emitRecords写入记录this.emitRecords
done全部完成this.hooks.done.callAsync
  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编写 Webpack 插件的步骤如下: 1. 创建一个 Node.js 模块,其中包含一个插件类。这个插件类需要实现 `apply` 方法,该方法会在 Webpack 打包过程中被调用。 2. 在 `apply` 方法中注册一个或多个 Webpack 生命周期钩子。Webpack 生命周期钩子是 Webpack 打包过程中的不同阶段,其中包括 `compilation`、`emit`、`done` 等。 3. 在钩子中编写插件的逻辑。例如,在 `compilation` 钩子中可以访问 Webpack 的编译对象和编译器,可以修改编译对象和编译器的行为。 4. 在插件类中定义一个可选的选项对象,用于配置插件的行为。这些选项可以通过 Webpack 配置文件中的插件选项进行传递。 下面是一个简单的 Webpack 插件的示例代码: ```javascript class HelloWorldPlugin { constructor(options) { this.options = options; } apply(compiler) { compiler.hooks.done.tap('HelloWorldPlugin', () => { console.log(`Hello, ${this.options.name}!`); }); } } module.exports = HelloWorldPlugin; ``` 在这个示例中,我们定义了一个 `HelloWorldPlugin` 类,它接受一个选项对象作为参数。在插件的 `apply` 方法中,我们注册了 `done` 钩子,并在钩子中输出一条简单的消息。 要使用这个插件,我们可以在 Webpack 配置文件中进行如下配置: ```javascript const HelloWorldPlugin = require('./HelloWorldPlugin'); module.exports = { // ... plugins: [ new HelloWorldPlugin({ name: 'World' }) ] }; ``` 在这个配置中,我们将 `HelloWorldPlugin` 插件实例传递给了 Webpack 的 `plugins` 选项。在运行 Webpack 时,插件会在 `done` 钩子中输出一条消息,例如: ``` Hello, World! ``` 除了 `done` 钩子,Webpack 还提供了很多其他的钩子,可以用于在不同的打包阶段进行操作。通过注册这些钩子,我们可以编写各种不同的 Webpack 插件,以满足不同的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值