Mastering Hot Module Replacement in San: Inside san-hot-loader

This article explains how to enable and configure Hot Module Replacement for the San framework using san-hot-loader, covering both San CLI and manual Webpack setups, the underlying HMR workflow, module detection logic, and the core code that injects runtime updates for components and stores.

Baidu App Technology
Baidu App Technology
Baidu App Technology
Mastering Hot Module Replacement in San: Inside san-hot-loader

Hot Module Replacement (HMR) lets developers update modules in the browser without a full page reload, dramatically improving development experience. The san-hot-loader plugin leverages Webpack's HMR API to provide hot‑update capabilities for San components and San stores.

Enabling HMR

First, enable HMR in Webpack (e.g., via webpack-dev-server) by adding hot: true to the devServer configuration:

devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    hot: true
},

For detailed Webpack HMR documentation, see the official "Module Hot Replacement" guide.

Using san-hot-loader

Via San CLI

San CLI bundles Webpack and includes san-hot-loader out of the box. Run san inspect in a project created with San CLI to view the current configuration.

Via Manual Webpack Configuration

When not using San CLI, add san-hot-loader to the module.rules array and enable HMR in the dev server:

module.exports = {
    // ... other webpack config
    module: {
        rules: [
            {
                test: /\.js$/,
                use: ['san-hot-loader']
            }
            // ... other loaders
        ]
    }
};

If your code uses ES7+ syntax, combine babel-loader with the san-hot-loader Babel plugin to inject HMR code:

module.exports = {
    // ... other webpack config
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        plugins: [require.resolve('san-hot-loader/lib/babel-plugin')]
                    }
                }]
            }
            // ... other loaders
        ]
    }
};

Plugin Configuration Options

Configuration is passed through the loader options, allowing fine‑grained control over which files receive hot updates:

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [{
                    loader: 'san-hot-loader',
                    options: {
                        enable: process.env.NODE_ENV === 'development',
                        component: { patterns: [/\.san\.js$/, 'auto'] },
                        store: { patterns: [function (resourcePath) {
                            return /\.store\.js$/.test(resourcePath);
                        }, 'auto'] }
                    }
                }]
            }
        ]
    }
};

How san-hot-loader Works

HMR Workflow Overview

Webpack watches source files; when a change is detected, it recompiles the affected module into memory. webpack-dev-server detects the new bundle and notifies the browser via a persistent WebSocket connection.

The HMR client receives a hash message, stores the new hash, and waits for an ok message.

Upon receiving ok, the client requests the list of updated modules (JSON) from the server.

The client fetches the updated module code via JSONP, compares it with the old version, and applies the update.

If any step fails, the client falls back to a full page reload.

HMR workflow diagram
HMR workflow diagram

Design of san-hot-loader

The plugin uses Webpack's Module API (not the Management API) because both San components and stores are treated as modules. It distinguishes between component and store updates and injects appropriate HMR handling code.

Key Code Paths

When a module is processed, the loader obtains its configuration and, if HMR is enabled, calls getHandler to select either ComponentHandler or StoreHandler. Both handlers share similar logic: match the module type, generate HMR code via a template ( tpl), and append it to the source.

Component handling example (simplified):

module.exports = function ({resourcePath}) {
    const context = path.dirname(resourcePath);
    const componentId = genId(resourcePath, context);
    return `
    if (module.hot) {
        var __HOT_API__ = require('${componentHmrPath}');
        var __HOT_UTILS__ = require('${utilsPath}');
        var __SAN_COMPONENT__ = __HOT_UTILS__.getExports(module);
        if (__SAN_COMPONENT__.template || __SAN_COMPONENT__.prototype.template) {
            module.hot.accept();
            __HOT_API__.install(require('san'));
            var __HMR_ID__ = '${componentId}';
            if (!module.hot.data) {
                __HOT_API__.createRecord(__HMR_ID__, __SAN_COMPONENT__);
            } else {
                __HOT_API__.hotReload(__HMR_ID__, __SAN_COMPONENT__);
            }
        }
    }
    `;
};

The runtime API ( __HOT_API__) provides install, createRecord, and hotReload methods that perform the actual component replacement.

Store handling follows a similar pattern, but also preserves the store's state and actions before swapping the implementation:

module.exports = function ({resourcePath}) {
    const context = path.dirname(resourcePath);
    const id = genId(resourcePath, context);
    return `
    if (module.hot) {
        var __SAN_STORE_ID__ = '${id}';
        var __SAN_STORE_CLIENT_API__ = require('${storeClientApiPath}');
        var __UTILS__ = require('${runtimeUtilPath}');
        module.hot.accept();
        var __SAN_STORE_INSTANCE__ = __UTILS__.getExports(module) || require('san-store').store;
        __SAN_STORE_CLIENT_API__.update(__SAN_STORE_ID__, __SAN_STORE_INSTANCE__);
    }
    `;
};

Module Matching Logic

To decide whether a file is a San component or store, the loader examines the AST. For components, it checks for:

Import of the san module.

Use of san.defineComponent, san.Component, or a class extending san.Component.

Export of the component as the default export.

Special comments (e.g., // san-hmr disable) can disable HMR for a file, while // san-hmr component can force it.

Store detection looks for imports from san-store and either:

Calls to store.addAction without exporting the store (global actions).

Exporting a new Store instance (custom store).

Conclusion

san-hot-loader provides a practical HMR solution for San projects, handling both component UI updates and store state preservation. Understanding its workflow, configuration options, and matching algorithms helps developers extend or debug the hot‑reload process effectively.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptfrontend developmentWebpackSANHot Module Replacementsan-hot-loader
Baidu App Technology
Written by

Baidu App Technology

Official Baidu App Tech Account

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.