Динамические имена точек входа

Webpack формирует граф зависимостей начиная с точки входа (entry). В классическом варианте конфигурации точка входа задаётся как статическое значение:

module.exports = {
  entry: './src/index.js'
};

В этом случае имя точки входа неявно определяется как main, а итоговый бандл получает имя main.js (если не переопределён output.filename).

При переходе к многостраничным приложениям, модульным архитектурам и динамической генерации сборок появляется необходимость управлять именами точек входа программно. Это приводит к использованию динамических entry-объектов, функций и шаблонов именования.


Статическая объектная форма entry и базовая подстановка имени

Самый простой способ задать несколько точек входа:

module.exports = {
  entry: {
    home: './src/home.js',
    about: './src/about.js',
    contact: './src/contact.js'
  },
  output: {
    filename: '[name].bundle.js'
  }
};

Ключи объекта (home, about, contact) становятся именами точек входа. Webpack использует их в качестве значения переменной [name].

Механизм подстановки:

  • [name] — имя entry
  • [id] — внутренний идентификатор чанка
  • [contenthash] — хэш содержимого
  • [chunkhash] — хэш чанка

Динамические имена точек входа фактически опираются на генерацию этого объекта.


Генерация entry через JavaScript

Конфигурация Webpack — это обычный Node.js модуль. Поэтому entry можно формировать программно:

const fs = require('fs');
const path = require('path');

const pagesDir = path.resolve(__dirname, 'src/pages');

const entries = fs.readdirSync(pagesDir).reduce((acc, page) => {
  const pageName = page.replace('.js', '');
  acc[pageName] = path.join(pagesDir, page);
  return acc;
}, {});

module.exports = {
  entry: entries,
  output: {
    filename: '[name].bundle.js'
  }
};

В этом подходе имена точек входа полностью зависят от структуры файловой системы. Добавление нового файла автоматически создаёт новый entry без изменения конфигурации.


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

Webpack сам по себе не интерпретирует glob-шаблоны в entry, но это можно реализовать через вспомогательные библиотеки:

const glob = require('glob');
const path = require('path');

const entries = {};

glob.sync('./src/pages/**/*.js').forEach(file => {
  const name = path.basename(file, '.js');
  entries[name] = file;
});

module.exports = {
  entry: entries,
  output: {
    filename: '[name].js'
  }
};

Здесь динамическое имя точки входа формируется из имени файла. Это базовый паттерн для CMS, админ-панелей и multi-page приложений.


Динамическая entry через функцию

Webpack поддерживает функциональный entry:

module.exports = {
  entry: () => {
    return {
      app: './src/app.js',
      admin: './src/admin.js'
    };
  }
};

Функция может быть синхронной или асинхронной:

module.exports = {
  entry: async () => {
    const response = await fetchConfigFromApi();
    return response.entries;
  }
};

Такая модель используется в сложных сборочных системах, где конфигурация зависит от внешних факторов:

  • окружение CI/CD
  • feature flags
  • серверная конфигурация
  • пользовательские профили сборки

Влияние динамических имен на output

Ключевой элемент динамических entry — согласование с output:

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: '[name].[contenthash].js'
}

Если имя entry формируется динамически, Webpack подставляет его в filename. Это означает:

  • структура выходных файлов зеркалит структуру entry
  • изменение имени entry изменяет URL ассетов
  • кеширование зависит от стабильности имен

Ошибка в именовании entry приводит к раздуванию кэша и дублированию бандлов.


Динамические точки входа и code splitting

Entry не следует путать с динамическими импортами (import()).

Entry формирует начальные чанки:

entry: {
  main: './src/main.js'
}

Dynamic import формирует асинхронные чанки:

import('./module.js').then(module => {
  module.init();
});

Однако динамические имена entry часто используются вместе с code splitting:

entry: {
  pageA: './src/pageA.js',
  pageB: './src/pageB.js'
},
optimization: {
  splitChunks: {
    chunks: 'all'
  }
}

В этом случае Webpack:

  • создаёт отдельные entry-бандлы
  • выделяет общие зависимости в shared chunks
  • использует имена entry как основу для именования файлов

Автоматизация multi-page архитектуры

Типичный кейс — генерация entry для страниц сайта:

const pages = [
  'home',
  'catalog',
  'product',
  'checkout'
];

const entry = pages.reduce((config, page) => {
  config[page] = `./src/pages/${page}/index.js`;
  return config;
}, {});

module.exports = {
  entry,
  output: {
    filename: '[name]/bundle.js'
  }
};

Здесь имя entry используется как сегмент пути:

