Шаблоны HTML и переменные окружения в шаблоне

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 не просто копирует его, а интерпретирует как основу для генерации итогового документа.


Типы шаблонов HTML

HtmlWebpackPlugin поддерживает несколько подходов к шаблонизации:

Логика через lodash template (по умолчанию)

По умолчанию используется lodash templating. Внутри HTML можно применять выражения:

<!DOCTYPE html>
<html>
<head>
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

Здесь <%= %> — синтаксис подстановки значений.


Использование EJS-подобного синтаксиса

Хотя используется 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>

Передача данных в шаблон через HtmlWebpackPlugin

Данные в шаблон передаются через 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
    };
  }
})

Переменные окружения в Webpack

Переменные окружения используются для переключения конфигурации между режимами разработки и продакшена.

На уровне Node.js они доступны через process.env, но в браузер они не попадают автоматически.


DefinePlugin и внедрение переменных

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 заменяются на строку на этапе компиляции.


Использование dotenv для управления окружением

Для удобного управления переменными часто используется файл .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)
})

Использование переменных окружения в HTML-шаблоне

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 %>">

Разделение окружений: development и production

Обычно логика строится вокруг 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>
<% } %>

Интеграция с несколькими HTML-страницами

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.

Разделение обычно выглядит так:

  • публичные переменные: API URL, режим, версия
  • приватные данные: хранятся только на сервере

Расширенные шаблоны: кастомные функции

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>

Хуки HtmlWebpackPlugin и модификация HTML

Плагин предоставляет события жизненного цикла, позволяющие изменять итоговый 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)
})

Условная генерация блоков HTML

Шаблоны позволяют формировать разную структуру страницы:

<% 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>
<% } %>

Передача переменных из Webpack в браузер

Часто используется глобальный объект конфигурации:

new HtmlWebpackPlugin({
  templateParameters: {
    config: {
      apiUrl: process.env.API_URL,
      env: process.env.NODE_ENV
    }
  }
})
<script>
  window.__CONFIG__ = <%= JSON.stringify(htmlWebpackPlugin.options.templateParameters.config) %>;
</script>

Такой подход позволяет централизованно управлять конфигурацией фронтенда без дополнительных запросов к серверу.