Сборка серверного кода

Особенности серверной сборки

Сборка серверного JavaScript-кода отличается от браузерной прежде всего окружением исполнения. Node.js предоставляет доступ к файловой системе, сетевым интерфейсам и встроенным модулям, которые не требуют полифиллов или бандлирования в привычном смысле.

Parcel при работе с серверным кодом переходит в режим, ориентированный на Node.js, где ключевыми становятся следующие аспекты:

  • корректная обработка встроенных модулей (fs, path, http)
  • контроль над внешними зависимостями (node_modules)
  • сохранение семантики require и import
  • минимизация лишнего бандлинга там, где он не нужен
  • поддержка ESM и CommonJS в рамках одной сборки

Главная цель серверной сборки — не уменьшить размер, а обеспечить предсказуемое и переносимое выполнение кода.


Базовая сборка Node.js-приложения

Parcel автоматически определяет контекст по целевому окружению или конфигурации.

Типичный входной файл серверного приложения:

// src/server.js
import http from 'http';
import app from './app.js';

const server = http.createServer(app);

server.listen(3000, () => {
  process.stdout.write('Server started on port 3000');
});

Сборка выполняется командой:

parcel build src/server.js --target node

Результатом становится один или несколько файлов, подготовленных для запуска в Node.js без дополнительных преобразований.


Конфигурация targets в Parcel

Parcel v2 использует декларативную модель через package.json. Основная настройка серверного бандла задаётся через targets.

{
  "name": "my-server-app",
  "source": "src/server.js",
  "targets": {
    "node": {
      "context": "node",
      "engines": {
        "node": ">=18"
      },
      "outputFormat": "commonjs",
      "distDir": "dist/server",
      "includeNodeModules": true
    }
  }
}

Ключевые параметры:

context: “node” Определяет среду выполнения. Parcel отключает браузерные трансформации и полифиллы.

outputFormat

  • commonjs — классический формат Node.js
  • esmodule — ESM-вывод для современных окружений

includeNodeModules Контролирует, будут ли зависимости из node_modules включены в бандл или оставлены внешними.


Работа с зависимостями node_modules

Серверная сборка почти всегда требует строгого контроля внешних библиотек.

Parcel поддерживает два подхода:

Оставление зависимостей внешними

{
  "targets": {
    "node": {
      "includeNodeModules": false
    }
  }
}

В этом случае результат сборки содержит require('express'), и установка зависимостей обязательна в runtime.

Инлайнинг зависимостей

{
  "targets": {
    "node": {
      "includeNodeModules": true
    }
  }
}

Parcel включает код зависимостей в бандл, уменьшая количество внешних файлов, но увеличивая размер результата.

Выбор зависит от сценария:

  • сервер в Docker → часто предпочтительно false
  • serverless-функции → часто true

Обработка встроенных модулей Node.js

Node.js предоставляет набор встроенных модулей, которые Parcel не должен пытаться бандлить:

import fs from 'fs';
import path from 'path';

Parcel автоматически:

  • оставляет их как runtime-зависимости
  • не пытается искать их в node_modules
  • не трансформирует в полифиллы

Важно учитывать, что попытка использовать браузерные API в серверном таргете не имеет смысла:

// некорректная логика для node-target
window.document.querySelector('div');

Такие конструкции должны быть изолированы или разделены по окружениям.


Разделение серверного и клиентского кода

В реальных проектах серверный и клиентский код часто сосуществуют.

Типичная структура:

src/
  server/
    index.js
    routes.js
  client/
    index.js

Parcel позволяет создать несколько таргетов:

{
  "targets": {
    "server": {
      "context": "node",
      "source": "src/server/index.js",
      "distDir": "dist/server"
    },
    "client": {
      "context": "browser",
      "source": "src/client/index.js",
      "distDir": "dist/client"
    }
  }
}

Это обеспечивает полную изоляцию окружений и предотвращает случайное смешивание API.


Переменные окружения и конфигурация runtime

Серверные приложения активно используют process.env.

Parcel позволяет инлайнить значения переменных окружения на этапе сборки:

NODE_ENV=production parcel build src/server.js --target node

Или через конфигурацию:

{
  "targets": {
    "node": {
      "define": {
        "process.env.NODE_ENV": "\"production\""
      }
    }
  }
}

Такой подход полезен для:

  • отключения debug-логики
  • переключения конфигураций
  • оптимизации ветвлений кода

Асинхронные импорты и серверный код

Parcel поддерживает динамические импорты и на сервере:

const loadModule = async () => {
  const module = await import('./heavy-module.js');
  return module.run();
};

В Node.js-сборке это может приводить к:

  • ленивой загрузке модулей
  • уменьшению стартового времени приложения
  • разделению логики по запросам

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


Source maps в серверной сборке

Для отладки серверного кода важны корректные stack trace.

Parcel генерирует sourcemaps:

{
  "targets": {
    "node": {
      "sourceMaps": true
    }
  }
}

Это позволяет:

  • видеть исходные файлы в stack trace
  • отлаживать TypeScript и транспилированный код
  • анализировать ошибки в production-логике

Особенности работы с TypeScript

Parcel поддерживает TypeScript без дополнительной конфигурации.

Серверный пример:

import http from 'http';

const handler = (req: http.IncomingMessage, res: http.ServerResponse) => {
  res.end('OK');
};

export default handler;

В серверной сборке важны следующие моменты:

  • типы полностью удаляются на этапе трансформации
  • tsconfig.json влияет на резолв модулей
  • пути paths учитываются при бандлинге

Монорепозитории и серверные пакеты

В монорепозиториях Parcel должен корректно обрабатывать локальные зависимости.

Пример структуры:

packages/
  server/
  shared/
  database/

Серверный пакет:

import { connect } from '@app/database';
import { logger } from '@app/shared';

Parcel:

  • резолвит локальные пакеты через workspace-конфигурацию
  • сохраняет структуру модулей
  • может инлайнить или внешне подключать зависимости в зависимости от includeNodeModules

Производственная сборка и оптимизация

При сборке серверного кода в production-режиме Parcel применяет ряд оптимизаций:

  • удаление мёртвого кода
  • упрощение условных выражений
  • минификация (если включена)
  • объединение модулей при необходимости

Команда:

parcel build src/server.js --target node --no-source-maps

В продакшене обычно отключаются:

  • source maps
  • watch mode
  • лишние dev-зависимости

Watch-режим для серверной разработки

Parcel поддерживает режим наблюдения:

parcel watch src/server.js --target node

Поведение:

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

Часто используется совместно с nodemon:

nodemon dist/server.js

Частые проблемы при сборке серверного кода

Смешивание ESM и CommonJS Несовместимость require и import может приводить к ошибкам резолва.

Неправильный target Отсутствие context: node может привести к включению браузерных трансформаций.

Лишний бандлинг node_modules Увеличивает размер и замедляет сборку.

Использование browser API Код, зависящий от DOM, не должен попадать в серверный таргет.

Динамические пути в import Могут усложнить статический анализ:

import(`./handlers/${name}.js`);

Архитектурные подходы к серверной сборке

Parcel хорошо сочетается с несколькими стилями организации серверного кода:

  • модульный монолит (один бандл)
  • микросервисы (несколько таргетов)
  • serverless-функции (каждая функция как entry point)
  • гибридные приложения SSR + API

В каждом случае важно правильно разделять entry points и конфигурацию targets, чтобы избежать смешивания окружений и избыточного бандлинга.