Skip to content

webpack5 源码详解 - 编译模块 #17

@Hazlank

Description

@Hazlank

编译构建模块

上一篇讲了关于webpack初始化做了哪些工作,之后会调用make hook分步骤进行处理模块。

Make

make hooks注册了EntryPlugin,它会调用compilation.addEntry处理入口模块

//Compiler.js
this.hooks.make.callAsync(compilation, err=> {
	//...
})

//EntryPlugin.js
const { entry, options, context } = this;
const dep = EntryPlugin.createDependency(entry, options);

compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
	//context是运行目录,dep是 Entrydependecy,包含name, 入口路径
	compilation.addEntry(context, dep, options, err => {
		callback(err);
	});
});

首先会创建依赖,这个依赖叫EntryDependency,之后会将dep,options,context作为传参到compilation.addEntry

addEntry

//Compilation.js

addEntry(context, entry, optionsOrName, callback) {
  //如果options不为对象就转换成对象
  const options =
    typeof optionsOrName === "object"
      ? optionsOrName
      : { name: optionsOrName };

  this._addEntryItem(context, entry, "dependencies", options, callback);
}

_addEntryItem(context, entry, target, options, callback) {
	const { name } = options;
  //如果从entries拿不到就会创建entryData
	let entryData =
		name !== undefined ? this.entries.get(name) : this.globalEntry;
	if (entryData === undefined) {
		entryData = {
			dependencies: [],
			includeDependencies: [],
			options: {
				name: undefined,
				...options
			}
		};
    //target: dependencies | includeDependencies
		entryData[target].push(entry);
    //保存到Map
		this.entries.set(name, entryData);
	} else {
		//..
	}
	
this.addModuleTree(//...);
}

因为支持MPA(multiple entry points),所以会判断有无设置过的entry。因为配置的时候是只有一个入口为entry: "./index.js",所以在前面处理options时会转成对象,name为"main"

const getNormalizedEntryStatic = entry => { 
	if (typeof entry === "string") { 
		return { main: { import: [entry] }
	} 
};

addModuleTree

保存完EntryData后就会执行addModuleTree

addModuleTree({ context, dependency, contextInfo }, callback) {
	const moduleFactory = this.dependencyFactories.get(Dep);
	
	this.handleModuleCreation(
		{
			factory: moduleFactory,
			dependencies: [dependency],
			originModule: null,
			contextInfo,
			context
		},
		(err, result) => {
      //...
		}
	);
}

首先会拿到对应的moduleFactory,它可以为NormalModuleFactoryContextModuleFactory 。不同的依赖对应不同的处理方式 比如EntryDependency和ImportDependency对应NormalModuleFactory, AMDRequireContextDependency 对应ContextModuleFactory ,它们都是之前初始化插件的时候注入到dependencyFactories里的。

img

handleModuleCreation

得到对应的moduleFactory后,handleModuleCreation将正式开始处理模块了,包括后续递归处理引入的外部依赖也是调用它

handleModuleCreation(
	{
		factory,
		dependencies,
		originModule,
		contextInfo,
		context,
		recursive = true,
		connectOrigin = recursive
	},
	callback
) {
	const moduleGraph = this.moduleGraph;

	this.factorizeModule(
		{
			currentProfile,
			factory,
			dependencies,
			factoryResult: true,
			originModule,
			contextInfo,
			context
		},
		() => {
			//...
		}
	)
}

factorizeModule =  (
  function (options, callback) {
    this.factorizeQueue.add(options, callback);
  }
);

moduleGraph是个实例化对象,用于保存依赖,模块之间的引用信息等,用以后续的分析。

moduleGraph: {
  /** @type {WeakMap<Dependency, ModuleGraphConnection>} */
	_dependencyMap:WeakMap

  /** @type {Map<Module, ModuleGraphModule>} */
	_moduleMap:Map(0) {size: 0}
  //...
}

factorizeModule会调用this.factorizeQueue.add,用来解析模块,它是一个AsyncQueue。 Compilation包含几个AsyncQueue用来分步骤处理模块,每一个AsyncQueue就是一个task

AsyncQueue

Compilation为模块不同的操作分别定义了异步队列,队列提供了很多方法,比如可以判断队列是否在运行,停止,清除队列等, 可以传入parallelism控制并行运行的任务数量等(用于微调性能或者让处理模块时序正确保证分析结果)。AsyncQueue是webpack5才有的代码,Commit message如下

add queues to Compilation

remove Semaphore and use AsyncQueue instead
deprecate Module.needRebuild, add Module.needBuild
remove Module.unbuild
add Module.invalidateBuild

//AsyncQueue

//处理模块内的外部依赖
this.processDependenciesQueue = new AsyncQueue({
		name: "processDependencies",
		parallelism: options.parallelism || 100,
		processor: this._processModuleDependencies.bind(this)
	});

//添加依赖到对应的moduleGraph
this.addModuleQueue = new AsyncQueue({
	name: "addModule",
	parent: this.processDependenciesQueue,
	getKey: module => module.identifier(),
	processor: this._addModule.bind(this)
});

//解析模块信息
this.factorizeQueue = new AsyncQueue({
	name: "factorize",
	parent: this.addModuleQueue,
	processor: this._factorizeModule.bind(this)
});

//编译模块
this.buildQueue = new AsyncQueue({
	name: "build",
	parent: this.factorizeQueue,
	processor: this._buildModule.bind(this)
});

//重构建
this.rebuildQueue = new AsyncQueue({
	name: "rebuild",
	parallelism: options.parallelism || 100,
	processor: this._rebuildModule.bind(this)
});

this.factorizeQueue.add会调用AsyncQueue的add方法。接着创建AsyncQueueEntry保存在AsyncQueue的_entries和_queued里。当可以运行时,会执行setImmediate(root._ensureProcessing)。

//AsyncQueue.js
add(item, callback) {
	if (this._stopped) return callback(new WebpackError("Queue was stopped"));
	this.hooks.beforeAdd.callAsync(item, err => {
		//实例化AsyncQueueEntry
		const newEntry = new AsyncQueueEntry(item, callback);
		if (this._stopped) {
			//...
		} else {
			this._entries.set(key, newEntry);
			this._queued.enqueue(newEntry);
			const root = this._root;
			root._needProcessing = true;
			if (root._willEnsureProcessing === false) {
				root._willEnsureProcessing = true;
				setImmediate(root._ensureProcessing);
			}
			this.hooks.added.call(item);
		}
	});
}

_ensureProcessing() {
	  //...
  this._willEnsureProcessing = false;
  if (this._queued.length > 0) return;
  if (this._children !== undefined) {
  //_children是根据实例化AysncQueue的传参parent生成的

  //_children: Array<addModuleQueue , factorizeQueue , buildQueue >
  for (const child of this._children) {
    while (this._activeTasks < this._parallelism) {

      //将队列任务取出
      const entry = child._queued.dequeue();  
      if (entry === undefined) break;
      this._activeTasks++;
      entry.state = PROCESSING_STATE;
      
      //将entry丢进_startProcessing里
      child._startProcessing(entry);
    }
    if (child._queued.length > 0) return;
    }
  }
  if (!this._willEnsureProcessing) this._needProcessing = false;
}

_startProcessing(entry) {
  this.hooks.beforeStart.callAsync(entry.item, err => {
	  if (err) {
		//...
	  }
	  let inCallback = false;
	  try {
		  //Async.add 最终会调用Async._processor
		  this._processor(entry.item, (e, r) => {
			  inCallback = true;
			  this._handleResult(entry, e, r);
		  });
	  } catch (err) {
		  //...
	  }
	  this.hooks.started.call(entry.item);
	});
}

当执行_ensureProcessing的时候,会循环将队列拿出来,因为我们调用的是factorizeQueue,所以第一次循环取出来的addModule的队列是空的会被跳过。所以会执行factorizeQueue的_processor, 它是this._factorizeModule.bind(this)

factorizeModule

factorizeModule用于resolve模块信息,比如相对,绝对路径,packjson内容等。

_factorizeModule(
  {
    currentProfile,
    factory,
    dependencies,
    originModule,
    factoryResult,
    contextInfo,
    context
  },
  callback
) {
  //因为EntryDdependency对应的是NormalModuleFactory,所以调用NormalModuleFactory.create
  factory.create(
    {
      contextInfo: {
        issuer: originModule ? originModule.nameForCondition() : "",
        issuerLayer: originModule ? originModule.layer : null,

        compiler: this.compiler.name,
        ...contextInfo
      },
      resolveOptions: originModule ? originModule.resolveOptions : undefined,
      context: context
        ? context
        : originModule
        ? originModule.context
        : this.compiler.context,
      dependencies: dependencies
    },
    (err, result) => {
      //...
      callback(null, factoryResult ? result : result.module);
    }
  );
}

第一步会先调用factory.create,然后将结果传回去,originModule表示当前模块是被谁引用的,因为处理的是入口文件,所以originModule为undefiend。所以factory.created的参数只用关注dependencies为Array<EntryDependency>,context为程序运行目录。

NormalModuleFactory.create

create(data, callback) {
  //dependencies: Array<EntryDependency>
  const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
  //运行目录
  const context = data.context || this.context;
  //resolve配置
  const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
  //依赖
  const dependency = dependencies[0];
  //request: "/index.js"
  const request = dependency.request;
  const assertions = dependency.assertions;
  const contextInfo = data.contextInfo;
  //依赖类型 如:esm
  const dependencyType = (dependencies.length > 0 && dependencies[0].category) || "";

  //解析数据
  const resolveData = {
    contextInfo,
    resolveOptions,
    context,
    request,
    assertions,
    dependencies,
    dependencyType,
    createData: {},
    cacheable: true
  };

  this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
    //...
    this.hooks.factorize.callAsync(resolveData, (err, module) => {
      const factoryResult = {
        module,
        cacheable: resolveData.cacheable
      };

      callback(null, factoryResult);
    });
  });
}

