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:

  1. ESM-Based Module System

    • Adopts modern ECMAScript Modules standards
    • Supports native module imports and exports
    • Enables better code splitting and on-demand loading
  2. Intelligent Dependency Collection

    • Dynamically collects dependencies based on actual rendering paths
    • Avoids unnecessary resource loading
    • Supports async components and dynamic imports
  3. Precise Resource Injection

    • Strictly controls resource loading order
    • Optimizes first-screen loading performance
    • Ensures reliable client-side hydration
  4. 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:

  1. 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
  2. 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:

  1. Multilingual Site Deployment

    main-domain.com → Default language main-domain.com/cn/ → Chinese site main-domain.com/en/ → English site
  2. 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:

  1. Inline Mode (default)

    • Embeds import maps directly in HTML
    • Ideal for small applications, reducing additional network requests
    • Immediately available when page loads
  2. 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:

  1. 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};
  2. A/B Testing

    • Supports different rendering logic for the same page
    • Facilitates user experience experiments
    • Enables flexible switching between rendering strategies
  3. Special Rendering Requirements

    • Supports custom rendering flows for specific pages
    • Adapts to performance optimization needs across scenarios
    • Enables finer-grained rendering control

Best Practices

  1. Obtaining RenderContext Instances

    • Always obtain instances via esmx.render()
    • Pass appropriate parameters as needed
    • Avoid manual instance creation
  2. 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
  3. Resource Injection

    • Strictly follow resource injection order
    • Never inject CSS in the body
    • Ensure importmap precedes moduleEntry
  4. 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