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.
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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
