Несмотря на кажущуюся простоту темы, мне не довелось поучаствовать ни в одном проекте, где сборка фронтовой и серверной частей была бы настроена удобно, то есть обладала бы следующими характеристиками: Коллеги настраивали лишь последовательную сборку этих частей при изменении любых файлов, что приводило к обязательному перезапуску сервера и нескольким перезагрузкам страницы (т.к. для этого использовалась либо middleware, отслеживающая запуск сервера, либо watch-сервер webpack, но видел и что используются они одновременно). Поэтому тема показалась актуальной, разберем все по полочкам. Условия задачи указаны выше, соответственно можно спроектировать последовательность воплощения. На мой взгляд, наиболее удобно использовать .env файлы: Для текущей задачи будет достаточно включить в них настройки для основного сервера, для сервера, перезагружающего браузер, и параметры Webpack, которые будут меняться в зависимости от среды запуска или для удобства разработчика: Для того, чтобы смерджить реальное env-окружение машины и данный файл, можно использовать dotenv, но я предпочитаю better-npm-run, который заодно решает проблему кроссплатформенной передачи параметров из рецептов package.json. В код эти переменные будут передаваться с помощью следующей утилиты: В реальном приложении здесь нужно будет также проверять согласованность всех .env-файлов. Результат одной будет использоваться в целевых браузерах и выполняться на машине клиента, второй — в установленной в системе версии Node.js. По большей части они должны быть одинаковы, кроме следующих моментов (F — фронтендовый конфиг, B — бэкендовый): F: target: 'web'
B: target: 'node' F: entry: { client: path.resolve(paths.sourcePath, 'client.js') }
B: entry: { server: path.resolve(paths.serverPath, 'server.js') } В данном случае фронтенд-сборка транспилируется в код, понятный браузерам из browserslist в package.json с автоматическим анализом файлов и внедрением полифиллов, а серверная — в код, понятный установленной в системе версии Node.js. Здесь подразумевается, что серверная сборка не должна перемещать файлы из src в build с соответствующим переименованием, этим будет заниматься фронтовая.  Таким образом, фронтенд-сборка будет записывать стили в css-файлы, а бэкенд-сборка только преобразует их в js-объекты вида { myClass: "FolderName__myClass" } благодаря параметру onlyLocals: true. И последнее различие между двумя сборками — в настройке оптимизации. Бэкендовая сборка должна сгенерировать один главный файл, который будет обращаться к дополнительным пакетам из node_modules напрямую, не включая в себя. Для этого служит настройка externals: [nodeExternals()]. Во фронтовой же будет генерироваться неограниченное количество файлов (асинхронные чанки + группы из cacheGroups). Для базовой версии достаточно иметь возможность запустить функцию, как только обе сборки завершились в первый раз. С этим справится небольшая обертка над node-worker-farm под названием parallel-webpack. Этот пакет собирает сигналы от параллельных процессов через node-ipc и может выполнить необходимый коллбэк. К сожалению, напрямую конфиги в виде массива передать нельзя, поэтому придется создать дополнительный файл webpackParallel.config.ts с реэкспортом: В итоге получится подобная структура файлов: Непосредственно билдер будет содержать следующий функционал: Как видно из файла, dev-режим будет отличаться только тем, что в консоль не будет выводиться информация о команде запуска (параметр -s в двух местах) и передачей переменной START_SERVER_AFTER_BUILD, все остальные настройки контролируются env-файлом. Для того, чтобы babel-node позволил использовать Typescript в файлах сборщика, нужно добавить дефолтный конфиг для бабеля в корне проекта, который трансформирует код в понятный для установленной версии node.js: Плагины в данном случае (кроме @babel/plugin-transform-typescript) не нужны, но я вынес их в этот файл для переиспользования в babel-loader'ах (во втором разделе статьи). Для слежения за изменением файла server.js и рестарта я буду использовать nodemon, как записано выше в рецепте "start": "nodemon -q ./build/server.js". Для watch-режима достаточно в корень проекта поместить файл: Включать этот функционал в node.js сервер неэффективно, так как он будет вызывать ложные перезагрузки и может не вызвать нужных ввиду разного времени завершения билдов серверного и клиентского кода. Поэтому сделал отдельный рецепт "reload-browser": "babel-node --extensions .ts,.tsx ./server/watchServer.ts", который будет выполняться в отдельном процессе. Цель этого скрипта — запустить WebSocket сервер + file watcher для папки билда, чтобы при изменении определенных файлов подавать сигнал браузеру. Таким образом, основному серверу достаточно будет запросить HOT_RELOAD_URL/reload/reload.js, а полученный скрипт настроит соединение и будет реагировать на сигналы. Также если процесс прерван, он будет пытаться заново подключиться — и если получится, то бесшовно продолжит перезагружать браузер при новых изменениях. Так как данная схема используется только при локальной разработке, никаких деградаций до long polling предусматривать не нужно. При слежении за папкой build я исключил вложенные папки и файл server.js, так как при работе чисто с серверными файлами перезагрузка браузера и новые запросы создадут лишь неудобство. Заметьте, что filePath.indexOf(fileName) !== -1 в реальном проекте для этих целей не подойдет — проигнорируются в том числе файлы, в части имени или в названии папки которых присутствует строка "server". Оставил это решение только для того, чтобы читатели не забывали внимательно относиться к тому, что пишут в интернетах. Небольшой дебаунс сделан для единой перезагрузки браузера при обновлении вебпаком сразу нескольких файлов, магическая цифра 50 не имеет какого-то сакрального смысла, думаю, без проблем будет работать и меньшая, но не 0 ввиду задержек срабатывания node-watch и неодновременной записи файлов вебпаком. Так как локальная разработка должна вестись на приближенной к стендовой среде, то включил вариант работы по HTTPS, чтобы браузером не выдавались ошибки из разряда mixed content. Подключение этого скрипта в основном сервере сделать очень просто: Все, система, соответствующая критериям создана — теперь можно спокойно заниматься сервером и получать быструю пересборку с перезапуском, либо заниматься стилями и получать только фронтовую пересборку (но при добавлении нового класса изменится в том числе серверный js-объект, что приведет к обеим пересборкам). При этом file watcher гибко настраивается под любой проект. Последнее, на что хотел бы обратить внимание — что parallel-webpack не имеет функционала выполнения логики перед пересборками, а этот функционал необходим для системы автоматической генерации файлов (отдельная тема, по которой планирую еще одну статью), поэтому скорее всего придется создавать более гибкое решение.  Репозиторий Комфортного всем кодинга. Front-end архитектор