create会初始化属性并调用钩子this.hooks.factorize.callAsync一共有两个插件。第一个是ExternalModuleFactoryPlugin,会对external配置的模块进行处理。第二个插件的名字叫NormalModuleFactory,他是之前实例化NormalModuleFactory时注册进来的

class NormalModuleFactory {
	constructor () {
    //...

    //Compiler.js实例化NormalModuleFactory的时候注册
    this.hooks.factorize.tapAsync(
      {
        name: "NormalModuleFactory",
        stage: 100
      },
      (resolveData, callback) => {
        //调用resolve钩子
        this.hooks.resolve.callAsync(resolveData, (err, result) => {
          //...
        });
      }
    );
   //...
	  
  this.hooks.resolve.tapAsync(...)
	}
	//...
}

在实例化NormalModuleFactory的时候会注册两个插件,一个在factorize阶段,一个在resolve阶段。factorize钩子会调用resolve钩子。resolve下注册的hook代码如下

NormalModuleFactory.hooks.resolve

this.hooks.resolve.tapAsync(
  {
    name: "NormalModuleFactory",
    stage: 100
  },
  (data, callback) => {
    const {
      contextInfo,
      context,
      dependencies,
      dependencyType,
      request,
      assertions,
      resolveOptions,
    } = data;
    //获取loader的解析器
    const loaderResolver = this.getResolver("loader");

    /** @type {ResourceData | undefined} */
    let matchResourceData = undefined;
    /** @type {string} */
    let unresolvedResource;
    /** @type {ParsedLoaderRequest[]} */
    let elements;
    let noPreAutoLoaders = false;
    let noAutoLoaders = false;
    let noPrePostAutoLoaders = false;

    //Scheme表示为URL方案,data,file等
    const contextScheme = getScheme(context);
    /** @type {string | undefined} */
    let scheme = getScheme(request);
    
    if (!scheme) {
      //...
    }
    //...
    
  }
);

首先会对context和request调用getScheme。context是执行目录,request就是我们的入口文件/index.js。getScheme会解析对应的字符串是否是某些URL方案。比如可能是file:///user/webpack/index.js或者data:text/javascript;base64...,如果是的话会丢到相应的dataUrlPluginfileUrlPlugin处理

Get scheme if specifier is an absolute URL specifier

继续resolve hook之后的代码

//this.hooks.resolve.tapAsync

if (!scheme) {
	/** @type {string} */
	
	let requestWithoutMatchResource = request;
	//...

	scheme = getScheme(requestWithoutMatchResource);

	if (!scheme && !contextScheme) {
    //判断是否有inline-loader
		const firstChar = requestWithoutMatchResource.charCodeAt(0);
		const secondChar = requestWithoutMatchResource.charCodeAt(1);
		noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!"
		noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!"
		noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!";
		const rawElements = requestWithoutMatchResource
			.slice(
				noPreAutoLoaders || noPrePostAutoLoaders
					? 2
					: noAutoLoaders
					? 1
					: 0
			)
			.split(/!+/);
		unresolvedResource = rawElements.pop();
		elements = rawElements.map(el => {
			const { path, query } = cachedParseResourceWithoutFragment(el);
			return {
				loader: path,
				options: query ? query.slice(1) : undefined
			};
		});
		scheme = getScheme(unresolvedResource);
	} else {
		unresolvedResource = requestWithoutMatchResource;
		elements = EMPTY_ELEMENTS;
	}
} else {
	unresolvedResource = request;
	elements = EMPTY_ELEMENTS;
}

