【Rolldown源码】六、rolldown核心包hooks之buildStart

443 阅读3分钟

写给自己看的Rolldown代码解析,有问题可以讨论。

首先调用插件中的build_start钩子。会调用所有插件中的buildStart方法

self.plugin_driver.build_start().await?;

全部流程

buildStart 结束于 scan方法内

let ret = ScanStage::new(
	Arc::clone(&self.options),
	Arc::clone(&self.plugin_driver),
	self.fs.clone(),
	Arc::clone(&self.resolver),
)
.scan()
.await;

上述代码首先创建一个扫描阶段的实例,然后同步调用了实例中的scan方法。

实例化

pub fn new(
	input_options: SharedOptions,
	plugin_driver: SharedPluginDriver,
	fs: OsFileSystem,
	resolver: SharedResolver,
) -> Self {
	Self { input_options, plugin_driver, fs, resolver, errors: vec![] }
}

ScanStage实例化什么基本什么都没做,就是将参数注入。

调用scan方法

pub async fn scan(&mut self) -> anyhow::Result<ScanStageOutput> {
	// 如果没有input,输出panic,并终止程序的执行
	assert!(!self.input_options.input.is_empty(), "You must supply options.input to rolldown");

	// 生成模块加载实例
	let module_loader = ModuleLoader::new(
		Arc::clone(&self.input_options),
		Arc::clone(&self.plugin_driver),
		self.fs.clone(),
		Arc::clone(&self.resolver),
	);

	// 解析用户定义的入口
	let user_entries = self.resolve_user_defined_entries().await?;

	// 下面的暂时不需要理解
	let ModuleLoaderOutput {
		module_table,
		entry_points,
		symbols,
		runtime,
		warnings,
		errors,
		ast_table,
	} = module_loader.fetch_all_modules(user_entries).await?;
	
	self.errors.extend(errors);

	Ok(ScanStageOutput {
		module_table,
		entry_points,
		symbols,
		runtime,
		warnings,
		ast_table,
		errors: std::mem::take(&mut self.errors),
	})

}

生成模块加载实例 module_loader

// 先创建一个容量为1024消息通道,可用于异步任务之间数据通信。
let (tx, rx) = tokio::sync::mpsc::channel::<Msg>(1024);

let tx_to_runtime_module = tx.clone();

// 然后创建一个可以在各个线程中共享的数据。
let common_data = Arc::new(TaskContext {
	input_options: Arc::clone(&input_options),
	tx,
	resolver,
	fs,
	plugin_driver,
});

let mut intermediate_normal_modules = IntermediateNormalModules::new();

let mut symbols = Symbols::default();

let runtime_id = intermediate_normal_modules.alloc_module_id(&mut symbols);

// 创建一个task
let task = RuntimeNormalModuleTask::new(runtime_id, tx_to_runtime_module);

{
	let handle = tokio::runtime::Handle::current();
	// 异步执行`run`方法。
	handle.spawn(async { task.run() });
}

Self {
	shared_context: common_data,
	rx,
	input_options,
	visited: FxHashMap::default(),
	runtime_id,
	// runtime module is always there
	remaining: 1,
	intermediate_normal_modules,
	external_modules: IndexVec::new(),
	symbols,
}

上面主要实例化了一个moduleLoader,并先异步运行一个task

看看这个初始化中task.run()中做了什么

let source: Arc<str> = include_str!("../runtime/runtime-without-comments.js").to_string().into();

let (ast, scope, scan_result, symbol, namespace_symbol) = self.make_ast(&source);

// ...数据拼入
let module = {...}

// 通过消息通道返回处理后的信息
self.tx.try_send(Msg::RuntimeNormalModuleDone(RuntimeNormalModuleTaskResult	{
	warnings: self.warnings,	
	ast_symbol: symbol,
	module,	
	runtime,
	ast,
}))

做了下面两件事:

  1. crates/rolldown/src/runtime/runtime-without-comments.js (运行时代码,用于加载、导出、转换CommonJS和ESM模块),中代码扫描解析
  2. 通过之前的消息通道返回(具体接受信息的代码会在fetch_all_modules的方法中使用,这边依赖remaining记数,所以默认返回1),这边猜测后面每个处理的文件无论导出还是加载,都需要用到里面的方法,所以现在这里处理了。

解析用户定义的入口 resolve_user_defined_entries

let resolver = &self.resolver;
let plugin_driver = &self.plugin_driver;


let resolved_ids = join_all(self.input_options.input.iter().map(|input_item|
	async move {
		struct Args<'a> {
		specifier: &'a str,
	}
	
	let args = Args { specifier: &input_item.import };
	
	// 下一个hook
	let resolved = resolve_id(
		resolver,
		plugin_driver,
		args.specifier,
		None,	
		HookResolveIdExtraOptions { is_entry: true, kind: ImportKind::Import },
	)
	.await;
	
	resolved.map(|info| (args, info.map(|info| (input_item.name.clone(), info))))

}))
.await;

// ...

这里用到option的input,具体的类型如下。

pub struct InputItem {
	pub name: Option<String>,
	pub import: String,
}

pub input: Vec<InputItem>

根据定义可以知道,input最终会处理成多个入口,每个入口包含import(入口地址),可能包含name(入口文件名)

	let resolved_ids = join_all(self.input_options.input.iter().map(|input_item| async move {
		
		let args = Args { specifier: &input_item.import };
		
		let resolved = resolve_id(
			resolver,
			plugin_driver,
			args.specifier,
			None,
			HookResolveIdExtraOptions { is_entry: true, kind: ImportKind::Import },
		)
		.await;
		
		resolved.map(|info| (args, info.map(|info| (input_item.name.clone(), info))))
	}))
	.await;

	// ...
}

这边循环使用resolve_id方法处理入口文件,resolve_id方法中调用了resolve_dynamic_import或resolve_id的钩子。

总结一下buildStart做了什么

  1. 实例化一个module_loader
  2. 创建一个扫描解析runtime代码的异步任务

image.png