背景
在面试的时候,面试官常常会问:webpack 的 Loader 和 Plugin 有什么区别?哈哈,不知道你还能不能回答得了。发现没,Loader 和 Plugin 常被并列讨论,因为它们都是扩展构建流程、改变最终产物的核心机制,另外,估计很多人不知道,其实 Loader 的执行是通过插件机制来实现对模块的处理。
在上一篇文章《Rspack 插件架构原理:从 Tapable 到 Rust Hook》中,我们深入剖析了 Rspack 如何兼容 webpack 的插件体系,并通过 Rust 实现高性能的 Hook 系统。
那么 Loader 是不是和 Plugin 类似,采用了类似的兼容策略?Rspack 又是如何在 Rust 中实现自己的 Loader 机制的?
在 Rspack 的 JavaScript 端 Compiler 构造函数中,有这样一行关键代码:
js
new JsLoaderRspackPlugin(this).apply(this);
这个 JsLoaderRspackPlugin 在整个 Loader 架构设计中扮演着什么角色?当 Rust 层需要执行 JavaScript 编写的 Loader 时,底层是如何调用?
带着这些问题,本文将带你深入 Rspack 的 Loader 相关的源码,逐步拆解其 Loader 架构的设计原理。另外你还将会收获:
- Rspack 是如何兼容 webpack Loader?
- Rust Loader Pipeline 是什么?它和 JavaScript Loader Runner 有什么不同?
- JavaScript Loader 是如何注册进来的?
- 当 Rust 层执行到某个 Loader 时,如何判断是否需要切换到 JavaScript Loader?
本篇文章的编写思路是这样,先回顾 Loader 的基本概念,让你复习一下基础,然后通过绘制整体架构与数据流程图让你建立全局的视角,最后结合整体架构图的每个阶段从代码入口出发,逐层剖析 Rspack Loader 的运行机制。
什么是 Loader
引用 Rspack 官方定义:Loader 是一种模块转换器,用于将各种类型的资源模块转换为 Rspack 能够理解和打包的格式。通过配置不同的 Loader,你可以扩展 Rspack 的能力,使其支持 JSX、Markdown、Sass、Less 等非标准 JavaScript 模块。
简而言之,Loader 是一个带有副作用的内容转译器。它接收原始文件内容,经过处理后输出新的内容(或元数据),供后续构建流程使用。
和 Plugin 类似,Loader 也根据使用场景分为多种类型,Rspack 兼容 webpack 的 Loader 规范,支持以下几种常见形式:
同步 Loader(Sync Loader)
这是最基础的 Loader 类型,可通过 return 或 this.callback() 同步返回结果:
js
module.exports = function (source, map?, meta?) {
return someSyncOperation(source);
};
虽然 return 写法简洁,但 this.callback() 更灵活,支持传递错误、source map 和元数据:
js
module.exports = function (source, map, meta) {
this.callback(null, someSyncOperation(source), map, meta);
// 调用 callback() 时需要返回 undefined,避免返回值冲突
return;
};
异步 Loader(Async Loader)
当需要执行异步操作(如读取文件、发起网络请求等)时,应使用异步 Loader。通过调用 this.async() 获取回调函数:
js
module.exports = function (source, map, meta) {
// 获取异步回调函数
const callback = this.async();
// 执行异步操作
someAsyncOperation(source, function (err, result) {
// 处理错误情况
if (err) return callback(err);
// 成功时返回处理结果
callback(null, result, map, meta);
});
};
异步回调同样支持传递 source map 和 meta 数据,确保构建信息完整传递。
ESM Loader
Rspack 支持使用 ES Module 语法编写 Loader。只需通过 export default 导出处理函数,并满足以下任一条件:
- 文件扩展名为
.mjs; - 或在
package.json中设置"type": "module"。
例如:
js
// loader.mjs
export default function (source) {
return transform(source);
}
Raw Loader
默认情况下,Rspack 会将文件内容以 UTF-8 字符串形式传入 Loader。但在处理二进制资源(如图片、字体、音频)时,需要直接操作原始 Buffer。
此时,可在 Loader 模块中导出 raw: true,告知 Rspack 以 Buffer 形式传递输入:
js
function rawLoader(content) {
// content 是 Buffer 类型
return processBinary(content);
}
rawLoader.raw = true;
module.exports = rawLoader;
Pitching Loader
Rspack 的 Loader 执行分为两个阶段:
- Normal 阶段:从右向左依次执行每个 Loader 的主函数;
- Pitch 阶段 :从左向右提前执行每个 Loader 的
pitch方法(如果存在)。
Pitch 阶段常用于跳过后续 Loader(例如缓存命中时),或仅基于资源路径做预处理,而不依赖前序 Loader 的输出。
Pitch 函数签名如下:
js
module.exports.pitch = function (remainRequest, precedingRequest, data) {
// ...
};
包含三个参数:
remainingRequest:当前 Loader 之后尚未处理的请求链(包括资源路径和后续 Loader);previousRequest:当前 Loader 之前已经处理过的请求链;data:一个可在pitch与normal阶段之间共享的上下文对象。
这种双向执行机制赋予了 Loader 极高的灵活性,是高级资源处理的关键手段。
以上内容大部分是基于 Rspack 官方文档整理的。如你需深入了解,建议查阅 Rspack 官网 Loader 文档。
整体架构概览
还记得在《Rspack 原理:webpack,我为什么不要你》文章中,我们介绍了 Rspack 的核心执行流程,主要分为初始化阶段、桥接阶段、构建阶段和生成阶段。Loader 的执行贯穿于这些阶段之中,通过插件机制实现对模块的处理。
下图展示了 Rspack 在整个构建过程中 Loader 执行流程的整体架构:

