rspack源码解析
rspack源码解析
Build
webpack的构建流程简单而言就是从entry dependency
出发开始构建module树,每一个module会先扫描它的dependency
,这些dependency
有些是普通的模块js,有些是特殊的dependency
,例如用于esm
的import dependency
, 接着这些模块会递归的进行dependency
的收集,最终构建出module graph
以及chunk graph
, 一般而言,在webpack中一个chunk
对应一个entry,最终输出的资源也是一个chunk对应一个资源,所以在构建完整个chunk graph
之后就会遍历每个chunk,对其进行code generation
, 最后再输出资源
make
make阶段,首先会有4个队列,一个是factorize_queue
一个是add_queue
一个是build_queue
, 还有一个是process_dependencies_queue
这四个队列是按顺序清除的,也就是factorize_queue
里的每项任务做完再进行add_queue
里的任务,然后是build_queue
最后是process_dependencies_queue
.在make阶段每一个queue里的任务都会新建一个线程来处理,这也是rspack高性能的原因之一
factorize_queue
rspack_entry_plugin
会在make 这个hook添加force_build_dependency
,最开始的factorize_queue
是由entry_dependency
构成的,factorize_task
首先会创建一个resource_data
pub struct ResourceData {
/// Resource with absolute path, query and fragment
pub resource: String,
/// Absolute resource path only
pub resource_path: PathBuf,
/// Resource query with `?` prefix
pub resource_query: Option<String>,
/// Resource fragment with `#` prefix
pub resource_fragment: Option<String>,
pub resource_description: Option<DescriptionData>,
pub mimetype: Option<String>,
pub parameters: Option<String>,
pub encoding: Option<String>,
pub encoded_content: Option<String>,
scheme: OnceCell<Scheme>,
}
接着会开始收集loader,loader分为四类,分别是pre_loaders
, post_loaders
,normal_loaders
以及inline_loaders
,最后构建一个normal_module
build_queue
之前的factorize_task
是收集了模块构建需要的信息,例如路径,loaders
,build_task
就是真正开始构建,构建第一步就是执行相应的loader
,loader transform完成后开始解析
pub struct ParseResult {
pub dependencies: Vec<BoxDependency>,
pub presentational_dependencies: Vec<Box<dyn DependencyTemplate>>,
pub source: BoxSource,
pub analyze_result: OptimizeAnalyzeResult,
}
解析后会得到这个模块包含的依赖,转换后的代码, 以及性能分析结果(?)
add_queue
add_task
主要就是将module
添加到module graph
中
process_dependencies_queue
process_dependencies_task
主要是通过之前检测出来的依赖创建下一轮的factorize_task
seal
make后就是在seal阶段去生成最后的代码
code generation
因为之前在build阶段已经完成了loader的转换这里主要是针对之前扫描出来的dependency
, 例如比较常规的CommonJsDependency
它会把require('./answer')
转换为__webpack_require__("./src/answer.js")
process_runtime_requirements
之前code generation
每个模块的CodeGenerationResult
都会有runtimeRequirements
, 该流程会先以chunk纬度收集这些runtimeRequirement
, 接着调用additional_chunk_runtime_requirements
, 这个hook提供了特定情况的一些runtime,例如CommonJsChunkFormatPlugin
fn additional_chunk_runtime_requirements(
&self,
_ctx: PluginContext,
args: &mut AdditionalChunkRuntimeRequirementsArgs,
) -> PluginAdditionalChunkRuntimeRequirementsOutput {
let compilation = &mut args.compilation;
let chunk_ukey = args.chunk;
let runtime_requirements = &mut args.runtime_requirements;
let chunk = compilation
.chunk_by_ukey
.get(chunk_ukey)
.ok_or_else(|| anyhow!("chunk not found"))?;
if chunk.has_runtime(&compilation.chunk_group_by_ukey) {
return Ok(());
}
if compilation
.chunk_graph
.get_number_of_entry_modules(chunk_ukey)
> 0
{
runtime_requirements.insert(RuntimeGlobals::REQUIRE);
runtime_requirements.insert(RuntimeGlobals::STARTUP_ENTRYPOINT);
runtime_requirements.insert(RuntimeGlobals::EXTERNAL_INSTALL_CHUNK);
}
Ok(())
}
接着会出发runtime_requirement_in_tree
, 这个hook主要是根据已有runtime_requirements
注入其他有关联的runtime_requirements
, 接着为该chunk 添加runtime modules
create_chunk_assets
这几步做完之后就是真正创建资源了,这些资源就是将来要输出的文件
我们之前对每一个模块进行了code generation
以及为每一个chunk添加需要的runtime module
接下来就是需要去调用render_manifest
hook,对于js资源,这个hook先是需要拿到输出文件的文件名,根据用户的filename_template 拿到用户想要的文件名
最后就是模块代码的大整合
pub async fn render_main(&self, args: &rspack_core::RenderManifestArgs<'_>) -> Result<BoxSource> {
let compilation = args.compilation;
let chunk = args.chunk();
let runtime_requirements = compilation
.chunk_graph
.get_tree_runtime_requirements(&args.chunk_ukey);
let (module_source, chunk_init_fragments) =
render_chunk_modules(compilation, &args.chunk_ukey)?;
let (header, startup) = self.render_bootstrap(&args.chunk_ukey, args.compilation);
let mut sources = ConcatSource::default();
sources.add(RawSource::from("var __webpack_modules__ = "));
sources.add(module_source);
sources.add(RawSource::from("\n"));
sources.add(header);
sources.add(render_runtime_modules(compilation, &args.chunk_ukey)?);
if chunk.has_entry_module(&compilation.chunk_graph) {
let last_entry_module = compilation
.chunk_graph
.get_chunk_entry_modules_with_chunk_group_iterable(&chunk.ukey)
.keys()
.last()
.expect("should have last entry module");
if let Some(source) = compilation
.plugin_driver
.render_startup(RenderStartupArgs {
compilation,
chunk: &chunk.ukey,
module: *last_entry_module,
source: startup,
})?
{
sources.add(source);
}
if runtime_requirements.contains(RuntimeGlobals::RETURN_EXPORTS_FROM_RUNTIME) {
sources.add(RawSource::from("return __webpack_exports__;\n"));
}
}
let mut final_source = if compilation.options.output.iife {
render_iife(sources.boxed())
} else {
sources.boxed()
};
final_source = render_init_fragments(
final_source,
chunk_init_fragments,
&mut ChunkRenderContext {},
)?;
if let Some(source) = compilation.plugin_driver.render(RenderArgs {
compilation,
chunk: &args.chunk_ukey,
source: &final_source,
})? {
return Ok(source);
}
Ok(final_source)
}
render_manifest
后就可以通过emit_asset
添加资源了
process_assets(hook)
在最后输出到文件系统之前,需要对assets进行一些处理,例如minify就是通过这个hook对资源进行压缩的
Runtime module
在我们使用rspack
构建完一个项目后, 我们可以看到这样的模块
这些模块就是runtime module
, 它是为了让打包后的产物能够正常在浏览器上运行的模块
我们可以看到这样的runtime requirement
,每一个requirement
就是一个runtime module
, 在整个构建过程中会添加runtime requirement
, 最后在RuntimePlugin
里通过runtime_requirements_in_tree
为chunk添加runtime module
runtimeChunk
runtimeChunk是一种优化策略,它的可以把runtimeChunk单独分成一个chunk,我们可以通过配置中的optimization.runtimeChunk
进行配置,也可以给entry option
添加一个runtime
属性用来配置runtime chunk
的name,实际上optimization.runtimeChunk
也是通过对entry.runtime
配置为entryName
实现的
在seal阶段会进行codeSplit, 在正式codeSplit之前会进行prepare_input_entrypoints_and_modules
, 这里会添加namedChunk
,建立chunkGroup
, 创建runtimeChunk
,如果没有设置entry.runtime
,这个runtimeChunk
就是它自己