Inside san-loader: How San Files Are Split, Compiled, and Integrated in Webpack

This article provides a deep technical walkthrough of san-loader, explaining how the Baidu‑developed San framework’s .san files are parsed, split into template, script and style sections, processed by san-loader‑plugin, and finally compiled into browser‑ready code with support for options like compileTemplate, esModule, and CSS Modules.

Baidu App Technology
Baidu App Technology
Baidu App Technology
Inside san-loader: How San Files Are Split, Compiled, and Integrated in Webpack

Overview

San is Baidu’s MVVM front‑end framework, similar to Vue or React. Files with a .san extension contain template, script and style sections. san-loader is a webpack pre‑processor that parses a .san file, splits it into those three parts and forwards each part to the appropriate downstream loaders, ultimately producing browser‑executable code.

san-loader and san-loader‑plugin

san-loader consists of two components:

san-loader – only identifies .san files and extracts the three code blocks.

san-loader‑plugin – inserts the loader into the webpack compilation lifecycle so that it runs before other loaders, guaranteeing the required execution order.

Earlier versions bundled all downstream loaders inside san-loader, which caused maintenance problems when any loader was upgraded. The redesign separates responsibilities: the loader only splits files, while the plugin ensures correct ordering.

SanLoaderPlugin

Key webpack concepts used by the plugin:

compiler – the global compilation object created during webpack initialization, containing the configuration from webpack.config.js.

compilation – a new object generated for each build, holding information about changed files.

apply – the method webpack calls on a plugin instance; custom logic is placed inside this method.

class SanLoaderPlugin {
    apply(compiler) {
        // plugin logic
    }
}

The plugin clones the existing module.rules, inserts a rule for san-loader before the other rules, and then replaces compiler.options.module.rules with the new ordered list.

apply(compiler) {
    const rules = compiler.options.module.rules;
    const clonedRules = rules.filter(r => r !== rawSanRules)
        .map(rawRule => cloneRule(rawRule, new Map()));
    compiler.options.module.rules = [...clonedRules, ...rules];
}

After the plugin guarantees the order, the split blocks are processed by downstream loaders such as html-loader, css-loader, etc., using query strings like lang=html&san=&type=template.

Execution Flow

The loader uses htmlparser2 to parse the source and produce a descriptor object that groups AST nodes for template, script and style. The flow includes parsing, path resolution, import generation and final export assembly.

File Splitting

let descriptor = {};
for (let node of ast) {
    if (ELEMENT_TYPES.includes(node.type) && tagNames.includes(node.name)) {
        descriptor[node.name] = descriptor[node.name] || [];
        descriptor[node.name].push(node);
    }
}

Source‑map Generation

const MagicString = require('magic-string');
function stringManager(code, ast) {
    const s = new MagicString(code);
    traverse(ast, node => {
        if (node && node.type !== 'comment') {
            s.addSourcemapLocation(node.startIndex);
            if (node.children && node.children[0] && node.children[0].startIndex != null) {
                s.addSourcemapLocation(node.children[0].startIndex - 1);
            }
        }
    });
    return s;
}
const s = stringManager(source, ast);
const map = s.generateMap({
    file: path.basename(resourcePath),
    source: resourcePath,
    includeContent: true
});

Import Code Generation

For each script or template block the loader creates an import (ES module) or require (CommonJS) statement. For each style block it generates an import and records the variable name when CSS Modules are used.

code += `import script from '${resource}';
`;
code += `import template from '${resource}';
`;
code += `import style${i} from '${resource}';
`;
injectStyles.push(`style${i}`);

Template‑Script Combination

if (template) {
    if (typeof template === 'string') {
        dfns[i].template = template;
    } else if (Array.isArray(template)) {
        dfns[i].aPack = template;
    } else {
        dfns[i].aNode = template;
    }
}

Options

Two main options can be configured in webpack.config.js for san-loader:

compileTemplate – controls compilation of string‑based templates. Accepted values are 'none' (default), 'aPack' and 'aNode'. 'aPack' produces a compressed one‑dimensional array representation of the aNode tree, reducing bundle size.

esModule – when true, the loader emits ES‑module syntax; otherwise it uses CommonJS.

{
    test: /\.san$/,
    use: [{
        loader: require.resolve('san-hot-loader'),
        options: {
            compileTemplate: 'aPack',
            esModule: true
        }
    }]
}

CSS Modules Support

If a <style module> block is present, the loader treats it as a CSS Module. The generated import relies on css-loader configured with modules enabled.

{
    loader: 'css-loader',
    options: {
        modules: { localIdentName: '[local]_[hash:base64:5]' },
        localsConvention: 'camelCase',
        sourceMap: true
    }
}
if (isCSSModule) {
    code += `var style${i} = require('${resource}');
`;
    injectStyles.push(`style${i}`);
}

Reference Repositories

San: https://github.com/baidu/san san-loader: https://github.com/ecomfe/san-loader
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.

frontendWebpackloaderCSS Modulessan frameworktemplate compilationsan-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.