如果没有scheme,会判断是否含有inline-loader,如果有会被拆分开来。比如!style-loader!css-loader?modules!./styles.css会拆成如下对象,然后丢进resolveRequestArray进行相关处理

[
	{
		loader: 'style-loader', options: undefined
	},
	{
		loader: 'css-loader', options: 'modules'
	}
]

接着会调用defaultResolve(context)生成模块解析器。

//this.hooks.resolve

if (scheme) {
	//...
}

else if (contextScheme) {
	//...
}

// resource without scheme and without path
defaultResolve(context);

const defaultResolve = context => {
	if (/^($|\?)/.test(unresolvedResource)) {
		//...
	}

	// resource without scheme and with path
	else {
		//创建解析器
		const normalResolver = this.getResolver(
			"normal",
			//dependencyType: "esm"
			dependencyType
				? cachedSetProperty(
						resolveOptions || EMPTY_RESOLVE_OPTIONS,
						"dependencyType",
						dependencyType
						)
				: resolveOptions
		);
		
		this.resolveResource(
		  contextInfo,
		  context,
		  unresolvedResource,
		  normalResolver,
		  resolveContext,
		  (err, resolvedResource, resolvedResourceResolveData) => {
			  //...
			  continueCallback();
		  });
	}
};

this.getResolver会调用ResolverFactory生成解析器,解析器用于解析文件绝对路径等信息。

ResolverFactory内部的功能扩展于enhanced-resolve。enhanced-resolve是一个高度可配置的resolve库,比如我们经常配置resolve.alias用别名代替某些路径,配置resolve.extensions用于扩展名等。

根据getResolver传进来的参数会实例化enhanced-resolve并调用resolverFactory的钩子合并options

//WebpackOptionsApply.js

//getResolver('normal', 'esm')会生成Resolver,然后触发hooks进行合并用户配置的options.resolve

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 => {
		//...
	});
compiler.resolverFactory.hooks.resolveOptions
	.for("loader")
	.tap("WebpackOptionsApply", resolveOptions => {
		//...
	});

接着调用this.resolveResource,它会调用刚刚生成的resolver解析模块

resolveResource(
		contextInfo,
		context,
		unresolvedResource,
		resolver,
		resolveContext,
		callback
	) {
	resolver.resolve(
		contextInfo,
		context,
		unresolvedResource,
		resolveContext,
		(err, resolvedResource, resolvedResourceResolveData) => {
			if (err) {
				//...
			}
			callback(err, resolvedResource, resolvedResourceResolveData);
		}
	);
}

resolver.resolve会得到resolvedResource和resolvedResourceResolveData。

resolvedResource为c:\\Users\\Administrator\\Desktop\\webpack\\index.js,是执行webpack的入口文件的绝对路径。resolvedResourceResolveData会包含一些解析相关信息。

img

接着回到this.resolveResource回调执行continueCallback函数

const continueCallback = needCalls(2, err => {
	//...
	const settings = {};
	const useLoadersPost = [];
	const useLoaders = [];
	const useLoadersPre = [];

	// handle .webpack[] suffix
	let resource;
	let match;
	if (
		//...
	) {
		//...
	} else {
		settings.type = "javascript/auto";
		const resourceDataForRules = matchResourceData || resourceData;
		const result = this.ruleSet.exec({
      //模块路径
			resource: resourceDataForRules.path,
			realResource: resourceData.path,
			resourceQuery: resourceDataForRules.query,
			resourceFragment: resourceDataForRules.fragment,
      //url方案
			scheme,
			assertions,
      //模块的mimetype
			mimetype: matchResourceData
				? ""
				: resourceData.data.mimetype || "",
      //依赖类型
			dependency: dependencyType,
      //模块的描述文件,列如packjson.js
			descriptionData: matchResourceData
				? undefined
				: resourceData.data.descriptionFileData,
      
      //模块发起者
			issuer: contextInfo.issuer,
			compiler: contextInfo.compiler,
			issuerLayer: contextInfo.issuerLayer || ""
		});
    //loader类型
		for (const r of result) {
			if (r.type === "use") {
				if (!noAutoLoaders && !noPrePostAutoLoaders) {
					useLoaders.push(r.value);
				}
			} else if (r.type === "use-post") {
				if (!noPrePostAutoLoaders) {
					useLoadersPost.push(r.value);
				}
			} else if (r.type === "use-pre") {
				//...
			} else if (
				//...
			) {
				//...
			}
		}
	}

	let postLoaders, normalLoaders, preLoaders;

	//...
	this.resolveRequestArray(
		//...
		useLoadersPost,
		(err, result) => {
			//...
		}
	);

	this.resolveRequestArray(
		contextInfo,
		this.context,
		useLoaders,
		loaderResolver,
		resolveContext,
		(err, result) => {
			normalLoaders = result;
			continueCallback(err);
		}
	);

	this.resolveRequestArray(
		//...
		(err, result) => {
			//...
		}
	);
	
	//
	defaultResolve = () {
		//...
	}
});

首先会执行this.ruleSet.exec,之前的初始化篇讲过它是用来根据资源输出对应匹配的loader。解析出来的结果有三种类型,use,use-postuse-pre,然后放置在对应的useLoadersPost, useLoadersuseLoadersPre

在webpack1的时候有相应的配置,可以在loader处理之前或之后执行其他 预/后 处理loader。但是在v2后就被移除了。

之后会对每个loaders Array进行resolveRequestArray处理,因为没有preLoader和postLoader,所以我们只用关注中间的resolveRequestArray

resolveRequestArray(
		contextInfo,
		context,
		array,
		resolver,
		resolveContext,
		callback
	) {
  if (array.length === 0) return callback(null, array);
  //异步库
  asyncLib.map(
    array,
    (item, callback) => {
      //调用解析器的resolve方法
      resolver.resolve(
        contextInfo,
        context,
        item.loader,
        resolveContext,
        (err, result) => {
          //...
          const parsedResult = this._parseResourceWithoutFragment(result);
          const resolved = {
            loader: parsedResult.path,
            options:
              item.options === undefined
                ? parsedResult.query
                  ? parsedResult.query.slice(1)
                  : undefined
                : item.options,
            ident: item.options === undefined ? undefined : item.ident
          };
          return callback(null, resolved);
        }
      );
    },
    callback
  );
}