以上流程图呈现了从构建初始化到模块编译和 Loader 执行相关的关键步骤。我们对照流程图逐阶段解释下:
初始化阶段
此阶段主要完成 JavaScript 编译器(Compiler)的创建和基础插件的注册。执行流程如下:
- 调用
createCompiler()创建编译器实例; - 在 Compiler 构造函数里实例化
JsLoaderRspackPlugin插件并加入到this.#builtinPlugins数组; - 将
runLoaders方法绑定至Compiler上,作为后续 Rust 层执行 JavaScript Loader 的统一入口。
桥接阶段
本阶段是 Rspack 实现 JavaScript 与 Rust 双端协同的关键环节,负责连接 JavaScript 层与 Rust 层。Rspack 利用该阶段传递了关键参数 this.#builtinPlugins。执行流程如下:
- 创建
JsCompiler实例; - 传入
this.#builtinPlugins作为JsCompiler的第三个参数传递给 Rust 层; - 实例化
JsLoaderRunnerGetter,用于获取 JavaScript Loader; - 创建 Rust 层的
JsLoaderRspackPlugin,并注册内置的 Rust Loader 插件; - Rust 端的
JsLoaderRspackPlugin在apply()方法中注册多个关键 Hooks(loader_should_yield、loader_yield等),用于在编译流程中判断和执行 JavaScript Loader。
构建阶段
这是 Loader 实际执行的核心阶段,发生在模块构建过程中。执行流程如下:
- 触发
compilation.make(),开始模块解析与构建; - 调用
NormalModule.build()对每个模块进行处理; - 创建
RspackLoaderRunnerPlugin,负责协调 Loader 的执行逻辑; - 调用
run_loaders()方法,启动 Loader 链的执行流程。
以上涉及的调用方法比较多,一下子可能不太好理解,不过没关系,这里你只需要有个全局概念就行,大概知道每个阶段都和 Loader 执行有哪些关系,接下来我会逐一剖析以上每个阶段关键方法的执行细节。
插件注册与桥接机制
JsLoaderRspackPlugin
在初始化阶段中,我们介绍到了 JsLoaderRspackPlugin,JsLoaderRspackPlugin 是 Rspack 实现 JavaScript Loader 兼容性的内置插件。它的作用是:为 Rust 构建引擎提供一个回调入口,用于在需要时执行用户编写的 JavaScript Loader。它的实例化发生在 Compiler 构造函数:
js
class Compiler {
constructor(context: string, options: RspackOptionsNormalized) {
...
new JsLoaderRspackPlugin(this).apply(this);
...
}
}
这行代码完成了两件事:
- 创建插件实例;
- 立即调用其
apply(compiler)方法,将插件逻辑注入当前编译器上下文。
JsLoaderRspackPlugin 和传统的插件定义不一样,是通过 Rspack 内部的 create 工具函数生成的内置插件:
js
export const JsLoaderRspackPlugin = create(
BuiltinPluginName.JsLoaderRspackPlugin,
(compiler: Compiler) => runLoaders.bind(null, compiler),
/* Not Inheretable */
"thisCompilation"
);
其中最关键的部分是第二个参数:
js
(compiler: Compiler) => runLoaders.bind(null, compiler)
它返回一个 预绑定 compiler 实例的 runLoaders 函数,这个函数具有以下特点:
- 通过
bind方法实现函数预设参数,始终引用compiler参数; - 这个绑定后的函数后续会被传递给 Rust 侧,作为执行 JavaScript Loader 的回调入口;
- 在 Rust 需要运行某个 JavaScript Loader 时,只需调用
compiler._runLoader(loaderContext),无需再显式传入compiler。
这种设计实现了闭包式上下文绑定,确保 JavaScript 回调始终作用于正确的编译器实例。
最终,JsLoaderRspackPlugin 会被注册到内部插件列表 this.#builtinPlugins 中:
js
__internal__registerBuiltinPlugin(plugin: binding.BuiltinPlugin) {
this.#builtinPlugins.push(plugin);
}
这个数组充当了 JavaScript 层与 Rust 层之间的桥梁。在创建底层 Rust 编译器实例时,this.#builtinPlugins 会作为 JsCompiler 的第三个参数传递给 Rust 端:

Rust 端如何处理插件?
当 JsLoaderRspackPlugin 插件传递到 Rust 端后,Rspack 如何将其与自身的构建流程集成?特别是,Rust 原生插件与 JavaScript Loader 是如何协同工作的?答案在于 Rspack 自研的 Rust Loader Pipeline 机制。
在 Rust 端,被 JavaScript 端调用的 JsCompiler::new 接收插件列表并进行处理:
js
#[napi]
impl JsCompiler {
pub fn new(
// ...
builtin_plugins: Vec<BuiltinPlugin>, // 接收插件列表
// ...
) -> Result<Self> {
// ...
for bp in builtin_plugins { // 遍历处理每个插件
bp.append_to(env, &mut this, &mut plugins)?; // 调用 append_to
}
// ...
}
}
append_to 方法会处理每个插件,对于 JsLoaderRspackPlugin,append_to 会执行以下操作:
js
match name {
...
BuiltinPluginName::JsLoaderRspackPlugin => {
// 1. 将 JS 的 runLoaders 函数绑定到 compiler._runLoader
compiler_object.set_named_property("_runLoader", self.options)?;
// 2. 创建 JsLoaderRunnerGetter(用于跨语言调用)
let loader_runner_getter = JsLoaderRunnerGetter::new(&env)?;
// 3. 创建 Rust 插件实例并加入插件列表
plugins.push(JsLoaderRspackPlugin::new(loader_runner_getter).boxed());
}
...
}
注意以上的
compiler_object是 JavaScript 的compiler实例。
我们直接看代码注释来理解,以上代码的执行引出了两个 Rust 关键组件:JsLoaderRunnerGetter 和 JsLoaderRspackPlugin。
由于 Rust 与 JavaScript 运行在不同的执行环境,Rspack 必须解决如何让 Rust 安全调用 JavaScript 函数的问题。解决方案是使用 NAPI 的
ThreadsafeFunction,它允许在 Rust 的异步线程中安全地调用 JavaScript 函数。
JsLoaderRunnerGetter
JsLoaderRunnerGetter 负责从 JavaScript 端获取 runLoaders 函数,并封装为可在 Rust 中调用的形式:
js
impl JsLoaderRunnerGetter {
pub fn new(env: &Env) -> napi::Result<Self> {
// 创建一个 NAPI threadsafe function
// 这个函数可以在 Rust 的异步环境中安全地调用 JavaScript
let mut ts_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_threadsafe_function(
raw_env,
ptr::null_mut(),
ptr::null_mut(),
async_resource_name,
0,
1,
ptr::null_mut(),
None,
ptr::null_mut(),
Some(napi_js_callback), // 回调函数
&mut ts_fn,
)
},
"Failed to create threadsafe function"
)?;
Ok(Self { ts_fn })
}
JsLoaderRunnerGetter 通过 new() 函数创建一个 ThreadsafeFunction 对象,并将其保存在 JsLoaderRunnerGetter 实例中。
napi_create_threadsafe_function 的其他参数涉及到 NAPI 的 ThreadsafeFunction 相关知识,这里我们先忽略,我们只需要关注 Some(napi_js_callback) 参数就行。
注意:此时并没有真正拿到
runLoaders函数。这个ThreadsafeFunction的回调是napi_js_callback,它会在未来的某个时刻被触发。
napi_js_callback 会在 JavaScript 线程 中执行,它的作用是从 JavaScript 获取 _runLoader:
js
extern "C" fn napi_js_callback(
env: sys::napi_env,
_js_callback: sys::napi_value,
_context: *mut c_void,
data: *mut c_void,
) {
// 1. 获取 compiler 对象(通过 weak reference)
let compiler_object = unsafe {
let napi_value = ToNapiValue::to_napi_value(env, weak_reference.clone())?;
Object::from_napi_value(env, napi_value)?
};
// 2. 从 compiler._runLoader 拿到 JavaScript 函数
let run_loader = compiler_object
.get_named_property::<Function<JsLoaderContext, Promise<JsLoaderContext>>>("_runLoader")?;
// 3. 把这个 JavaScript 函数也包装成一个新的 ThreadsafeFunction(JsLoaderRunner),可以在 Rust 中调用
let ts_fn: JsLoaderRunner = run_loader
.build_threadsafe_function::<JsLoaderContext>()
.weak::<true>()
.callee_handled::<false>()
.max_queue_size::<0>()
.build()?;
// 4. 把这个新的 ThreadsafeFunction 传回给 Rust
Ok(ts_fn)
}
当我们在 Rust 中调用这个 ThreadsafeFunction 时,NAPI 会把 napi_js_callback 排队到 JavaScript 主线程执行。
在这个回调里,我们才真正访问 compiler._runLoader,并把它转换成另一个 可被 Rust 调用的 ThreadsafeFunction(即以上的 JsLoaderRunner)。
JsLoaderRunnerGetter.call() 会触发上述流程,拿到最终的 JsLoaderRunner:
js
pub async fn call(&self, compiler_id: &CompilerId) -> napi::Result<JsLoaderRunner> {
// 1. 调用创建的 ThreadsafeFunction(即触发 napi_js_callback)
unsafe {
let _ = napi_call_threadsafe_function(
self.ts_fn, // 这就是刚才通过 new() 创建的 ThreadsafeFunction
Box::into_raw(Box::new(data)).cast(),
sys::ThreadsafeFunctionCallMode::nonblocking,
);
}
// 2. 等待结果
let result = rx.await.to_napi_result()?;
let loader_runner = result?;
Ok(loader_runner) // 返回可以在 Rust 中调用的 JavaScript 函数
}
后续就可以直接用它来执行 JavaScript Loader。
JsLoaderRspackPlugin
JsLoaderRspackPlugin 是 Rspack 中实现 JavaScript Loader 兼容性的调度插件。它本身不直接执行任何加载逻辑,而是通过 Hook 系统,在构建流程中动态判断是否需要切换到 JavaScript 环境,并借助 JsLoaderRunnerGetter 调用 JavaScript Loader,最终将执行结果同步回 Rust 构建上下文。
在插件初始化阶段,JsLoaderRspackPlugin 会向模块构建流程注册两个关键的 Hook:
js
fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
// Hook 1:判断当前 loader 是否需要让出控制权给 JavaScript
ctx
.normal_module_hooks
.loader_should_yield
.tap(scheduler::loader_should_yield::new(self));
// Hook 2:实际执行 JavaScript Loader
ctx
.normal_module_hooks
.loader_yield
.tap(scheduler::loader_yield::new(self));
Ok(())
}
解释下这两个关键 Hook:
loader_should_yield:判断当前 Loader 是否需要切换到 JavaScript 执行;loader_yield:负责实际调用 JavaScript Loader 并处理其返回结果。
当 Rust Loader Pipeline 执行到某个 Loader 时,loader_should_yield 会被调用,用来判断是否需要切换到 JavaScript Loader:
js
#[plugin_hook(NormalModuleLoaderShouldYield for JsLoaderRspackPlugin, tracing=false)]
pub(crate) async fn loader_should_yield(
&self,
loader_context: &LoaderContext<RunnerContext>,
) -> Result<Option<bool>> {
match loader_context.state() {
LoaderState::Pitching => {
let current_loader = loader_context.current_loader();
if current_loader.request().starts_with(BUILTIN_LOADER_PREFIX) {
Ok(Some(false)) // 内置 Loader,由 Rust 直接处理
} else {
Ok(Some(true)) // 用户自定义 JavaScript Loader,需 yield 到 JavaScript Loader
}
}
LoaderState::Normal => Ok(Some(
!loader_context.current_loader().request().starts_with(BUILTIN_LOADER_PREFIX),
)),
}
}
Rust 是通过 #[plugin_hook] 宏注册 Hook。
这里的判断逻辑很简单:
- 若 Loader 的请求路径以
builtin:开头(例如builtin:css-loader),则视为 Rust 原生内置 Loader,无需切换; - 否则,视为 JavaScript 编写的 Loader,需暂停 Rust 流程,交由 JavaScript 引擎执行。
一旦判定需要执行 JavaScript Loader,Rspack 就会调用 loader_yield,完成完整的跨语言调用流程:
js
#[plugin_hook(NormalModuleLoaderShouldYield for JsLoaderRspackPlugin, tracing=false)]
pub(crate) async fn loader_yield(
&self,
loader_context: &mut LoaderContext<RunnerContext>,
) -> Result<()> {
// 1. 获取或初始化 JavaScript Loader Runner
let runner = runner
.get_or_try_init(|| async {
let compiler_id = self.compiler_id.get().unwrap();
self.runner_getter.call(compiler_id).await // 使用 JsLoaderRunnerGetter 获取
})
.await?;
// 2. 调用 JavaScript 的 runLoaders 函数
let new_cx = runner
.call_async(loader_context.try_into()?) // 调用 JavaScript 函数
.await?
.await?;
// 3. 将执行结果合并回 Rust 上下文
merge_loader_context(loader_context, new_cx)?;
Ok(())
}
这个过程分为三步:
- 获取 Loader Runner:调用
self.runner_getter.call(compiler_id),利用 NAPIThreadsafeFunction机制,在 JavaScript 主线程中获取compiler._runLoader,并封装为可在 Rust 中安全调用的JsLoaderRunner; - 调用 JavaScript 函数:将 Rust 的
LoaderContext转换为 JavaScript 的JsLoaderContext,然后调用 JavaScript 的 runLoaders 函数; - 合并执行结果:将 JavaScript 执行的结果(
content、sourceMap、additionalData等)合并回 Rust 的LoaderContext。
JsLoaderRspackPlugin 通过以上三步,实现了 Rust 高性能 Loader 与 JavaScript Loader 生态兼容。
状态机驱动的 Rust Loader Pipeline
在深入理解 JavaScript Loader 的桥接机制后,其实我们差不多理解了 Rspack 的 Loader 执行机制了,接下来我们再来一起看看 Rspack 内置的 Rust Loader 是如何定义,以及 Rust Loader 的执行流程又是怎样的。
Rspack 构建了一套状态机驱动的 Loader Pipeline,统一管理所有 Loader(无论是 Rust 内置还是 JavaScript 编写的)的执行流程。这种设计不仅保证了执行顺序的正确性,还处理了 Rust 和 JavaScript 之间的切换。
状态机定义
Rspack 的 Loader Pipeline 使用状态机来管理执行流程,定义了五个状态:
js
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum State {
Init, // 初始状态
Pitching, // 正在执行 pitch 阶段(从左到右)
ProcessResource, // 正在读取资源文件内容
Normal, // 正在执行 normal 阶段(从右到左)
Finished, // 执行完成
}
是不是感觉非常熟悉,其实和 JavaScript Loader 一样,以上每个状态都有明确的职责:
- Init :Pipeline 的入口点,立即转换到
Pitching状态; - Pitching :从左到右执行每个 Loader 的
pitch方法; - ProcessResource :当所有
pitch阶段完成后,读取原始资源文件内容; - Normal :从右到左执行每个 Loader 的
run方法; - Finished:Pipeline 完成,退出循环。
状态的流转是由 transition() 方法控制,确保 Loader Pipeline 始终沿着合法路径推进:
js
impl State {
pub(crate) fn transition(&mut self, next: State) {
*self = match (*self, next) {
(State::Init, State::Pitching) => State::Pitching,
(State::Pitching, State::ProcessResource) => State::ProcessResource,
(State::Pitching, State::Normal) => State::Normal, // pitch 返回内容,跳过读取
(State::ProcessResource, State::Normal) => State::Normal,
(State::Normal, State::Finished) => State::Finished,
_ => panic!("Unexpected loader runner state (current: {self:?}, next: {next:?})"),
};
}
}
以上代码通过显式枚举所有允许的状态转移路径,禁止任何非法跳转。例如:
- 正常流程中,
Pitching之后应进入ProcessResource(读取资源); - 但如果某个 Loader 的
pitch函数返回了有效内容,Pipeline 就可以合法地从Pitching直接跳转到Normal,从而跳过资源读取阶段。
任何未被列出的转换(如从 Init 直接到 Finished)都会触发 panic,保障执行流程的正确性。
Pipeline 执行流程
有了以上状态定义和状态流转规则,Pipeline 的核心执行逻辑位于 run_loaders_impl(),通过一个 loop 不断推进状态机:
js
async fn run_loaders_impl<Context: Send>(
cx: &mut LoaderContext<Context>,
fs: Arc<dyn ReadableFileSystem>,
) -> Result<()> {
loop {
match cx.state {
State::Init => {
// 这就是前面介绍的 transition 状态转换
cx.state.transition(State::Pitching);
}
State::Pitching => {
// 从左到右执行每个 Loader 的 pitch
// 如果遇到 JavaScript Loader,会通过 start_yielding() 切换到 JavaScript
if cx.start_yielding().await? {
// 如果 JavaScript Loader 的 pitch 返回了内容,直接跳到 Normal
if cx.content.is_some() {
cx.state.transition(State::Normal);
cx.loader_index -= 1;
}
continue;
}
...
}
State::ProcessResource => {
// 读取资源文件内容
process_resource(cx, fs.clone()).await?;
...
}
State::Normal => {
// 从右到左执行每个 Loader 的 run
// 如果遇到 JavaScript Loader,同样通过 start_yielding() 切换到 JavaScript
...
}
State::Finished => break,
}
}
Ok(())
}
这个循环会一直执行,直到状态变为 Finished。在每个状态中,都会根据当前状态执行相应的逻辑,例如如果遇到 JavaScript Loader 时,会通过 start_yielding() 切换到 JavaScript Loader,并在适当的时候调用 transition 转换到下一个状态。
完整的调用链:以 NormalModule.build() 开始
看完了以上介绍内容,估计你脑子里已经有点凌乱了,哈哈,没关系,接下来我们以完整的调用链来帮助你梳理整个执行流程,把知识点连接起来。
还记得前面整体架构概览 中呈现的构建流程图吗?在 compilation.make 阶段,Rspack 会为每个模块触发构建,NormalModule.build() 是核心入口。该方法最终会调用 run_loaders(),启动整个 Loader Pipeline。

