HTML-шаблоны в Webpack представляют собой связующее звено между процессом сборки и конечной разметкой приложения. В отличие от статических HTML-файлов, шаблоны позволяют динамически формировать структуру страницы, подставлять данные сборки, подключать ассеты с хешами и управлять метаинформацией в зависимости от окружения.
Основная роль в этой системе принадлежит плагину HtmlWebpackPlugin, который генерирует HTML-файл на основе шаблона и автоматически подключает туда все собранные бандлы.
Webpack по умолчанию работает только с JavaScript-модулями, поэтому HTML-файлы требуют отдельной обработки. HtmlWebpackPlugin решает задачу создания HTML как части графа сборки.
Базовая конфигурация:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
})
]
};
Параметр template указывает исходный HTML-файл, который
становится шаблоном. Webpack не просто копирует его, а интерпретирует
как основу для генерации итогового документа.
HtmlWebpackPlugin поддерживает несколько подходов к шаблонизации:
По умолчанию используется lodash templating. Внутри HTML можно применять выражения:
<!DOCTYPE html>
<html>
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
Здесь <%= %> — синтаксис подстановки значений.
Хотя используется lodash template engine, синтаксис во многом совпадает с EJS:
<%= value %> — вывод значения<% if (...) { %> — логика<%- value %> — небезопасный вывод (без
экранирования)Пример динамической логики:
<body>
<% if (process.env.NODE_ENV === 'development') { %>
<div class="debug">Dev mode enabled</div>
<% } %>
<div id="root"></div>
</body>
Данные в шаблон передаются через templateParameters.
new HtmlWebpackPlugin({
template: './src/index.html',
templateParameters: {
appName: 'My App',
version: '1.0.0'
}
})
Использование в шаблоне:
<title><%= htmlWebpackPlugin.options.templateParameters.appName %></title>
<meta name="version" content="<%= htmlWebpackPlugin.options.templateParameters.version %>">
Для упрощения часто создают промежуточную структуру:
new HtmlWebpackPlugin({
template: './src/index.html',
templateParameters: (compilation, assets, assetTags, options) => {
return {
appName: process.env.APP_NAME,
buildHash: compilation.hash
};
}
})
Переменные окружения используются для переключения конфигурации между режимами разработки и продакшена.
На уровне Node.js они доступны через process.env, но в
браузер они не попадают автоматически.
Webpack предоставляет механизм статической подстановки значений через DefinePlugin.
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env.API_URL': JSON.stringify('https://api.example.com')
})
]
};
После сборки все вхождения process.env.API_URL
заменяются на строку на этапе компиляции.
Для удобного управления переменными часто используется файл
.env.
Пример .env:
APP_NAME=MyApplication
API_URL=https://api.example.com
Подключение:
require('dotenv').config();
Дальше значения можно передавать в Webpack:
new webpack.DefinePlugin({
'process.env.APP_NAME': JSON.stringify(process.env.APP_NAME),
'process.env.API_URL': JSON.stringify(process.env.API_URL)
})
HtmlWebpackPlugin позволяет прокидывать значения окружения прямо в шаблон.
Конфигурация:
new HtmlWebpackPlugin({
template: './src/index.html',
templateParameters: {
appName: process.env.APP_NAME,
apiUrl: process.env.API_URL
}
})
Шаблон:
<meta name="app-name" content="<%= htmlWebpackPlugin.options.templateParameters.appName %>">
<meta name="api-url" content="<%= htmlWebpackPlugin.options.templateParameters.apiUrl %>">
Обычно логика строится вокруг NODE_ENV.
const isProd = process.env.NODE_ENV === 'production';
Webpack конфигурация:
new HtmlWebpackPlugin({
minify: isProd,
template: './src/index.html',
templateParameters: {
isProd,
title: isProd ? 'Production App' : 'Development App'
}
})
В шаблоне:
<title><%= htmlWebpackPlugin.options.templateParameters.title %></title>
<% if (!htmlWebpackPlugin.options.templateParameters.isProd) { %>
<script src="http://localhost:35729/livereload.js"></script>
<% } %>
Webpack часто используется для SPA, но поддерживает мультистраничные приложения.
plugins: [
new HtmlWebpackPlugin({
template: './src/pages/home.html',
filename: 'home.html',
chunks: ['home']
}),
new HtmlWebpackPlugin({
template: './src/pages/about.html',
filename: 'about.html',
chunks: ['about']
})
]
Каждая страница получает собственные переменные и набор бандлов.
Шаблоны позволяют формировать SEO-метаинформацию:
new HtmlWebpackPlugin({
template: './src/index.html',
templateParameters: {
title: process.env.APP_TITLE,
description: process.env.APP_DESCRIPTION
}
})
<meta name="description" content="<%= htmlWebpackPlugin.options.templateParameters.description %>">
Webpack предоставляет данные о сборке через
compilation.
templateParameters: (compilation) => ({
buildTime: new Date().toISOString(),
hash: compilation.hash,
version: process.env.APP_VERSION
})
В шаблоне:
<script>
window.__BUILD_INFO__ = {
buildTime: "<%= htmlWebpackPlugin.options.templateParameters.buildTime %>",
hash: "<%= htmlWebpackPlugin.options.templateParameters.hash %>"
};
</script>
Переменные окружения, попадающие в HTML и клиентский JavaScript, становятся публичными. Любые секреты, ключи доступа или приватные токены не должны передаваться через DefinePlugin или templateParameters.
Разделение обычно выглядит так:
TemplateParameters могут содержать функции:
templateParameters: {
formatDate: (date) => new Date(date).toLocaleDateString(),
env: process.env.NODE_ENV
}
Использование:
<span><%= htmlWebpackPlugin.options.templateParameters.formatDate(Date.now()) %></span>
Шаблоны часто используются для подключения CDN в зависимости от окружения:
const cdn = process.env.NODE_ENV === 'production'
? 'https://cdn.example.com'
: '';
new HtmlWebpackPlugin({
templateParameters: {
cdn
}
})
<script src="<%= htmlWebpackPlugin.options.templateParameters.cdn %>/runtime.js"></script>
Плагин предоставляет события жизненного цикла, позволяющие изменять итоговый HTML:
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
'MyPlugin',
(data, cb) => {
data.html = data.html.replace('<!-- inject -->', 'Injected content');
cb(null, data);
}
);
});
Это позволяет вмешиваться в процесс генерации уже после рендеринга шаблона.
Webpack часто добавляет хеши к файлам:
output: {
filename: '[name].[contenthash].js'
}
HtmlWebpackPlugin автоматически подставляет корректные ссылки в HTML,
но шаблон может учитывать хеши через
compilation.assets.
templateParameters: (compilation) => ({
assets: Object.keys(compilation.assets)
})
Шаблоны позволяют формировать разную структуру страницы:
<% if (htmlWebpackPlugin.options.templateParameters.env === 'development') { %>
<div class="dev-banner">Development build</div>
<% } %>
<% if (htmlWebpackPlugin.options.templateParameters.env === 'production') { %>
<div class="prod-banner">Production build</div>
<% } %>
Часто используется глобальный объект конфигурации:
new HtmlWebpackPlugin({
templateParameters: {
config: {
apiUrl: process.env.API_URL,
env: process.env.NODE_ENV
}
}
})
<script>
window.__CONFIG__ = <%= JSON.stringify(htmlWebpackPlugin.options.templateParameters.config) %>;
</script>
Такой подход позволяет централизованно управлять конфигурацией фронтенда без дополнительных запросов к серверу.