dist/
  home/bundle.js
  catalog/bundle.js

Это приближает структуру сборки к структуре маршрутизации приложения.


Динамическое именование через контекст require.context

Webpack предоставляет механизм контекстов:

const modules = require.context('./pages', true, /\.js$/);

const entries = modules.keys().reduce((acc, key) => {
  const name = key.replace('./', '').replace('.js', '');
  acc[name] = modules(key);
  return acc;
}, {});

module.exports = {
  entry: entries
};

Особенности:

  • автоматическое сканирование директории
  • отсутствие ручного перечисления файлов
  • строгая привязка имени entry к пути

Параметризация entry через переменные окружения

Часто динамические имена точек входа зависят от окружения:

const isAdmin = process.env.BUILD_TARGET === 'admin';

module.exports = {
  entry: isAdmin
    ? { admin: './src/admin.js' }
    : { app: './src/app.js' }
};

Более сложный вариант:

const target = process.env.TARGET;

module.exports = {
  entry: {
    [target]: `./src/${target}/index.js`
  }
};

Здесь имя entry совпадает с логическим именем сборки, что упрощает CI-конфигурации.


Конфликты имен и стратегия нейминга

При динамической генерации entry возникает риск:

  • дублирования имён
  • пересечения namespace
  • нестабильности сборки

Решение — нормализация имени:

function normalizeName(filePath) {
  return filePath
    .replace('./src/', '')
    .replace('/index.js', '')
    .replace(/\//g, '-');
}

Пример:

./src/pages/user/profile/index.js → pages-user-profile

Такой подход гарантирует уникальность даже при глубокой вложенности.


Связь entry-имен с chunk graph

Webpack строит граф модулей, но entry-имя становится корнем графа чанка. Это означает:

  • изменение имени entry не влияет на содержимое модулей
  • но влияет на идентификацию чанка
  • влияет на кэширование и splitChunks

Пример:

entry: {
  dashboard: './src/app.js'
}

Если переименовать:

entry: {
  admin: './src/app.js'
}

модульный граф тот же, но выходной chunk — новый.


Стабильность имен в больших проектах

В production-сборках динамические имена требуют стабильности. Частые ошибки:

  • генерация имени из временного состояния (timestamp)
  • использование случайных значений
  • зависимость имени от порядка файловой системы

Корректный подход:

  • имя должно зависеть только от бизнес-логики
  • структура должна быть детерминированной
  • сортировка входных данных обязательна

Комбинирование нескольких стратегий генерации

Реальные проекты часто используют гибрид:

const fs = require('fs');
const path = require('path');

const baseEntries = {
  vendor: './src/vendor.js'
};

const pageEntries = fs.readdirSync('./src/pages')
  .sort()
  .reduce((acc, name) => {
    acc[name] = `./src/pages/${name}/index.js`;
    return acc;
  }, {});

module.exports = {
  entry: {
    ...baseEntries,
    ...pageEntries
  }
};

Такое разделение позволяет:

  • фиксировать системные entry
  • динамически расширять пользовательские страницы
  • контролировать стабильность ключевых бандлов

Влияние на HTML генерацию

При динамических entry почти всегда используется связка с HTML генераторами:

  • HtmlWebpackPlugin
  • multi-html pipelines

Пример логики:

  • каждый entry → отдельный HTML
  • имя entry → имя HTML файла
  • chunks фильтруются по имени entry

Это делает имя entry центральной точкой всей сборочной системы.


Ошибки при проектировании динамических entry

Наиболее частые проблемы:

  • неконтролируемый рост количества entry
  • дублирование одинаковых модулей в разных entry
  • отсутствие shared chunks
  • нестабильные имена при повторной сборке
  • чрезмерная вложенность имен

Типичная архитектурная ошибка — превращение entry в замену маршрутизатору приложения, что приводит к дублированию логики между бандлами.


Производительность при большом количестве entry

При десятках и сотнях entry:

  • увеличивается время компиляции
  • растёт нагрузка на memory graph
  • усложняется оптимизация splitChunks

Решения:

  • кэширование filesystem scans
  • мемоизация генерации entry
  • ограничение глубины сканирования
  • предварительная генерация manifest-файла

Manifest-подход к генерации entry

Вместо динамической генерации на лету используется промежуточный слой:

manifest.json
{
  "home": "./src/pages/home/index.js",
  "about": "./src/pages/about/index.js"
}

Webpack конфиг:

const manifest = require('./manifest.json');

module.exports = {
  entry: manifest
};

Такой подход переносит динамику из Webpack в отдельный этап сборки, повышая предсказуемость.