为何DefinePlugin在html-loader配合extract-loader时未完成变量替换?
Let’s break down why you’re hitting this error and how to fix it.
The Root Cause
You’re right that webpack runs loaders right-to-left (bottom-to-top in the use array), but there’s a critical timing issue here:
DefinePluginoperates during webpack’s compilation phase: it scans the AST of processed modules and replaces matching identifiers after loaders have finished their work.extract-loaderworks differently: it executes the output of the preceding loader (html-loader) during the loader processing phase, before webpack runs its full compilation steps (including DefinePlugin’s variable replacements).
When you add extract-loader to the chain, html-loader first converts your ${TITLE} into raw JS code referencing the TITLE variable. Then extract-loader tries to execute this code immediately—but since DefinePlugin hasn’t had a chance to replace TITLE with your string value yet, you get the ReferenceError: TITLE is not defined error.
This doesn’t happen when using html-loader alone or with HtmlWebpackPlugin because:
- With just html-loader: the processed module goes through webpack’s full compilation, where DefinePlugin replaces
TITLEbefore the code is bundled. - With HtmlWebpackPlugin: the plugin processes the template after webpack’s compilation is complete, so DefinePlugin’s replacements are already applied.
Fixes to Try
1. Use HtmlWebpackPlugin (Recommended)
This is the simplest and most idiomatic solution for generating standalone HTML files with webpack. It automatically handles the timing issue and doesn’t require extract-loader:
const { DefinePlugin } = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'none', entry: ["./src/index.js"], // Keep your JS entry, or omit if you only need HTML module: { rules: [ { test: /\.html$/, use: [ { loader: "html-loader", options: { interpolate: true } } ] } ] }, plugins: [ new DefinePlugin({ TITLE: JSON.stringify('My page title') }), new HtmlWebpackPlugin({ template: './src/index.html' // Point to your HTML template }) ] };
2. Pre-Replace Variables with string-replace-loader
Add string-replace-loader to your loader chain to replace ${TITLE} before html-loader and extract-loader process the file. This skips the need for DefinePlugin to handle the HTML variable:
const { DefinePlugin } = require('webpack'); module.exports = { mode: 'none', entry: ["./src/index.html"], module: { rules: [ { test: /\.html$/, use: [ "extract-loader", { loader: "html-loader", options: { interpolate: true } }, { loader: "string-replace-loader", options: { search: /\${\s*TITLE\s*}/, // Match `${ TITLE }` with optional whitespace replace: JSON.stringify('My page title'), } } ] } ] }, // Keep DefinePlugin if you need it for other files plugins: [new DefinePlugin({ TITLE: JSON.stringify('My page title') })], };
3. Switch to a Template Loader Like ejs-loader
Use a loader that supports passing template parameters directly, avoiding dependency on DefinePlugin’s compilation-phase replacement:
First, update your index.html to use EJS-style interpolation:
<!DOCTYPE html> <html> <head> <title><%= TITLE %></title> </head> <body> </body> </html>
Then adjust your webpack config:
module.exports = { mode: 'none', entry: ["./src/index.html"], module: { rules: [ { test: /\.html$/, use: [ "extract-loader", { loader: "ejs-loader", options: { data: { TITLE: 'My page title' // Pass your variables directly here } } } ] } ] } };
内容的提问来源于stack exchange,提问作者NReilingh

