Render Context
RenderContext is a core class in the Esmx framework, primarily responsible for resource management and HTML generation during server-side rendering (SSR). It features the following key characteristics:
-
ESM-Based Module System
- Adopts modern ECMAScript Modules standards
- Supports native module imports and exports
- Enables better code splitting and on-demand loading
-
Intelligent Dependency Collection
- Dynamically collects dependencies based on actual rendering paths
- Avoids unnecessary resource loading
- Supports async components and dynamic imports
-
Precise Resource Injection
- Strictly controls resource loading order
- Optimizes first-screen loading performance
- Ensures reliable client-side hydration
-
Flexible Configuration Mechanism
- Supports dynamic base path configuration
- Provides multiple import mapping modes
- Adapts to various deployment scenarios
Usage
In the Esmx framework, developers typically don't need to create RenderContext instances directly. Instead, they obtain instances through the esmx.render()
method:
src/entry.node.ts
1async server(esmx) {
2 const server = http.createServer((req, res) => {
3 // Static file handling
4 esmx.middleware(req, res, async () => {
5 // Obtain RenderContext instance via esmx.render()
6 const rc = await esmx.render({
7 params: {
8 url: req.url
9 }
10 });
11 // Respond with HTML content
12 res.end(rc.html);
13 });
14 });
15}
Core Features
Dependency Collection
RenderContext implements an intelligent dependency collection mechanism that dynamically gathers dependencies based on actually rendered components rather than preloading all potential resources:
On-Demand Collection
- Automatically tracks and records module dependencies during component rendering
- Only collects CSS, JavaScript, and other resources actually used by the current page
- Uses
importMetaSet
to precisely record each component's module dependencies
- Supports dependency collection for async components and dynamic imports
Automated Processing
- Developers don't need to manually manage dependency collection
- The framework automatically collects dependency information during component rendering
- Processes all collected resources uniformly via the
commit()
method
- Automatically handles circular and duplicate dependencies
Performance Optimization
- Avoids loading unused modules, significantly reducing first-screen load time
- Precisely controls resource loading order to optimize rendering performance
- Automatically generates optimal import maps
- Supports resource preloading and on-demand loading strategies
Resource Injection
RenderContext provides multiple methods for injecting different types of resources, each carefully designed to optimize loading performance:
preload()
: Preloads CSS and JS resources with priority configuration support
css()
: Injects first-screen stylesheets with critical CSS extraction support
importmap()
: Injects module import maps with dynamic path resolution
moduleEntry()
: Injects client entry modules with multi-entry configuration support
modulePreload()
: Preloads module dependencies with on-demand loading strategies
Resource Injection Order
RenderContext strictly controls resource injection order based on browser mechanics and performance optimization considerations:
-
Head section:
preload()
: Preloads CSS and JS resources for early discovery by browsers
css()
: Injects first-screen stylesheets to ensure styling is ready when content renders
-
Body section:
importmap()
: Injects module import maps to define ESM module path resolution rules
moduleEntry()
: Injects client entry modules (must execute after importmap)
modulePreload()
: Preloads module dependencies (must execute after importmap)
Complete Rendering Workflow
A typical RenderContext usage flow:
src/entry.server.ts
1export default async (rc: RenderContext) => {
2 // 1. Render page content and collect dependencies
3 const app = createApp();
4 const html = await renderToString(app, {
5 importMetaSet: rc.importMetaSet
6 });
7
8 // 2. Commit dependency collection
9 await rc.commit();
10
11 // 3. Generate complete HTML
12 rc.html = `
13 <!DOCTYPE html>
14 <html>
15 <head>
16 ${rc.preload()}
17 ${rc.css()}
18 </head>
19 <body>
20 ${html}
21 ${rc.importmap()}
22 ${rc.moduleEntry()}
23 ${rc.modulePreload()}
24 </body>
25 </html>
26 `;
27};
Advanced Features
Base Path Configuration
RenderContext provides a flexible dynamic base path configuration mechanism that supports runtime setting of static resource base paths:
src/entry.node.ts
1const rc = await esmx.render({
2 base: '/esmx', // Set base path
3 params: {
4 url: req.url
5 }
6});
This mechanism is particularly useful for:
-
Multilingual Site Deployment
main-domain.com → Default language
main-domain.com/cn/ → Chinese site
main-domain.com/en/ → English site
-
Micro-Frontend Applications
- Supports flexible deployment of sub-applications under different paths
- Facilitates integration with various host applications
Import Map Modes
RenderContext offers two import map modes:
-
Inline Mode (default)
- Embeds import maps directly in HTML
- Ideal for small applications, reducing additional network requests
- Immediately available when page loads
-
JS Mode
- Loads import maps via external JavaScript files
- Suitable for large applications, leveraging browser caching
- Supports dynamic map content updates
Configure the appropriate mode:
src/entry.node.ts
1const rc = await esmx.render({
2 importmapMode: 'js', // 'inline' | 'js'
3 params: {
4 url: req.url
5 }
6});
Entry Function Configuration
RenderContext supports specifying server-side rendering entry functions via entryName
:
src/entry.node.ts
1const rc = await esmx.render({
2 entryName: 'mobile', // Specify mobile entry function
3 params: {
4 url: req.url
5 }
6});
This mechanism is particularly useful for:
-
Multi-Template Rendering
src/entry.server.ts
1// Mobile entry function
2export const mobile = async (rc: RenderContext) => {
3 // Mobile-specific rendering logic
4};
5
6// Desktop entry function
7export const desktop = async (rc: RenderContext) => {
8 // Desktop-specific rendering logic
9};
-
A/B Testing
- Supports different rendering logic for the same page
- Facilitates user experience experiments
- Enables flexible switching between rendering strategies
-
Special Rendering Requirements
- Supports custom rendering flows for specific pages
- Adapts to performance optimization needs across scenarios
- Enables finer-grained rendering control
Best Practices
-
Obtaining RenderContext Instances
- Always obtain instances via
esmx.render()
- Pass appropriate parameters as needed
- Avoid manual instance creation
-
Dependency Collection
- Ensure all modules correctly call
importMetaSet.add(import.meta)
- Call
commit()
immediately after rendering completes
- Use async components and dynamic imports wisely to optimize first-screen loading
-
Resource Injection
- Strictly follow resource injection order
- Never inject CSS in the body
- Ensure importmap precedes moduleEntry
-
Performance Optimization
- Use preload for critical resources
- Apply modulePreload judiciously to optimize module loading
- Avoid unnecessary resource loading
- Leverage browser caching mechanisms to optimize loading performance