写给自己看的
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,
}))
做了下面两件事:
- 将
crates/rolldown/src/runtime/runtime-without-comments.js(运行时代码,用于加载、导出、转换CommonJS和ESM模块),中代码扫描解析 - 通过之前的消息通道返回(具体接受信息的代码会在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做了什么
- 实例化一个module_loader
- 创建一个扫描解析runtime代码的异步任务