async是一个异步库,map方法会循环array,然后把每个item给resolver.resolve执行,最后调用callback。resolver是之前调用的 this.getResolver("loader")创建的LoaderResolver,resolver解析loader相应的绝对路径,比如传进去的为babel-loader, 出来的就是node_modules包里入口地址C:\\Users\\Administrator\\Desktop\\webpack\\node_modules\\.pnpm\\[email protected]_ed870ac3ba52c4ec230ba2bc3dbb311c\\node_modules\\babel-loader\\lib\\index.js。然后将结果回调给continueCallback

const continueCallback = needCalls(3, err => {
	if (err) {
		return callback(err);
	}
	const allLoaders = postLoaders;
	if (matchResourceData === undefined) {
		for (const loader of loaders) allLoaders.push(loader);
		for (const loader of normalLoaders) allLoaders.push(loader);
	} else {
		for (const loader of normalLoaders) allLoaders.push(loader);
		for (const loader of loaders) allLoaders.push(loader);
	}
	for (const loader of preLoaders) allLoaders.push(loader);
	let type = settings.type;
	const resolveOptions = settings.resolve;
	const layer = settings.layer;
	if (layer !== undefined && !layers) {
		return callback(
			new Error(
				"'Rule.layer' is only allowed when 'experiments.layers' is enabled"
			)
		);
	}
	try {
		Object.assign(data.createData, {
			layer: layer === undefined ? contextInfo.issuerLayer || null : layer,				//资源发起者
			request: stringifyLoadersAndResource(allLoaders, resourceData.resource),		//loader绝对路径和模块资源绝对路径
			userRequest,																						//用户请求绝对路径
			rawRequest: request,																			//未经处理的请求路径
			loaders: allLoaders,																				//资源所需要的所有loader
			resource: resourceData.resource,														//资源绝对路径		
			context: resourceData.context || getContext(resourceData.resource),
			matchResource: matchResourceData ? matchResourceData.resource : undefined,
			resourceResolveData: resourceData.data,			//enhance-resolve返回的数据
			settings,												
			type,									//资源类型
			parser: this.getParser(type, settings.parser),			//根据类型和设置返回的Parser
			parserOptions: settings.parser,
			generator: this.getGenerator(type, settings.generator),					//根据类型和设置返回的generator
			generatorOptions: settings.generator,
			resolveOptions
		});
	} catch (e) {
		return callback(e);
	}
	callback();
});

img

continueCallback会整合所有之前resolve的数据(资源信息,loader信息)到createData

createData还会初始化对应的parser和generator,对于不同的type会有不同的parser和generator,用于webpack处理不同的资源和输出不同代码

Parser

JavascriptParser

  • javascript/auto
  • javascript/esm
  • javascript/dynamic

AssetParser

  • asset
  • asset/inline
  • asset/resource
  • asset/source

JsonParser

WebAssemblyParser

CssParser

Generator

  • JavascriptGenerator
  • JsonGenerator
  • WebAssemblyGenerator
  • CssGenerator

webpack5也允许对ParserGenerator进行配置以获得某些功能或者更改输出配置

NormalModuleFactory.hooks.factorize

至此,resolve hooks的工作都做完了,将会回到factorize hooks。

// this.hooks.factorize.tapAsync

 this.hooks.resolve.callAsync(resolveData, (err, result) => {
    //resolveData: createData

		//...
	  this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
		  if (err) return callback(err);

		  //enhanse-resolve解析的数据
		  const createData = resolveData.createData;

		  this.hooks.createModule.callAsync(
			  createData,
			  resolveData,
			  (err, createdModule) => {
				  //...

					//实例化NormalModule
					createdModule = new NormalModule(
						/** @type {NormalModuleCreateData} */ (createData)
					);
				}				

          //调用插件判断是否配置sideEffects
				  createdModule = this.hooks.module.call(
					  createdModule,
					  createData,
					  resolveData
				  );

				  return callback(null, createdModule);
			  }
		  );
	  });
});

之后便会根据createData实例化NormalModule,这样NormalModule就保存了当前模块的解析信息。一个模块会对应一个NormalModule,之后模块相关的操作都会在NormalModule进行。

this.hooks.module.call会有两个插件 ,一个判断packjson的有无sideEffects 配置,一个判断Rules有无sideEffects 配置。对模块设置sideEffects能够更友好的让webpack进行tree shaking

// SideEffectsFlagPlugin

//package.json存在sideEffects就进行相关设置
(module, data) => {
  //resolve数据
	const resolveData = data.resourceResolveData;
	if (
		resolveData &&
		resolveData.descriptionFileData &&
		resolveData.relativePath
	) {
    //packjson.sideEffects
		const sideEffects = resolveData.descriptionFileData.sideEffects;
		if (sideEffects !== undefined) {
			if (module.factoryMeta === undefined) {
				module.factoryMeta = {};
			}
			const hasSideEffects =
				SideEffectsFlagPlugin.moduleHasSideEffects(
					resolveData.relativePath,
					sideEffects,
					cache
				);
			module.factoryMeta.sideEffectFree = !hasSideEffects;
		}
	}

	return module;
}

//Rules里包含sideEffects就进行设置
(module, data) => {
	if (typeof data.settings.sideEffects === "boolean") {
		if (module.factoryMeta === undefined) {
			module.factoryMeta = {};
		}
		module.factoryMeta.sideEffectFree = !data.settings.sideEffects;
	}
	return module;
}

到这里,整个模块相关信息就解析完了, 将会开始回收栈,把结果返回给this.hooks.factorize,回到factory.create,再回到_startProcessing里的_processor

_startProcessing(entry) {
  this.hooks.beforeStart.callAsync(entry.item, err => {
	//...
	  try {
		  this._processor(entry.item, (e, r) => {
			  inCallback = true;
			  //回到_processor回调执行_handleResult
			  this._handleResult(entry, e, r);
		  });
	  } catch (err) {
		  //...
	  }
	
	});
}

