rspack源码解析

fagaNovember 1, 2023About 5 min

rspack源码解析

Build

webpack的构建流程简单而言就是从entry dependency出发开始构建module树,每一个module会先扫描它的dependency,这些dependency有些是普通的模块js,有些是特殊的dependency,例如用于esmimport 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

image-20231101094701439

它会把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_manifesthook,对于js资源,这个hook先是需要拿到输出文件的文件名,根据用户的filename_templateopen in new window 拿到用户想要的文件名

最后就是模块代码的大整合

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构建完一个项目后, 我们可以看到这样的模块

image-20231028151550264

这些模块就是runtime module, 它是为了让打包后的产物能够正常在浏览器上运行的模块

我们可以看到这样的runtime requirement,每一个requirement就是一个runtime module, 在整个构建过程中会添加runtime requirement, 最后在RuntimePlugin 里通过runtime_requirements_in_tree为chunk添加runtime module

image-20231028151933331

runtimeChunk

runtimeChunkopen in new window是一种优化策略,它的可以把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就是它自己

Last update:
Contributors: faga
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.13.0