注意图中创建的 RspackLoaderRunnerPlugin ,等下会介绍到。
具体来说,run_loaders() 会进一步调用 run_loaders_impl()。在 run_loaders_impl 的循环处理中,每当遇到需要执行的 Loader 时,会执行如下逻辑:
js
if cx.start_yielding().instrument(span).await? {
// ...
}
其中 start_yielding() 方法会调用传入的 plugin:
js
async fn start_yielding(&mut self) -> Result<bool> {
if let Some(plugin) = &self.plugin
&& plugin.should_yield(self).await?
{
plugin.clone().start_yielding(self).await?;
return Ok(true);
}
Ok(false)
}
这里的 plugin 正是在 NormalModule.build() 阶段创建的 RspackLoaderRunnerPlugin(上图中的那个插件)。
RspackLoaderRunnerPlugin 实现了 LoaderRunnerPlugin trait,其 should_yield() 和 start_yielding() 方法会分别触发 Rspack 的插件系统:
js
async fn should_yield(&self, context: &LoaderContext<Self::Context>) -> Result<bool> {
let res = self
.plugin_driver
.normal_module_hooks
.loader_should_yield
.call(context)
.await?;
if let Some(res) = res {
return Ok(res);
}
Ok(false)
}
async fn start_yielding(&self, context: &mut LoaderContext<Self::Context>) -> Result<()> {
self
.plugin_driver
.normal_module_hooks
.loader_yield
.call(context)
.await
}
插件系统会调用所有注册的插件,包括 JsLoaderRspackPlugin。前面我们已经介绍过了 JsLoaderRspackPlugin 通过 #[plugin_hook] 宏注册了这两个 Hook:loader_yield 和 loader_should_yield。
当 loader_yield 钩子被触发时,JsLoaderRspackPlugin 会通过 JsLoaderRunnerGetter 从 JavaScript 端获取 _runLoaders 函数,并将其包装成线程安全的函数供 Rust 端调用。
总结
这是 Rspack 原理分析系列第四篇文章,说实话 Rust 语法代码看着是真难受,我也很高兴你能坚持看到结尾,开篇我们提到了好几个问题,相信这时的你应该都能一一解答。如果觉得有什么地方遗漏、疑惑,欢迎评论讨论。
最初写 Rspack 原理系列是因为 Rspack 解决了我项目中构建速度慢的痛点,让我对 Rspack 产生了兴趣,后续可能还会继续专注于在这个前端工程化领域。此外,我也计划写一个关于 NestJS 架构设计 的系列,结合 DDD(领域驱动设计) 理念,系统梳理和分享这几年在项目中积累的经验。如果你对此感兴趣,欢迎持续关注。