_handleResult(entry, err, result) {
	this.hooks.result.callAsync(entry.item, err, result, hookError => {
		const error = hookError
			? makeWebpackError(hookError, `AsyncQueue(${this._name}).hooks.result`)
			: err;

		const callback = entry.callback;
		const callbacks = entry.callbacks;
		entry.state = DONE_STATE;
		entry.callback = undefined;
		entry.callbacks = undefined;
		entry.result = result;
		entry.error = error;
		
		//...
		//执行callback
		callback(error, result);
		inHandleResult--;
	});
}

_handleResult会将一些属性赋值给AsyncQueueEntry对象并执行callback,还记得最初是由this.factorizeModule调用的吗

handleModuleCreation () {
  //...
  this.factorizeModule(
    {
      currentProfile,
      factory,
      dependencies,
      factoryResult: true,
      originModule,
      contextInfo,
      context
    },
    (err, factoryResult) => {
      //...

      const newModule = factoryResult.module;
      //...

      this.addModule(newModule, (err, module) => {
        //...
      }
    }
  );

它将会用resolveData创建的NormalModule丢进this.addModule处理,

addModule AsyncQueue

this.addModule和this.factorizeModule一样也是AsyncQueue。

addModule(module, callback) {
	this.addModuleQueue.add(module, callback);
}

_addModule(module, callback) {
	const identifier = module.identifier();
	//...

	this._modulesCache.get(identifier, null, (err, cacheModule) => {
		//...
		this._modules.set(identifier, module);
		this.modules.add(module);
		if (this._backCompat)
			ModuleGraph.setModuleGraphForModule(module, this.moduleGraph);
		if (currentProfile !== undefined) {
			currentProfile.markIntegrationEnd();
		}
		callback(null, module);
	});
}


//ModuleGraph.js
const moduleGraphForModuleMap = new WeakMap();

static setModuleGraphForModule(module, moduleGraph) {
	moduleGraphForModuleMap.set(module, moduleGraph);
}

首先会获取模块的identifier,它会拼凑模块的type,request和layer。然后将module和对应的moduleGraph加入到map里。然后执行的this.addModule的回调

this.addModule(newModule, (err, module) => {

	if (...) {
		//...
	} else {
		//...
		for (let i = 0; i < dependencies.length; i++) {
			const dependency = dependencies[i];
			moduleGraph.setResolvedModule(
				connectOrigin ? originModule : null,
				dependency,
				module
			);
		}
	}

	//...
});

首先会循环dependencies并将当前module和依赖丢进setResolvedModule,originModule是父模块,比如index.jsmodule_c.js引入了个变量,module_c的originModule就是index

ModuleGraph

setResolvedModule会执行一些ModuleGraph操作

ModuleGraph.js里会包含ModuleGraph、ModuleGraphConnection、ModuleGraphModule,它们会收集模块间的依赖关系,并为后续处理提供信息

  • ModuleGraph
    • _dependencyMap
    • _moduleMap

ModuleGraph会被实例化,然后赋值到Compilation对象里,之后只要调用Compilation.ModuleGraph就能进行相关操作。

ModuleGraph有两个重要的Map,_dependencyMap_moduleMap

_dependencyMap接收Dependency为键,ModuleGraphConnection为值,Dependency比如为EntryDependency,ImportDependency等。

_moduleMap接收Module为键,ModuleGraphModule为值 , Module就是我们resloveData创建的NormalModule。

对于Map数据结构,之后就可以通过Module和Dependency对象直接方便找到对应的相关ModuleGraphConnection或ModuleGraphModule信息

  • ModuleGraphConnection
    • originModule;
    • dependency;
    • module
    • weak

ModuleGraphConnection保存引用信息,当前模块,父模块,依赖等。weak表示为弱依赖,用于处理一些SSR场景

  • ModuleGraphModule
    • incomingConnections
    • outgoingConnections
    • issuer
    • exports

incomingConnections保存模块的被引用集合,outgoingConnections保存模块的引用集合,集合对象都是ModuleGraphConnection。

ModuleGraphModule还保存着其他的信息,比如ExportsInfo,之后会用于保存模块的导出信息,并在优化的时候分析是否被使用来实现tree shaking

回到addModule

this.addModule(newModule, (err, module) => {

	if (...) {
		//...
	} else {
		//...
		for (let i = 0; i < dependencies.length; i++) {
			const dependency = dependencies[i];
			moduleGraph.setResolvedModule(
				connectOrigin ? originModule : null,
				dependency,
				module
			);
		}
	}

  //...
});

它会循环每个依赖并调用setResolvedModule

//ModuleGraph.js
setResolvedModule(originModule, dependency, module) {
  //实例化ModuleGraphConnection
	const connection = new ModuleGraphConnection(
		originModule,
		dependency,
		module,
		undefined,
		dependency.weak,
		dependency.getCondition(this)
	);
  //获取模块对应ModuleGraphModule的incomingConnections
	const connections = this._getModuleGraphModule(module).incomingConnections;
	connections.add(connection);

  //保存在outgoingConnections或_dependencyMap
	if (originModule) {
		const mgm = this._getModuleGraphModule(originModule);
		if (mgm._unassignedConnections === undefined) {
			mgm._unassignedConnections = [];
		}
		mgm._unassignedConnections.push(connection);
		if (mgm.outgoingConnections === undefined) {
			mgm.outgoingConnections = new SortableSet();
		}
		mgm.outgoingConnections.add(connection);
	} else {
		this._dependencyMap.set(dependency, connection);
	}
}

setResolvedModule会通过当前module新建ModuleGraphConnection实例,并保存在module对应的ModuleGraphModule的incomingConnections里。

之后如果有父模块就保存在outgoingConnections里,否则在_dependencyMap里保存connection。

最后会执行this.addmodule里的_handleModuleBuildAndDependencies,开始处理模块内容。

this.addModule(newModule, (err, module) => {
	//...
	this._handleModuleBuildAndDependencies(
		originModule,
		module,
		recursive,
		callback
	);

});


_handleModuleBuildAndDependencies(originModule, module, recursive, callback) {
	//...
	this.buildModule(module, err => {
		//...
    callback()
	});
}


//buildModule的processor
_buildModule(module, callback) {
	//...

	module.needBuild(
		{
			compilation: this,
			fileSystemInfo: this.fileSystemInfo,
			valueCacheVersions: this.valueCacheVersions
		},
		(err, needBuild) => {
			//...
			
      //调用module.build构建模块
			module.build(
				this.options,
				this,
				this.resolverFactory.get("normal", module.resolveOptions),
				this.inputFileSystem,
				err => {
					//...
				}
			);
		}
	);
}

buildModule AsyncQueue

this.buildModule同样也是一个AsyncQueue,它最终会调用Module.build开始构建模块

//normalModule.js
build(options, compilation, resolver, fs, callback) {

  //初始化属性
	this._forceBuild = false;
	this._source = null;
	if (this._sourceSizes !== undefined) this._sourceSizes.clear();
	this._sourceTypes = undefined;
	this._ast = null;
	this.error = null;
	this.clearWarningsAndErrors();
	this.clearDependenciesAndBlocks();
	this.buildMeta = {};
	this.buildInfo = {
		cacheable: false,
		parsed: true,
		buildDependencies: undefined,
		valueDependencies: undefined,
		hash: undefined,
		assets: undefined,
		assetsInfo: undefined
	};

	const startTime = compilation.compiler.fsStartTime || Date.now();

	const hooks = NormalModule.getCompilationHooks(compilation);

	return this._doBuild(options, compilation, resolver, fs, hooks, err => {
		//...
	});
}

module.build会初始化构建信息,源码,AST,buildInfo 等,然后调用_doBuild

_doBuild(options, compilation, resolver, fs, hooks, callback) {
  //获取loader上下文
	const loaderContext = this._createLoaderContext(
		resolver,
		options,
		compilation,
		fs,
		hooks
	);
	
	//...

  //调用loader-runner执行loaders
	runLoaders(
		{
			//模块路径
			resource: this.resource,
			//loaders
			loaders: this.loaders,
			//loader上下文
			context: loaderContext,
			//处理资源的函数
			processResource: (loaderContext, resourcePath, callback) => {
				const resource = loaderContext.resource;
				const scheme = getScheme(resource);

        //根据不同的方案读文件
				hooks.readResource
					.for(scheme)
					.callAsync(loaderContext, (err, result) => {
						if (err) return callback(err);
						if (typeof result !== "string" && !result) {
							return callback(new UnhandledSchemeError(scheme, resource));
						}
						return callback(null, result);
					});
			}
		},
		(err, result) => {
			//...

      //将loader添加进构建信息
			for (const loader of this.loaders) {
				this.buildInfo.buildDependencies.add(loader.loader);
			}
			this.buildInfo.cacheable = this.buildInfo.cacheable && result.cacheable;
			processResult(err, result.result);
		}
	);
}

_doBuild首先会创建loaderContext,用于runLoaders的参数,runLoaders来自loader-runner,这是webpack用于运行Loaders的库。

processResource用于该怎么读资源给Loader,因为我们的入口文件是"/indx.js",所以readResource hook会拿出FileUriPlugin,并进行fs.readFile,将文件Buffer取出。

runLoaders会将result返回,它包含resourceBuffer和result,如果有相应的loader,result将会转换成js可操作的字符串。

下图是使用babel-loader生成的结果
img

最后会将结果传给processResult进行处理

const processResult = (err, result) => {
	//...

	const source = result[0];
	const sourceMap = result.length >= 1 ? result[1] : null;
	const extraInfo = result.length >= 2 ? result[2] : null;
	
	//如果不是Buffer或者字符串就报错
	if (!Buffer.isBuffer(source) && typeof source !== "string") {
		const currentLoader = this.getCurrentLoader(loaderContext, 0);
		const err = new Error(
			`Final loader (${
				currentLoader
					? compilation.runtimeTemplate.requestShortener.shorten(
							currentLoader.loader
						)
					: "unknown"
			}) didn't return a Buffer or String`
		);
		const error = new ModuleBuildError(err);
		return callback(error);
	}
	
	//生成sourceMap
	this._source = this.createSource(
		options.context,
		//如果是二进制就转换成Buffer,否则字符串化
		this.binary ? asBuffer(source) : asString(source),
		sourceMap,
		compilation.compiler.root
	);
	if (this._sourceSizes !== undefined) this._sourceSizes.clear();
	this._ast =
		typeof extraInfo === "object" &&
		extraInfo !== null &&
		extraInfo.webpackAST !== undefined
			? extraInfo.webpackAST
			: null;
	return callback();
};

processResult会要求loader必须返回Buffer或者String,然后会调用createSrouce对result处理,createSource会调用webpack-sources库,能够生成有或者无sourceMap的源码。

最后初始化AST,调用callback,callback会回到_doBuild。

return this._doBuild(options, compilation, resolver, fs, hooks, err => {
	// if we have an error mark module as failed and exit
	if (err) {
		//...
	}
	//...

	let result;
	try {
		const source = this._source.source();
		//调用parser转换成AST
		result = this.parser.parse(this._ast || source, {
			source,
			current: this,
			module: this,
			compilation: compilation,
			options: options
		});
	}
  
  //...
});

_doBuild会把结果进行解析,还记得吗,这里的parser是在resolve阶段生成的,因为当前模块是js资源所以生成的是JavascriptParser。

JavascriptParser

parse(source, state) {
  //ast
	let ast;
  //注释
	let comments;
	const semicolons = new Set();
	if (source === null) {
		throw new Error("source must not be null");
	}
	if (Buffer.isBuffer(source)) {
		source = source.toString("utf-8");
	}
	if (typeof source === "object") {
		ast = /** @type {ProgramNode} */ (source);
		comments = source.comments;
	} else {
		comments = [];
    //调用acorn生成ast
		ast = JavascriptParser._parse(source, {
			sourceType: this.sourceType,
			onComment: comments,
			onInsertedSemicolon: pos => semicolons.add(pos)
		});
	}
	const oldScope = this.scope;
	const oldState = this.state;
	const oldComments = this.comments;
	const oldSemicolons = this.semicolons;
	const oldStatementPath = this.statementPath;
	const oldPrevStatement = this.prevStatement;
	//初始化作用域
	this.scope = {
		topLevelScope: true,
		inTry: false,
		inShorthand: false,
		isStrict: false,
		isAsmJs: false,
		definitions: new StackedMap()
	};
	/** @type {ParserState} */
	this.state = state;
	this.comments = comments;
	this.semicolons = semicolons;
	this.statementPath = [];
	this.prevStatement = undefined;
	if (this.hooks.program.call(ast, comments) === undefined) {
		//判断js的模式
		this.detectMode(ast.body);
    //遍历topLevelScope的变量并声明
		this.preWalkStatements(ast.body);
		this.prevStatement = undefined;
		this.blockPreWalkStatements(ast.body);
		this.walkStatements(ast.body);
	}
	this.hooks.finish.call(ast, comments);
	this.scope = oldScope;
	/** @type {ParserState} */
	this.state = oldState;
	this.comments = oldComments;
	this.semicolons = oldSemicolons;
	this.statementPath = oldStatementPath;
	this.prevStatement = oldPrevStatement;
	return state;
}

JavascriptParser._parse函数用于解析js,在函数里会用acorn作为parser,然后转换成AST(抽象语法树),对于AST(抽象语法书)的树结构可以去astexplorer探索。

this.detectMode用于去检测是否含有 "use strict""use asm""use strict"代表当前为严格模式,"use asm"表示是使用asm.js编译出来的,帮助浏览器优化性能。

preWalkStatements

preWalkStatements用于迭代声明变量的范围

preWalkStatements(statements) {
	for (let index = 0, len = statements.length; index < len; index++) {
		const statement = statements[index];
		this.preWalkStatement(statement);
	}
}

preWalkStatement(statement) {
		this.statementPath.push(statement);
		if (this.hooks.preStatement.call(statement)) {
			this.prevStatement = this.statementPath.pop();
			return;
		}
		switch (statement.type) {
			case "BlockStatement":
				this.preWalkBlockStatement(statement);
				break;
			case "DoWhileStatement":
				this.preWalkDoWhileStatement(statement);
				break;
			case "ForInStatement":
				this.preWalkForInStatement(statement);
				break;
			case "ForOfStatement":
				this.preWalkForOfStatement(statement);
				break;
			case "IfStatement":
				this.preWalkIfStatement(statement);
				break;
			case "TryStatement":
				this.preWalkTryStatement(statement);
				break;
			case "VariableDeclaration":
				this.preWalkVariableDeclaration(statement);
				break;
      //...
		}
		this.prevStatement = this.statementPath.pop();
	}

preWalkStatement会遍历AST body,如果遇到最外层的变量声明,就定义变量到this.scope.definitions。definitions是个map结构,name为key,this.scope为值。

比如遇到var a = 2, 就符合VariableDeclaration的case,然后将"a"保存到definitions map里,对应的this.scope的topLevelScope为true。当然如果在if,for等block是用let声明的,那么会跳过,后续再定义scope,因为它们不属于topLevelScope。

blockPreWalkStatements

接着就是调用blockPreWalkStatements

blockPreWalkStatements(statements) {
	for (let index = 0, len = statements.length; index < len; index++) {
		const statement = statements[index];
		this.blockPreWalkStatement(statement);
	}
}

blockPreWalkStatement(statement) {
	this.statementPath.push(statement);
	if (this.hooks.blockPreStatement.call(statement)) {
		this.prevStatement = this.statementPath.pop();
		return;
	}
	switch (statement.type) {
		case "ImportDeclaration":
			this.blockPreWalkImportDeclaration(statement);
			break;
		case "ExportAllDeclaration":
			this.blockPreWalkExportAllDeclaration(statement);
			break;
		case "ExportDefaultDeclaration":
			this.blockPreWalkExportDefaultDeclaration(statement);
			break;
		case "ExportNamedDeclaration":
			this.blockPreWalkExportNamedDeclaration(statement);
			break;
		case "VariableDeclaration":
			this.blockPreWalkVariableDeclaration(statement);
			break;
		case "ClassDeclaration":
			this.blockPreWalkClassDeclaration(statement);
			break;
	}
	this.prevStatement = this.statementPath.pop();
}

在blockPreWalkStatements里,如果遇到的声明是ImportDeclaration的时候会调用blockPreWalkImportDeclaration。比如import some form "some"

blockPreWalkImportDeclaration(statement) {
	//获取module name
	const source = statement.source.value;
	//添加HarmonyImportSideEffectDependency依赖
	this.hooks.import.call(statement, source);

	//根据不同的import type 定义 VariableInfo
	switch (specifier.type) {
		case "ImportDefaultSpecifier":
			if (
				!this.hooks.importSpecifier.call(statement, source, "default", name)
			) {
				this.defineVariable(name);
			}
			break;
		case "ImportSpecifier":
			if (
				!this.hooks.importSpecifier.call(statement,source,specifier.imported.name,name)
			) {
				this.defineVariable(name);
			}
			break;
		case "ImportNamespaceSpecifier":
			if (!this.hooks.importSpecifier.call(statement, source, null, name)) {
				this.defineVariable(name);
			}
			break;
		default:
			this.defineVariable(name);
	}
	//...
}

//impor钩子注入的回调
parser.hooks.import.tap(
	"HarmonyImportDependencyParserPlugin",
	(statement, source) => {
		parser.state.lastHarmonyImportOrder =
			(parser.state.lastHarmonyImportOrder || 0) + 1;

		//创建ConstDependency
		const clearDep = new ConstDependency(
			parser.isAsiPosition(statement.range[0]) ? ";" : "",
			statement.range
		);
		clearDep.loc = statement.loc;
		parser.state.module.addPresentationalDependency(clearDep);
		parser.unsetAsiPosition(statement.range[1]);
		const assertions = getAssertions(statement);
		const sideEffectDep = new HarmonyImportSideEffectDependency(
			source,
			parser.state.lastHarmonyImportOrder,
			assertions
		);
		//设置代码位置
		sideEffectDep.loc = statement.loc;
		parser.state.module.addDependency(sideEffectDep);
		return true;
	}
);

blockPreWalkImportDeclaration先会获取module名字,如import { some } form "loadsh" , source.value就为loadsh。然后会调用parser的import hook。

HarmonyImportDependencyParserPlugin做的事情就是创建HarmonyImportSideEffectDependency并收集到模块的Dependencies数组里。

HarmonyImportSideEffectDependency是用于生成最后的import代码。比如

import { c_var } from "./module_c"

//生成如下代码

/* harmony import */ var _module_c__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./module_c */ "./module_c.js");

之后会根据不同的Import specifier给importSpecifier hooks传不同的参数,该钩子的插件会设置import变量的scope,跟平常变量不同的是,它会创建VariableInfo对象set到this.scope.definitions。VariableInfo除了包含作用域,还包含模块名字等信息。

如果遇到的是export,也会执行上述操作,不同的地方在于会创建不同的Dependency。

walkStatements

walkStatement(statement) {
	this.statementPath.push(statement);
	if (this.hooks.statement.call(statement) !== undefined) {
		this.prevStatement = this.statementPath.pop();
		return;
	}
	switch (statement.type) {
		case "BlockStatement":
			this.walkBlockStatement(statement);
			break;
		case "ClassDeclaration":
			this.walkClassDeclaration(statement);
			break;
		case "ExportDefaultDeclaration":
			this.walkExportDefaultDeclaration(statement);
			break;
		case "ExportNamedDeclaration":
			this.walkExportNamedDeclaration(statement);
			break;
		case "ExpressionStatement":
			this.walkExpressionStatement(statement);
			break;
		case "ForStatement":
			this.walkForStatement(statement);
			break;
		case "FunctionDeclaration":
			this.walkFunctionDeclaration(statement);
			break;
		case "SwitchStatement":
			this.walkSwitchStatement(statement);
			break;
    case "VariableDeclaration":
				this.walkVariableDeclaration(statement);
				break;
		//...
	}
	this.prevStatement = this.statementPath.pop();
}

walkStatements除了会遍历块内变量并定义对应scope,还会做其他事情。

webpack很聪明,虽然有import,但如果没有使用的话,也不会去请求依赖,除非有变量引用了它。

当遇到VariableDeclaration,会从scope拿出这个变量引用信息,如果取出来的是VariableInfo,说明是import的依赖,就会调用相关插件创建HarmonyImportSpecifierDependency并添加到module.dependencies

如果遇到FunctionDeclaration,并且为ImportExpression。说明是个dynamic-imports,然后会调用ImportParserPlugin插件进行处理。
为了支持magic commentsImportParserPlugin会解析出comments,并对其进行相关设置。之后会创建AsyncDependenciesBlock,平常的import会添加到module.dependencies,但是动态导入是添加到module.block,而且处理优先级也在最后。

到这里,buildModule的事情就基本完成了,将会回到buildModule回调。

this.buildModule(module, err => {
  //...

  this.processModuleDependencies(module, err => {
    if (err) {
      return callback(err);
    }
    callback(null, module);
  });
});

processModuleDependencies AsyncQueue

在buildModule里会调用processModuleDependencies,它是个AsyncQueue,processor为_processModuleDependencies

_processModuleDependencies(module, callback) {
  /** @type {Array<{factory: ModuleFactory, dependencies: Dependency[], originModule: Module|null}>} */
  const sortedDependencies = [];

  /** @type {DependenciesBlock} */
  let currentBlock;

  /** @type {Map<ModuleFactory, Map<string, Dependency[]>>} */
  let dependencies;
  //...

  try {
    /** @type {DependenciesBlock[]} */
    const queue = [module];
    do {
      const block = queue.pop();
      if (block.dependencies) {
        currentBlock = block;
        let i = 0;
        for (const dep of block.dependencies) processDependency(dep, i++);
      }
      if (block.blocks) {
        for (const b of block.blocks) queue.push(b);
      }
    } while (queue.length !== 0);
  } catch (e) {
    return callback(e);
  }

  if (--inProgressSorting === 0) onDependenciesSorted();
}

_processModuleDependencies会循环模块的dependencies和block。前面说过dependencies里是正常的import,block里是Dynamic import,它们都会经过processDependency进行处理。

因为在parser的时候添加HarmonyImportSpecifierDependency依赖是根据代码引用位置,在dependencies里是乱序的。所以processDependency的作用就是根据import前后位置进行排序,然后将相同模块的几个依赖放在一起。

排序完后调用onDependenciesSorted处理所有外部依赖

const onDependenciesSorted = err => {
  if (err) return callback(err);

  // early exit without changing parallelism back and forth
  if (sortedDependencies.length === 0 && inProgressTransitive === 1) {
    return callback();
  }

  // This is nested so we need to allow one additional task
  this.processDependenciesQueue.increaseParallelism();

  for (const item of sortedDependencies) {
    inProgressTransitive++;
    this.handleModuleCreation(item, err => {
      // In V8, the Error objects keep a reference to the functions on the stack. These warnings &
      // errors are created inside closures that keep a reference to the Compilation, so errors are
      // leaking the Compilation object.
      if (err && this.bail) {
        if (inProgressTransitive <= 0) return;
        inProgressTransitive = -1;
        // eslint-disable-next-line no-self-assign
        err.stack = err.stack;
        onTransitiveTasksFinished(err);
        return;
      }
      if (--inProgressTransitive === 0) onTransitiveTasksFinished();
    });
  }
  if (--inProgressTransitive === 0) onTransitiveTasksFinished();
};

onDependenciesSorted会循环依赖,并执行handleModuleCreation,这个函数不就是最开始处理入口模块的吗?是的,从开头的handleModuleCreation到这就是webpack递归处理所有引入模块的过程。

当所有依赖都处理完后就会从processDependenciesQueue往后回收栈,回到buildQueue,factorizeModule,回到最初调用handleModuleCreation的地方

addModuleTree({ context, dependency, contextInfo }, callback) {
  //...

  this.handleModuleCreation(
    (//...)
    ,
    (err, result) => {
      if (err && this.bail) {
        callback(err);
        this.buildQueue.stop();
        this.rebuildQueue.stop();
        this.processDependenciesQueue.stop();
        this.factorizeQueue.stop();
      } else if (!err && result) {
        callback(null, result);
      } else {
        callback();
      }
    }
  );
}

handleModuleCreation会停止所有任务队列并回调到make hook

finishMake

//compiler.js

compile(callback) {
 //...
 err => {
    if (err) return callback(err);

    //...
    logger.time("make hook");
    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 => {
                //...
            });
          });
        });
      });
    });
  });

make hooks之后是finishMake hooks,在这里面会调用compilation.finish。

compilation.finish里大部分代码都在输出日志,但里面有个finishModules hooks里有个重要的插件flagDependencyExportsPlugin。它会将所有模块的export信息添加到NormalModule对应的ModuleGraphModule里,之后在优化阶段的时候用于分析是否被使用,是否需要tree shaking。

到这里make的所有环节就结束了

总结

总的来说make环节主要就是在处理模块,webpack将处理模块分为几个步骤。

factorize会调用resolver去解析模块和loader的路径相关信息,并生成对应的paser和generator。

addModule会设置模块对应的ModuleGraph,ModuleGraph包含模块,外部依赖的引用信息,导出信息等。

buildModule会通过Loader-runner执行loaders,将资源转换成js能操作的目标,然后将源码paser成AST,并遍历AST Body,对不同的声明进行处理,在此期间会收集外部依赖,导出信息等。

processModuleDependencies会处理所有收集的依赖,并将依赖回到factorize,从而实现递归处理所有的模块

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions