引言
webpack
@5.69.0
之前使用taro@3.5.5
创建一个taro-react项目之后build了一个weapp项目
最后一步就是启用webpack
把react
代码编译成weapp
webpack入口
在package.json
中可以找到入口文件
这个文件的作用就是引入webpack核心函数
(lib/webpack.js)以及工具文件,抛出整理好之后的webpack核心函数
"main": "lib/index.js",
最终输出:
// lib/index.js // mergeExports是处理fn的,最后输出的就是结果处理的fn module.exports = mergeExports(fn, { // 很多webpack内置插件 get webpack() { return require("./webpack"); // 核心文件 }, ...... })
mergeExports
// lib/index.js // 第一个参数是对象(函数继承于Fuction,Fuction继承于Object) const mergeExports = (obj, exports) => { // 克隆exports对象,传入这个对象里有很多的文件的引入 const descriptors = Object.getOwnPropertyDescriptors(exports); // 遍历这个对象 for (const name of Object.keys(descriptors)) { const descriptor = descriptors[name]; if (descriptor.get) { const fn = descriptor.get; // 遍历出的属性一个个添加getter到传入的webpack函数 Object.defineProperty(obj, name, { configurable: false, enumerable: true, /** * memoize就是执行了传入的fn返回一个返回执行之后的结果的一个函数 * memoize(fn)等于 * function(){ * return fn(); * } */ get: memoize(fn) }); } else if (typeof descriptor.value === "object") { Object.defineProperty(obj, name, { configurable: false, enumerable: true, writable: false, value: mergeExports({}, descriptor.value) // 递归 }); } else { throw new Error( "Exposed values must be either a getter or an nested object" ); } } // 返回了一个冻结之后的对象,这个对象是个函数,函数里面有很多添加进去的属性方法 return /** @type {A & B} */ (Object.freeze(obj)); };
打印
descriptor
可以得到如下{ get: [Function: get webpack], set: undefined, enumerable: true, configurable: true } ... { value: { ModuleDependency: [Getter], ConstDependency: [Getter], NullDependency: [Getter] }, writable: true, enumerable: true, configurable: true } ...
fn:
// lib/index.js // fn懒加载出来的webpack核心函数 const fn = lazyFunction(() => require("./webpack"));
lazyFunction:这里我们可以知道
webpack核心函数(lib/webpack)
是怎么接受参数的// lib/index.js const lazyFunction = factory => { // 和mergeExports一样返回一个返回执行了factory后的函数 const fac = memoize(factory); // fac函数一个函数 const f = ( // 这里args就是我们传入的webpack配置参数 (...args) => { /** * fac() 等于 factory() * fac()(...args) 等于 factory()(...args) * factory 等于 () => require("./webpack") * require("./webpack")(...args) */ return fac()(...args); } ); return (f);// 最后返回f这个函数,在mergeExports里getter添加之后作为入口抛出,在前端工程化项目中webpack启动器里传入webpack配置参数执行 };
webpack.js
lib/webpack.js
抛出编译器(有callback则执行编译器编译)
webpack
方法 主方法,抛出编译器compiler(有callback则执行编译器编译)options
是一个webpack配置对象或者配置对象数组,回调函数callback
,有callback
就可以在这里直接编译,没有则需要外部调用compiler.run()
在《build一个weapp》的packages/taro-webpack5-runner/src/index.mini.ts中const compiler = webpack(webpackConfig)
没有传callback
只传了webpack配置
,正好在webpack启动器里调用了run、watch、close
方法// lib/webpack.js const webpack = (options, callback) => { // create返回一个对象对象里有编译器、是否监听、监听参数 const create = () => { // options作为数组然后用webpackOptionsSchemaCheck校验参数 if (!asArray(options).every(webpackOptionsSchemaCheck)) { // false ... } let compiler; // MultiCompiler多个编译器或Compiler单个编译器 let watch = false; // 是否热更新监听,只要有一个webpack配置是热更新就监听 let watchOptions; // 热更新监听的参数可以是对象也可以是个对象数组 if (Array.isArray(options)) { compiler = createMultiCompiler(options,options); watch = options.some(options => options.watch); watchOptions = options.map(options => options.watchOptions || {}); } else { const webpackOptions = options; compiler = createCompiler(webpackOptions); watch = webpackOptions.watch; watchOptions = webpackOptions.watchOptions || {}; } return { compiler, watch, watchOptions }; }; if (callback) { try { const { compiler, watch, watchOptions } = create(); if (watch) { // 开始监听 compiler.watch(watchOptions, callback); } else { // 开始编译 compiler.run((err, stats) => { compiler.close(err2 => { callback(err || err2, stats); }); }); } // 抛出编译器 return compiler; } catch (err) { process.nextTick(() => callback(err)); return null; } } else { const { compiler, watch } = create(); if (watch) { util.deprecate( () => {}, "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.", "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK" )(); } // 抛出编译器 return compiler; } };
createCompiler
创建一个编译器// 参数就是webpack配置参数对象 const createCompiler = rawOptions => { // 把webpack配置参数标准化 const options = getNormalizedWebpackOptions(rawOptions); // 基础参数的初始化 applyWebpackOptionsBaseDefaults(options); // 实例化Compiler编译器 const compiler = new Compiler(options.context, options); // 拓展compiler,添加对文件的输入缓存、监听、输出、注册beforeRun钩子 new NodeEnvironmentPlugin({ infrastructureLogging: options.infrastructureLogging }).apply(compiler); // 注册plugin插件this指向编译器compiler if (Array.isArray(options.plugins)) { for (const plugin of options.plugins) { if (typeof plugin === "function") { plugin.call(compiler, compiler); } else { // packages/taro-webpack5-runner/src/plugins/MiniPlugin.ts // 插件入口 TaroMiniPlugin.apply(compiler),这里会调用编译器里的compilation.addEntry、compilation.dependencyFactories plugin.apply(compiler); } } } // 基础参数的初始化 applyWebpackOptionsDefaults(options); // 执行environment钩子和afterEnvironment钩子 compiler.hooks.environment.call(); // 获取环境信息 compiler.hooks.afterEnvironment.call(); // 获取环境信息之后 // 根据不同的环境不同的平台等等注册内置插件,多个hook的tag挂载 new WebpackOptionsApply().process(options, compiler); // 执行initialize钩子 compiler.hooks.initialize.call(); // 初始化 return compiler; };
注:以下是createCompiler
里的相关的函数、类的简单介绍:
applyWebpackOptionsBaseDefaults
基础参数的初始化
对标准webpack配置参数context、infrastructureLogging的初始化const applyWebpackOptionsBaseDefaults = options => { // context设置,如果options.context是undefined,options.context就等于项目路径 F(options, "context", () => process.cwd()); // 日志输出格式设置 applyInfrastructureLoggingDefaults(options.infrastructureLogging); };
applyWebpackOptionsDefaults
基础参数的初始化
对标准webpack配置参数context、target、devtool...
的初始化const applyWebpackOptionsDefaults = options => { ... };
NodeEnvironmentPlugin
拓展compiler,添加对文件的输入缓存、监听、输出、注册beforeRun钩子class NodeEnvironmentPlugin { constructor(options) { this.options = options; // webpack配置参数标准化 } apply(compiler) { // infrastructureLogging:日志输出格式设置 const { infrastructureLogging } = this.options; // 日志打印器ConsoleLogger打印器 compiler.infrastructureLogger = createConsoleLogger({ ... }); // 输入缓存文件操作 compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000); // 输入文件系统 const inputFileSystem = compiler.inputFileSystem; // 输出文件系统,挂载到compiler对象(graceful-fs就是对node原生fs做了一层封装本质还是node的fs) compiler.outputFileSystem = fs; compiler.intermediateFileSystem = fs; // 监听文件系统,挂载到compiler对象 compiler.watchFileSystem = new NodeWatchFileSystem( compiler.inputFileSystem ); // 注册beforeRun钩子 在执行compiler.run前执行 compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => { if (compiler.inputFileSystem === inputFileSystem) { compiler.fsStartTime = Date.now(); inputFileSystem.purge(); // 清除缓存 } }); } }
WebpackOptionsApply
注册内置插件class WebpackOptionsApply extends OptionsApply { constructor() { super(); } process(options, compiler) { // 注册内置插件 ... new ExternalsPlugin(...).apply(compiler); // web 配置外部文件的模块加载 new ChunkPrefetchPreloadPlugin().apply(compiler); new ArrayPushCallbackChunkFormatPlugin().apply(compiler); new EnableChunkLoadingPlugin(type).apply(compiler); new EnableWasmLoadingPlugin(type).apply(compiler); ... // 执行完成注册内置插件钩子 compiler.hooks.afterPlugins.call(compiler); ... compiler.hooks.afterResolvers.call(compiler); } }
[Plugins].apply
以MiniPlugin
为例
node_modules/@tarojs/webpack5-runner/dist/plugins/MiniPlugin.js
上面我们说了在lib/webpack.js
调用了MiniPlugin.apply
,这里就注册多个hook
// 插件入口
apply (compiler: Compiler) {
...
// 配置获取入口文件路径
this.appEntry = this.getAppEntry(compiler)
...
// 注册钩子run, lib/Compiler.js的run中使用也就是在开始编译的时候只用
compiler.hooks.run.tapAsync(
...
)
// 注册钩子watchRun lib/Watching.js里使用用于监听文件改变重新注册run钩子
compiler.hooks.watchRun.tapAsync(
...
)
// 注册钩子make,lib/Compiler.js的compile里使用
compiler.hooks.make.tapAsync(
...
)
// 注册钩子compilation,lib/Compiler.js的new Compilation后执行
compiler.hooks.compilation.tap(
...
)
// 注册钩子afterEmit
compiler.hooks.afterEmit.tapAsync(
...
)
// 实例化TaroNormalModulesPlugin并调用apply
new TaroNormalModulesPlugin(this.options.onParseCreateElement).apply(compiler)
}
Hook的注册和使用
以this.hooks.make.callAsync
为例
AsyncParallelHook异步钩子
AsyncParallelHook
调用钩子
// lib/Compiler.js的compile里 this.hooks.make.callAsync(compilation, () => {...})
实例化钩子
// lib/Compiler.js的constructor里初始化 this.hooks = Object.freeze({ ... // 传入了要执行的钩子compilation,实例化异步钩子AsyncParallelHook make: new AsyncParallelHook(["compilation"]), ... })
AsyncParallelHook
异步钩子// node_modules/tapable/lib/AsyncParallelHook.js function AsyncParallelHook(args = [], name = undefined) { // args = ["compilation"] const hook = new Hook(args, name); // 实例化一个钩子 hook.constructor = AsyncParallelHook; // 重写hook.compile hook.compile = COMPILE; hook._call = undefined; hook.call = undefined; // 返回hook可以在第一步hook.callAsync(compilation, () => {...})执行 return hook; }
hook.callAsync
// node_modules/tapable/lib/Hook.js constructor(args = [], name = undefined) { ... this.callAsync = CALL_ASYNC_DELEGATE; ... }
CALL_ASYNC_DELEGATE
// node_modules/tapable/lib/Hook.js const CALL_ASYNC_DELEGATE = function(...args) { this.callAsync = this._createCall("async"); // this执行Hooks,创建一个异步 return this.callAsync(...args); // 执行异步并返回结果 };
_createCall
// node_modules/tapable/lib/Hook.js _createCall(type) { // 执行this.compile,this.compile在AsyncParallelHook重写为COMPILE return this.compile({ taps: this.taps, interceptors: this.interceptors, args: this._args, type: type // "async" }); }
COMPILE
// node_modules/tapable/lib/AsyncParallelHook.js const factory = new AsyncParallelHookCodeFactory(); // 用于创建函数体 const COMPILE = function(options) { factory.setup(this, options); // 收集函数this指向Hook return factory.create(options); // 创建函数体 };
setup(instance, options) { instance._x = options.taps.map(t => t.fn); }
AsyncParallelHookCodeFactory
基础HookCodeFactory
钩子函数异步创建工厂// node_modules/tapable/lib/HookCodeFactory.js create(options) { ... let fn; // 在5中this._createCall("async"),_createCall里又把"async"作为type传给this.compile // 这里的this.options.type = "async" switch (this.options.type) { ... case "async": fn = new Function( this.args({after: "_callback"}), // 结尾加一个_callback参数 '"use strict";\n' + this.header() + this.contentWithInterceptors({ onError: err => `_callback(${err});\n`, onResult: result => `_callback(null, ${result});\n`, onDone: () => "_callback();\n" }) // 创建一个异步函数体 ); break; ... } return fn }
所创建的函数体
fn
// CALL_ASYNC_DELEGATE执行this.callAsync(...args)也就睡fn(compilation, () => {}) function fn(compilation, _callback) { "use strict"; var _context; var _x = this._x; // 5. COMPILE收集到的fn(this指向Hook) var _taps = this.taps; var _interceptors = this.interceptors; _interceptors[0].call(compilation); var _fn0 = _x[0]; // 收集的第一个fn执行 _fn0(compilation, (function (_err0) { if (_err0) { _callback(_err0); } else { _interceptors[0].done(); // ProgressPlugin进度条完成完成 _callback(); } })); }
options
打印这个就是传入的compilation
,也是执行taps[0].fn(compilation, () => {})
{ taps: [ { type: 'async', fn: [Function (anonymous)], name: 'TaroMiniPlugin' // 从taro项目转入的插件 } ], interceptors: [ { name: 'ProgressPlugin', // 进度条 call: [Function: call], done: [Function: done] } ], args: [ 'compilation' ], type: 'async' }
apply
taps怎么来,MiniPlugin的apply
里调用compiler.hooks.make.tapAsync
钩子生成tags
,apply
在createCompiler
(lib/webpack.js)里调用的//node_modules/@tarojs/webpack5-runner/dist/plugins/MiniPlugin.js apply (compiler: Compiler) { ... compiler.hooks.make.tapAsync( // tapAsync PLUGIN_NAME, this.tryAsync<Compilation>(async compilation => { const dependencies = this.dependencies const promises: Promise<null>[] = [] this.compileIndependentPages(compiler, compilation, dependencies, promises) dependencies.forEach(dep => { promises.push(new Promise<null>((resolve, reject) => { compilation.addEntry(this.options.sourceDir, dep, { name: dep.name, ...dep.options }, err => err ? reject(err) : resolve(null)) })) }) await Promise.all(promises) await this.options.onCompilerMake?.(compilation) }) ) ... }
Hooks.tapAsync
注册钩子
调用tapAsync
又回到node_modules/tapable/lib/Hook.jsnode_modules/tapable/lib/Hook.js // 传入的参数options, fn,options = 'TaroMiniPlugin',fn就是上面那个this.tryAsync(...)的结果,结果也是tags里的fn tapAsync(options, fn) { this._tap("async", options, fn); }
// node_modules/tapable/lib/Hook.js _tap(type, options, fn) { if (typeof options === "string") { options = { name: options.trim() //添加name }; } ... if (typeof options.context !== "undefined") { deprecateContext(); } options = Object.assign({ type, fn }, options);// 添加fn,type以及options里的其他值,fn这里就是我们最终的tags options = this._runRegisterInterceptors(options); this._insert(options);// 把钩子注册到taps }
// 注册成这样 [ { type: 'async', fn: [Function (anonymous)], name: 'TaroMiniPlugin' } ]
总结:
(1)就是通过tapAsync、tap、tapPromise
注册钩子、
(2)通过callAsync、call、callPromise
执行钩子
(3)执行注册的fn
就是this.tryAsync(...)(compilation, () => {})
Compiler
lib/Compiler.js将编译好的文件输出webpack
的主要引擎,在compiler
对象记录了完整的webpack
环境信息,在webpack
从启动到结束,compiler
只会生成一次。你可以在compiler
对象上读取到webpack config
信息,outputPath
等
class Compiler {
constructor(context, options = ({})) {
super();
this.hooks = Object.freeze({
initialize: new SyncHook([]),
shouldEmit: new SyncBailHook(["compilation"]),
...
});
this.webpack = webpack;
...
}
watch(watchOptions, handler) { ... }
run(callback) { ... }
purgeInputFileSystem() { ... }
compile(callback) { ... }
close(callback) { ... }
...
}
run
run(callback) { // 重复执行报错 if (this.running) { return callback(new ConcurrentCompilationError()); } let logger; // 日志打印 // 结束回调(无论执行成功还是失败) const finalCallback = (err, stats) => { ... }; // 此刻时间 const startTime = Date.now(); // 执行过了将running变为true,用于判断是否重复执行 this.running = true; const onCompiled = (err, compilation) => { ... }; // 执行 const run = () => { this.hooks.beforeRun.callAsync(this, err => { if (err) return finalCallback(err); this.hooks.run.callAsync(this, err => { if (err) return finalCallback(err); this.readRecords(err => { if (err) return finalCallback(err); // 传入onCompiled,this.compile继续执行 this.compile(onCompiled); }); }); }); }; if (this.idle) { this.cache.endIdle(err => { if (err) return finalCallback(err); this.idle = false; run(); }); } else { run(); } }
compile
完成编译compile(callback) { // 参数是:普通模块工厂normalModuleFactory、上下文模块工厂contextModuleFactory const params = this.newCompilationParams(); // 异步调用beforeCompile钩子 this.hooks.beforeCompile.callAsync(params, err => { if (err) return callback(err); // 调用compile钩子 this.hooks.compile.call(params); // 获取一个编译器Compilation!!!!!! const compilation = this.newCompilation(params); const logger = compilation.getLogger("webpack.Compiler"); logger.time("make hook"); // 入口文件开始,构建模块,直到所有模块创建结束 // 执行make钩子会调用compilation上的addEntry this.hooks.make.callAsync(compilation, err => { logger.timeEnd("make hook"); if (err) return callback(err); logger.time("finish make hook"); this.hooks.finishMake.callAsync(compilation, err => { logger.timeEnd("finish make hook"); if (err) return callback(err); process.nextTick(() => { logger.time("finish compilation"); // 编译完成 compilation.finish(err => { logger.timeEnd("finish compilation"); if (err) return callback(err); logger.time("seal compilation"); compilation.seal(err => { logger.timeEnd("seal compilation"); if (err) return callback(err); logger.time("afterCompile hook"); // 异步调用afterCompile,返回回调函数 this.hooks.afterCompile.callAsync(compilation, err => { logger.timeEnd("afterCompile hook"); if (err) return callback(err); // 执行一些列钩子后,执行run里的onCompiled,onCompiled(null, compilation) return callback(null, compilation); }); }); }); }); }); }); }); }
run里的
onCompiled
const onCompiled = (err, compilation) => { if (err) return finalCallback(err); //如果没有编译完成 if (this.hooks.shouldEmit.call(compilation) === false) { compilation.startTime = startTime; compilation.endTime = Date.now(); const stats = new Stats(compilation); this.hooks.done.callAsync(stats, err => { if (err) return finalCallback(err); return finalCallback(null, stats); }); return; } process.nextTick(() => { logger = compilation.getLogger("webpack.Compiler"); logger.time("emitAssets"); // 输出编译后的文件,调用emitAsset方法,emitAsset主要负责写入文件输出文件,不影响我们先看编译(通过this.outputFileSystem、mkdirp、emitFiles输出文件) this.emitAssets(compilation, err => { logger.timeEnd("emitAssets"); if (err) return finalCallback(err); // 如果上传的编译没有完成则重来一遍编译 if (compilation.hooks.needAdditionalPass.call()) { compilation.needAdditionalPass = true; compilation.startTime = startTime; compilation.endTime = Date.now(); logger.time("done hook"); const stats = new Stats(compilation); this.hooks.done.callAsync(stats, err => { logger.timeEnd("done hook"); if (err) return finalCallback(err); this.hooks.additionalPass.callAsync(err => { if (err) return finalCallback(err); this.compile(onCompiled); }); }); return; } // 完成编译 logger.time("emitRecords"); this.emitRecords(err => { logger.timeEnd("emitRecords"); if (err) return finalCallback(err); // 编译开始和编译结束时间 compilation.startTime = startTime; compilation.endTime = Date.now(); logger.time("done hook"); const stats = new Stats(compilation); this.hooks.done.callAsync(stats, err => { logger.timeEnd("done hook"); if (err) return finalCallback(err); this.cache.storeBuildDependencies( compilation.buildDependencies, err => { if (err) return finalCallback(err); // 编译完成返回new Stats(compilation)表示完成编译 return finalCallback(null, stats); } ); }); }); }); }); };
emitAssets
收集编译好的文件之后创建文件emitAssets(compilation, callback) { let outputPath; // 收集编译好的文件 const emitFiles = err => { const assets = compilation.getAssets(); compilation.assets = { ...compilation.assets }; const caseInsensitiveMap = new Map(); const allTargetPaths = new Set(); // 对每个file资源文件进行路径拼接,将每个source源码转换为buffer(为了性能提升),最后将文件写入真实目标路径 asyncLib.forEachLimit(...) ... } // 创建文件 this.hooks.emit.callAsync(compilation, err => { if (err) return callback(err); outputPath = compilation.getPath(this.outputPath, {}); mkdirp(this.outputFileSystem, outputPath, emitFiles); }); }
Compiler
的流程线
Compilation
lib/Compilation.js
使用对应 loader
对文件进行转换处理,解析文件的ast
语法树,创建bundles
,生成chunks
,在Compiler.js
实例化用于编译,代表了一次单一的版本构建和生成资源。Compiler
的流程线里调用hooks.make
钩子会执行Compilation.addEntry
。
(1)addEntry
:启动构建入口模块,成功后将入口模块添加到程序之中,调用hooks.addEntry
,执行Compilation.addModule
。
(2)finish
:完成编译,收集了每个模块构建是产生的问题。
(3)seal
:结束编译,生成chunks,对chunks进行一系列的优化。
addEntry
参数:context项目路径,entry入口依赖,optionsOrName项目配置或者项目名,callback回调。
(1)addEntry
整理好optionsOrName
后执行Compilation._addEntryItem
// Compilation._addEntryItem _addEntryItem(context, entry, target, options, callback) { // 第一步生成entryData大致如下 let entryData = { dependencies: [], includeDependencies: [], options: { name: undefined, ...options }, [target]: entry } ... // 把entryData存入entries this.entries.set(name, entryData); ... // 调用hooks.addEntry 让this.entries.get(name)的options变成{ name: 名对应字, runtime: 'runtime'} this.hooks.addEntry.call(entry, options); // 调用Compilation.addModuleTree this.addModuleTree({...参数}, 一个callback函数); }
(2)
hooks.addEntry
挂载了两个fn
,ProgressPlugin
是进度条。taps: [ { type: 'sync', fn: [Function (anonymous)], name: 'RuntimeChunkPlugin' }, { type: 'sync', fn: [Function: entryAdd], name: 'ProgressPlugin' } ],
(3)
RuntimeChunkPlugin
里的addEntry.tap
// lib/optimize/RuntimeChunkPlugin.js compilation.hooks.addEntry.tap( "RuntimeChunkPlugin", (_, { name: entryName }) => { if (entryName === undefined) return; const data = compilation.entries.get(entryName); if (data.options.runtime === undefined && !data.options.dependOn) { // 确定运行时区块名称 let name = this.options.name; if (typeof name === "function") { name = name({ name: entryName }); } data.options.runtime = name; // 让entries里对应entryName的值的runtime = 'runtime' } } );
(4)
addModuleTree
addModuleTree({ context, dependency, contextInfo }, callback) { ... // this.dependencyFactories是Map,在初始化注册插件apply的时候set const Dep = dependency.constructor; // class TaroSingleEntryDependency extends ModuleDependency // 根据对应的Dep获取对应的模块工厂,在Compiler.compile中可以知道分成普通模块工厂normalModuleFactory和上下文模块工厂contextModuleFactory const moduleFactory = this.dependencyFactories.get(Dep); // 执行Compilation.handleModuleCreation this.handleModuleCreation({...参数}, 一个callback函数) }
(5)
handleModuleCreation
handleModuleCreation({ factory, dependencies, originModule, contextInfo, context, recursive = true, connectOrigin = recursive },callback) { // ModuleGraph用于记录依赖关系 const moduleGraph = this.moduleGraph; // 配置文件 = undefined const currentProfile = this.profile ? new ModuleProfile() : undefined; this.factorizeModule({...参数}, 一个callback函数) }
(6)
factorizeModule
Compilation.prototype.factorizeModule = function (options, callback) { this.factorizeQueue.add(options, callback); // 放入到异步队列里 };
constructor(compiler, params) { ... // factorizeQueue是AsyncQueue的实例,作用是异步队列控制,将callback存入callbacks this.factorizeQueue = new AsyncQueue({ name: "factorize", parent: this.addModuleQueue, // 达到条件会后执行processor,会返回一个【modulefactory】,执行factory.create,执行完成之后会执行回调callbacks里的callback processor: this._factorizeModule.bind(this) }); ... }
_factorizeModule({currentProfile, factory, dependencies, originModule, factoryResult, contextInfo, context}, callback){ ... factory.create({...参数}, 个callback函数); }
(7)
normalModuleFactory
和contextModuleFactory
【normalModuleFactory】常用来处理import A from './a'这种导入语句的文件 【contextModuleFactory】常用来处理const b = require('./b/' + c)这种带变量的导入语句,在编译阶段webpack没办法识别真正运行时该用哪个文件,因此会将b目录下的所有文件都进行编译
(8)
factory.create
我们在项目里都是用normal
的方式引入,用于构建module// lib/NormalModuleFactory.js create(data, callback) { ... // 创建resolveData const resolveData = {....}; // 执行hooks.beforeResolve this.hooks.beforeResolve.callAsync(resolveData, (err, result) => { ... // 执行hooks.factorize(NormalModuleFactory初始化的时候挂载),内部会执行hooks.resolve和hooks.createModule this.hooks.factorize.callAsync(resolveData, (err, module) => { ... const factoryResult = {...}; callback(null, factoryResult); // 获得factoryResult执行回调 }) }) }
(9)
hooks.factorize
// NormalModuleFactory初始化 this.hooks.factorize.tapAsync({ name: "NormalModuleFactory", stage: 100},(resolveData, callback) => { // 生成对应的`loader`和依赖信息 this.hooks.resolve.callAsync(resolveData, (err, result) => { ... this.hooks.afterResolve.callAsync(resolveData, (err, result) => { ... this.hooks.createModule.callAsync(createData, resolveData, () => { ... createdModule = this.hooks.module.call( createdModule, createData, resolveData ); return callback(null, createdModule); // module构建完成 }) }) }) })
(10)
hooks.resolve
,解析NormalModuleFactory
,生成对应的loader
和依赖信息// NormalModuleFactory初始化 data = resolveData this.hooks.resolve.tapAsync({ name: "NormalModuleFactory", stage: 100},(data, callback) => { ... // 获取一个解析loader的工具 const loaderResolver = this.getResolver("loader"); // ... }
// type = "loader" resolveOptions = undefined getResolver(type, resolveOptions) { return this.resolverFactory.get(type, resolveOptions); }
// lib/ResolverFactory.js // ResolverFactory.get get(type, resolveOptions = EMPTY_RESOLVE_OPTIONS) { // 获取缓存 let typedCaches = this.cache.get(type); // 没有则创建缓存并存入 if (!typedCaches) { ... this.cache.set(type, typedCaches); } // 如果存在resolver直接返回resolver const cachedResolver = typedCaches.direct.get(resolveOptions); if (cachedResolver) { return cachedResolver; } ... // 创建了一个resolver并返回 // this._create通过require("enhanced-resolve").ResolverFactory.createResolver(resolveOptions)来创建resolver const newResolver = this._create(type, resolveOptions); ... return newResolver; }
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。