Component & Asset Orchestration
Archibald uses a three-phase asset processing lifecycle to ensure that the browser receives exactly the code it needs for the current page, either as external links or inlined "Critical" assets. This process spans from the initial build to the final server response.
Stage A: Build-Time Mapping (The Circle of Traceability)
Archibald relies on a "Circle of Traceability" to link your high-level component declarations to physical build artifacts:
- The Link: When you define a component using
Loadable, you provide amodulesarray (e.g.,modules: ['shop/Cart']). This string is the "Logical Module Path". - SplitChunks (The Engine): Rspack's internal
splitChunksoptimization analyzes the dynamicimport()call associated with that component and physically breaks the code into a granular chunk. - ReactLoadablePlugin (The Recorder): This plugin hooks into Rspack's
emitphase. It scans the resultingChunkGraph, looking specifically for dependencies triggered byimport(). When it finds the request for'shop/Cart', it records which physical chunk (e.g.,chunk.431.js) was created for it. - The Manifest: This mapping is emitted as
loadable.json.
Stage B: SSR Discovery Flow (The report() Mechanism)
The report() function is a side-effect collector that bridges the gap between the declarative React render pass and the imperative asset resolution pass.
Under the Hood: The report() Mechanism
The function is defined within Renderer.tsx and injected into the React component tree via the CaptureProvider (using a standard React Context).
1. What is the report() function?
It is a callback that accepts a CaptureModule object. Its internal implementation in the Renderer tracks the "life signs" of components:
const report = (module: CaptureModule) => {
// 1. Mark the component as "needed for hydration"
this.rendered.add(module.path);
// 2. Track specialized loading instructions
if (module.excluded) this.excluded.add(module.path);
if (module.preloaded) this.preloaded.add(module.path);
if (module.prefetched) this.prefetched.add(module.path);
};
2. Who calls it and when?
Every "Island" component (defined via Loadable) calls this function during its initialization/render phase on the server. As React "discovers" a Loadable component in the tree, it executes this logic:
// Inside Loadable.tsx
const context = useContext(__LoadableContext);
if (context) {
// modules is an array of logical paths, e.g., ['shop/components/Cart']
modules.forEach((path) => context.report({ path, ...options }));
}
3. What does it "output"?
The function returns void. Its "output" is the population of the Renderer instance's internal state. By the time the render pass is finished, the Renderer possesses a Set containing every logical path that appeared in the tree.
Stage C: Resolution & Optimization
Once the Renderer has collected the logical paths, it passes them to the RenderHelper. The framework then decides how to inject the assets based on the CaptureModule flags:
| Flag | Meaning | Final HTML Impact |
|---|---|---|
excluded: true | "I'll handle this myself" | No eager <script> is generated — the chunk loads lazily (via dynamic import()) only when its island hydrates on the client. Combine with preloaded: true (below) to additionally warm the chunk with a modulepreload hint. |
preloaded: true | "I need this ASAP" | Generates a <link rel="modulepreload"> in the <head> for this component's chunk, independent of the critical / optimization.preload strategy. A deliberate, per-component decision — see Per-component preload. The browser downloads it immediately and primes the module map; execution still waits for hydration. |
prefetched: true | "I might need this later" | Generates a <link rel="prefetch" as="script"> in the <head>. The browser downloads it during idle time. |
| Default | Standard Hydration | Generates a standard <script src="..."> (Linking) or inlines the content (Critical). |
Why
modulepreloadand notpreload as="script"? The framework's bundles are ES modules (<script type="module">).<link rel="modulepreload">is the matching hint: it fetches the module and its static-import graph and primes the browser's module map, whereaspreload as="script"only fetches the single file. It is the manifest-driven, string-emitted equivalent of React 19'spreloadModule(the framework resolves chunk URLs centrally in the server renderer, so it emits the tags directly rather than from within components).
4. How is this data used?
This is the most critical part: the Renderer does not know about .js files; it only knows about logical paths like shop/components/Cart.
- Context Transfer: The
RenderServicecallsrenderer.getContext(), which returns the populatedrendered,preloaded,prefetched, andexcludedSets. - Manifest Lookup: These Sets are passed to the
ScriptsPlugin. - The Bridge: The
ScriptsPluginreads theloadable.json(generated by Rspack). It says: "I see 'shop/components/Cart' was rendered. Looking at the manifest... that logical path belongs to 'chunk.43141.js'." - Final Injection: The physical tags are generated and injected into the HTML according to the flags.
The Asset Traceability Flow
The following diagram summarizes how a logical component path in your code eventually becomes a specific <script> or <style> tag in the browser.
Per-component preload (preloaded)
By default an excluded island chunk is not hinted at all: the browser only requests it when the island hydrates (on visible / click / hover / navigation) and triggers its dynamic import(). On slower connections this adds a request round-trip to the hydration critical path.
Rather than a blunt "preload every island" switch, preloading is a deliberate, per-component decision. Set preloaded: true on a component's Loadable definition and the renderer emits a <link rel="modulepreload"> for that chunk, so its JS is already in the browser cache by the time the island hydrates. Execution stays deferred — the hint only changes when the bytes are fetched, not when the module runs.
export default Loadable({
factory: () => import('shop/client/features/cms/components/header/CMSHeaderComponent'),
fallback: <HeaderSkeleton />,
preloaded: true // warm this chunk with <link rel="modulepreload">
});
Key properties
- Opt-in per component — nothing is preloaded unless a
Loadabledeclarespreloaded: true. - Independent of the critical strategy. It runs even when
critical: { modules: true }inlines the mainmoduleschunk, because island chunks are never inlined. - Production only — no effect in development.
- Composable with
excluded. A component stays a lazy island (excluded from the eager scripts) and also gets its chunk warmed.
Which components should opt in? Prefer the always-present, above-the-fold, interactive layout chrome whose JS you want ready the instant the user interacts — e.g. the header, main navigation, and the header layout template. Avoid opting in below-the-fold or rarely-interactive islands (a large product grid, the footer): preloading their chunks at modulepreload priority front-loads JS that competes with genuinely critical resources, which defeats the point of code-splitting them.
Build-Time Insights: loadable.json vs. SplitChunks
It is important to distinguish between Code Splitting and Asset Mapping:
- SplitChunks (The Engine): Rspack's internal
splitChunksoptimization analyzes yourimport()statements and physically breaks the code into granular chunks. - ReactLoadablePlugin (The Recorder): This plugin runs after the splitting logic. It scans the resulting
ChunkGraphto produce theloadable.jsonmanifest.- Without this manifest, the Server would have no way of knowing that a render call to a component named
Cartrequires the browser to downloadchunk.431.js. - The manifest provides the Traceability needed to link logical code paths to physical, hashed build artifacts.
- Without this manifest, the Server would have no way of knowing that a render call to a component named