HTML Loader provides preprocessor and postprocessor hooks for custom HTML transformations, enabling integration with template engines and other processing tools.
Process HTML content before html-loader handles asset imports and minification.
/**
* Preprocessor function for custom HTML transformations before processing
* @param content - Raw HTML content as a string
* @param loaderContext - Webpack loader context object
* @returns Processed HTML content (must be valid HTML)
*/
type PreprocessorFunction = (
content: string,
loaderContext: LoaderContext
) => string | Promise<string>;
interface LoaderContext {
/** Path to the current file being processed */
resourcePath: string;
/** Directory context for the loader */
context: string;
/** Webpack mode (development/production) */
mode?: string;
/** Function to emit error messages */
emitError: (error: Error) => void;
/** Get loader options */
getOptions: (schema?: object) => any;
}Usage Examples:
// Handlebars template processing
const Handlebars = require("handlebars");
{
test: /\.hbs$/i,
loader: "html-loader",
options: {
preprocessor: (content, loaderContext) => {
let result;
try {
result = Handlebars.compile(content)({
title: "My App",
version: "1.0.0"
});
} catch (error) {
loaderContext.emitError(error);
return content;
}
return result;
}
}
}
// Async preprocessing with PostHTML
const posthtml = require("posthtml");
const posthtmlWebp = require("posthtml-webp");
{
test: /\.html$/i,
loader: "html-loader",
options: {
preprocessor: async (content, loaderContext) => {
try {
const result = await posthtml()
.use(posthtmlWebp())
.process(content);
return result.html;
} catch (error) {
loaderContext.emitError(error);
return content;
}
}
}
}
// Environment-based processing
{
test: /\.html$/i,
loader: "html-loader",
options: {
preprocessor: (content, loaderContext) => {
const isDevelopment = loaderContext.mode === 'development';
// Remove analytics scripts in development
if (isDevelopment) {
content = content.replace(
/<script[^>]*google-analytics[^>]*>[\s\S]*?<\/script>/gi,
''
);
}
return content;
}
}
}Process the generated JavaScript module code after html-loader has handled asset imports and minification.
/**
* Postprocessor function for custom transformations of generated module code
* @param content - Generated JavaScript module code as a string
* @param loaderContext - Webpack loader context object
* @returns Processed JavaScript module code
*/
type PostprocessorFunction = (
content: string,
loaderContext: LoaderContext
) => string | Promise<string>;Generated Code Context:
The postprocessor receives the final JavaScript module code that html-loader generates, which includes:
Usage Examples:
// Template literal transformation
{
test: /\.html$/i,
loader: "html-loader",
options: {
postprocessor: (content, loaderContext) => {
// Detect if template literals are supported
const isTemplateLiteralSupported = content[0] === "`";
// Transform <%= %> syntax to template literal or string concatenation
return content
.replace(/<%=/g, isTemplateLiteralSupported ? `\${` : '" +')
.replace(/%>/g, isTemplateLiteralSupported ? "}" : '+ "');
}
}
}
// Add runtime transformations
{
test: /\.html$/i,
loader: "html-loader",
options: {
postprocessor: (content, loaderContext) => {
// Add custom runtime processing
const runtimeCode = `
// Custom runtime transformation
function processHtml(html) {
return html.replace(/{{(\w+)}}/g, (match, key) => {
return window.templateVars && window.templateVars[key] || match;
});
}
`;
// Wrap the exported HTML in the processing function
return content.replace(
/export default (.*);$/,
`${runtimeCode}\nexport default processHtml($1);`
);
}
}
}
// Async postprocessing
{
test: /\.html$/i,
loader: "html-loader",
options: {
postprocessor: async (content, loaderContext) => {
const processedContent = await someAsyncTransformation(content);
return processedContent;
}
}
}Common patterns for integrating template engines with html-loader.
/**
* Template engine integration patterns
*/Handlebars Integration:
const Handlebars = require("handlebars");
// Register helpers
Handlebars.registerHelper('json', function(context) {
return JSON.stringify(context);
});
{
test: /\.hbs$/i,
loader: "html-loader",
options: {
preprocessor: (content, loaderContext) => {
const template = Handlebars.compile(content);
// Get data from various sources
const templateData = {
// From loader context
filename: path.basename(loaderContext.resourcePath),
// From environment
isDev: process.env.NODE_ENV === 'development',
// From package.json
version: require('./package.json').version
};
try {
return template(templateData);
} catch (error) {
loaderContext.emitError(new Error(`Handlebars error: ${error.message}`));
return content;
}
}
}
}Mustache Integration:
const Mustache = require("mustache");
{
test: /\.mustache$/i,
loader: "html-loader",
options: {
preprocessor: (content, loaderContext) => {
const view = {
title: "My Application",
items: ['Item 1', 'Item 2', 'Item 3']
};
return Mustache.render(content, view);
}
}
}EJS Integration:
const ejs = require("ejs");
{
test: /\.ejs$/i,
loader: "html-loader",
options: {
preprocessor: async (content, loaderContext) => {
try {
return await ejs.render(content, {
filename: loaderContext.resourcePath,
title: "My App",
env: process.env.NODE_ENV
});
} catch (error) {
loaderContext.emitError(error);
return content;
}
}
}
}Best practices for error handling in preprocessor and postprocessor functions.
/**
* Error handling patterns for content processing
*/Error Handling Examples:
{
test: /\.html$/i,
loader: "html-loader",
options: {
preprocessor: (content, loaderContext) => {
try {
// Your processing logic here
return processContent(content);
} catch (error) {
// Emit error to webpack
loaderContext.emitError(
new Error(`Preprocessor error in ${loaderContext.resourcePath}: ${error.message}`)
);
// Return original content to prevent build failure
return content;
}
},
postprocessor: (content, loaderContext) => {
try {
return transformGeneratedCode(content);
} catch (error) {
// Log error with context
const contextualError = new Error(
`Postprocessor error in ${loaderContext.resourcePath}: ${error.message}\n` +
`Generated code: ${content.substring(0, 200)}...`
);
loaderContext.emitError(contextualError);
return content;
}
}
}
}
// Async error handling
{
test: /\.html$/i,
loader: "html-loader",
options: {
preprocessor: async (content, loaderContext) => {
try {
const result = await asyncProcessing(content);
return result;
} catch (error) {
// Emit async errors properly
await loaderContext.emitError(error);
return content;
}
}
}
}Understanding the order of operations in html-loader.
/**
* Processing order in html-loader:
* 1. Preprocessor (if configured)
* 2. Asset/source processing (src, href, etc.)
* 3. HTML minification (if enabled)
* 4. JavaScript code generation
* 5. Postprocessor (if configured)
* 6. Module export
*/Timing Considerations:
// Preprocessor: Modify HTML before any html-loader processing
preprocessor: (content) => {
// Content is raw HTML
// Perfect for template compilation, variable substitution
return processedHtml;
},
// Postprocessor: Modify generated JavaScript
postprocessor: (content) => {
// Content is generated JavaScript module code
// Perfect for code transformations, additional exports
return processedJavaScript;
}When to Use Each:
Integration with Other Loaders:
// Multiple loaders with html-loader preprocessing
{
test: /\.html$/i,
use: [
{
loader: "html-loader",
options: {
preprocessor: (content) => {
// html-loader preprocessing
return content;
}
}
}
]
},
// Separate preprocessing loader + html-loader
{
test: /\.template$/i,
use: [
"html-loader",
{
loader: "template-loader", // Custom loader
options: { /* template options */ }
}
]
}