Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Hazlank opened this issue Apr 12, 2022 · 0 comments
Open

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

Hazlank opened this issue Apr 12, 2022 · 0 comments
Labels

Comments

@Hazlank
Copy link
Owner

Hazlank commented Apr 12, 2022

编译构建模块

上一篇讲了关于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\\babel-loader@8.2.3_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,从而实现递归处理所有的模块

@Hazlank Hazlank changed the title webpack5 源码详解 - 编译模块 [WIP]webpack5 源码详解 - 编译模块 Apr 12, 2022
@Hazlank Hazlank changed the title [WIP]webpack5 源码详解 - 编译模块 webpack5 源码详解 - 编译模块 Apr 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant