webpack|webpack 流程解析(2)(初始化完成)

前言 上文说到 webpack 准备好了参数,要创建 compiler对象了。
创建完之后,则会执行 compiler.run 来开始编译,本文将阐述 new Compilercompiler.run()中间的过程。
整体过程都发生在createCompiler这个函数体内。

/** * @param {WebpackOptions} rawOptions options object * @returns {Compiler} a compiler */ const createCompiler = rawOptions => {

new Compiler
  • 在new之前,webpack会完成一次基础参数的初始化,这里只给日志输出格式和context进行了赋值
    applyWebpackOptionsBaseDefaults(options);

  • webpack/lib/Compiler.js 是整个webpack的编译核心流程。
  • new Compiler 的时候先在Tapable注册了一堆钩子,例如常见的watch-run,run, before-run, 等等。更多的钩子可以在这里查看。
初始化文件操作
new NodeEnvironmentPlugin({ infrastructureLogging: options.infrastructureLogging }).apply(compiler);

这里是在拓展compiler对象,增加对文件的一些操作,例如输入,输出,监听,缓存等方法。
apply(compiler) { const { infrastructureLogging } = this.options; compiler.infrastructureLogger = createConsoleLogger({ level: infrastructureLogging.level || "info", debug: infrastructureLogging.debug || false, console: infrastructureLogging.console || nodeConsole({ colors: infrastructureLogging.colors, appendOnly: infrastructureLogging.appendOnly, stream: infrastructureLogging.stream }) }); compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000); const inputFileSystem = compiler.inputFileSystem; compiler.outputFileSystem = fs; compiler.intermediateFileSystem = fs; compiler.watchFileSystem = new NodeWatchFileSystem( compiler.inputFileSystem ); compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => { if (compiler.inputFileSystem === inputFileSystem) { compiler.fsStartTime = Date.now(); inputFileSystem.purge(); } }); }

这里的fs,不是nodejs的 file system,是用了一个第三方包 graceful-fs
注册插件
if (Array.isArray(options.plugins)) { for (const plugin of options.plugins) { if (typeof plugin === "function") { plugin.call(compiler, compiler); } else { plugin.apply(compiler); } } }

接下来webpack会把options注册的插件,都注册一遍。传入compiler对象给插件内部使用,插件通过 compiler 提供的hook,可以在编译全流程注册钩子的回调函数。同时有些compiler钩子又传入了compilation对象,又可以在资源构建的时候注册 compilation 钩子回调。
如何编写一个webpack插件
environment ready 插件注册完之后,webpack 又一次给options赋值了一次默认参数。为什么和前面的applyWebpackOptionsBaseDefaults一起呢。
这里调用了
applyWebpackOptionsDefaults(options);

又加了一波默认值。
加完之后调用了environmentafterEnvironment两个钩子。
compiler.hooks.environment.call(); compiler.hooks.afterEnvironment.call();

注册内置插件 环境初始化之后,webpack还需要执行一下它自己内部的默认插件。
new WebpackOptionsApply().process(options, compiler);

这里会根据你的配置,执行对应的插件。
挑几个和钩子有关系的讲讲,
解析entry
new EntryOptionPlugin().apply(compiler); compiler.hooks.entryOption.call(options.context, options.entry);

这里就是生成构建所需的entry数据结构
/** @type {EntryOptions} */ const options = { name, filename: desc.filename, runtime: desc.runtime, layer: desc.layer, dependOn: desc.dependOn, publicPath: desc.publicPath, chunkLoading: desc.chunkLoading, wasmLoading: desc.wasmLoading, library: desc.library };

然后再调用EntryPluginapplay方法里注册Compiler.hooks:compilation, make 这两个钩子函数。
注册resloverFactory钩子 webpack本身的一些插件调用完成之后,会调用afterPlugin这个钩子。
compiler.hooks.afterPlugins.call(compiler);

接下来 webpack 在 compiler.resolverFactory 上注册了resolveOptions钩子
compiler.resolverFactory.hooks.resolveOptions .for("normal") .tap("WebpackOptionsApply", resolveOptions => { resolveOptions = cleverMerge(options.resolve, resolveOptions); resolveOptions.fileSystem = compiler.inputFileSystem; return resolveOptions; }); compiler.resolverFactory.hooks.resolveOptions .for("context") .tap("WebpackOptionsApply", resolveOptions => { resolveOptions = cleverMerge(options.resolve, resolveOptions); resolveOptions.fileSystem = compiler.inputFileSystem; resolveOptions.resolveToContext = true; return resolveOptions; }); compiler.resolverFactory.hooks.resolveOptions .for("loader") .tap("WebpackOptionsApply", resolveOptions => { resolveOptions = cleverMerge(options.resolveLoader, resolveOptions); resolveOptions.fileSystem = compiler.inputFileSystem; return resolveOptions; });

这里的目的是为 Factory.createResolver 提供默认的参数对象(含有相关的 resolve 项目配置项)。
然后再调用afterResolvers钩子
compiler.hooks.afterResolvers.call(compiler);

初始化完成 到目前为止,compiler 对象上已经有了足够的东西开始我们的编译,就告诉外界初始化完成,以webpack的调性,必然还有一个钩子的触发。
compiler.hooks.initialize.call();

结语 【webpack|webpack 流程解析(2)(初始化完成)】webpack 编译前的所有事情已经交待清楚,下一篇将开始启动编译。
文章中提到的各种钩子的注册,烦请读者记下来,后续在整个编译过程中,前面注册的一些钩子,经常会在你遗漏的地方触发,这也是调试webpack过程中的一个痛点。

    推荐阅读