Что такое сборка и почему она ломается
пять причин, по которым «у меня работает»
Что это
Сборка — это превращение твоих исходников в то, что запускается у других. Пока ты разрабатываешь, программу запускает инструмент разработчика: он читает твои файлы как есть, подставляет настройки с твоей машины, перезагружает изменённое на ходу. Это удобно и совсем не похоже на то, что получит пользователь.
При сборке происходит другое: код проверяется, переписывается, склеивается в
несколько файлов, значения настроек подставляются внутрь, лишнее выбрасывается.
Получается артефакт — один файл или папка, которые можно отдать: .apk для
Android, папка dist/ для сайта, .whl или контейнер для серверной части.
Разница между «запустилось у меня» и «работает артефакт» — самая частая и самая обидная поломка в проекте, потому что она случается в конце, когда всё уже казалось готовым.
Как выглядит в коде
Ниже пять причин поломок, и все пять проверены на живых проектах: показан настоящий вывод команд.
1. Сборка прошла, а код сломан
Вот файл с двумя явными ошибками типов — строка на месте числа и число на месте строки:
import { CityCard } from './components/CityCard'
export default function App() {
const temp: number = "жарко"
return <CityCard name={temp} />
}Запускаем сборку напрямую:
$ npx vite build
vite v7.3.7 building client environment for production...
✓ 29 modules transformed.
dist/index.html 0.39 kB │ gzip: 0.27 kB
dist/assets/index-DSlY0u7I.js 222.62 kB │ gzip: 69.35 kB
✓ built in 1.96sСобралось. За две секунды, с галочкой. Теперь проверка типов, на том же файле:
$ npx tsc -b
src/App.tsx(4,9): error TS2322: Type 'string' is not assignable to type 'number'.
src/App.tsx(5,20): error TS2322: Type 'number' is not assignable to type 'string'.Причина в том, что сборщик не проверяет типы — он их выбрасывает. Типы нужны редактору и проверяльщику, а в собранном файле их нет (статья 9.1). Поэтому в нормальном проекте команда сборки состоит из двух:
"build": "tsc -b && vite build"Сначала проверка, потом склейка. И вот ровно здесь появляется ошибка, которую
допускают и люди, и ИИ: сборка падает, её «чинят», убрав из команды tsc -b.
Сборка становится зелёной, проверка — выключенной.
2. Настройки запекаются внутрь
Переменные окружения при сборке не читаются в момент работы программы — они подставляются в текст артефакта. Файл настроек:
VITE_API_URL=https://api.example.com
API_KEY=secret-abc-123Код, который их читает:
const API = import.meta.env.VITE_API_URL
const KEY = import.meta.env.API_KEYА вот что получилось в собранном файле — настоящий фрагмент из dist/:
var yh=oh();const mh="https://api.example.com",dh=void 0;В одной строке сразу две разные беды.
Первая: адрес попал в артефакт текстом. Собранный файл отдаётся браузеру любого желающего, то есть всё, что в нём есть, — публично. Если бы на месте адреса был ключ от платного API, он лежал бы там же, открытым текстом.
Вторая: API_KEY превратился в void 0, то есть в «ничего». Сборщик
подставляет только переменные с нужным префиксом (VITE_ в этом примере),
остальные молча становятся пустыми. Ни ошибки, ни предупреждения: приложение
собирается, запускается и ведёт себя странно — например, стучится по адресу
undefined/api/cities.
Проверить это можно руками, и это стоит сделать хотя бы раз в жизни: собрать проект и поискать в собранном файле свой секрет.
$ grep -c "secret-abc-123" dist/assets/*.js
0
$ grep -c "api.example.com" dist/assets/*.js
1Правило простое: то, что должно остаться тайной, не попадает в код, который уходит на устройство пользователя. Ключи живут на сервере, а приложение обращается к своему серверу, а тот — к платному API.
3. Путь, который зависит от того, откуда запустили
Невинная строка:
with open("pogoda/cities.json", encoding="utf-8") as f:Из корня проекта работает:
$ python3 -m pogoda.main
{'Oslo': 4, 'Tromso': -3}Из любой другой папки — нет:
File ".../pogoda/main.py", line 5, in load_cities
with open("pogoda/cities.json", encoding="utf-8") as f:
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'pogoda/cities.json'Относительный путь считается не от файла с кодом, а от папки, из которой запустили программу. При разработке это всегда корень проекта, поэтому ошибки не видно. На сервере программу запускает служба, и папка у неё своя.
Правильно — считать от расположения самого файла:
from pathlib import Path
DATA = Path(__file__).parent / "cities.json"
def load_cities():
with DATA.open(encoding="utf-8") as f:
return json.load(f)После такой правки запуск из /tmp даёт то же, что из корня проекта. Это
проверено: {'Oslo': 4, 'Tromso': -3}.
4. Файл не попал в артефакт
Тот же проект, уже с исправленными путями, собираем в пакет:
$ python3 -m build --wheel
Successfully built pogoda-0.1.0-py3-none-any.whlНи ошибки, ни предупреждения. Смотрим, что внутри:
pogoda/__init__.py
pogoda/main.py
pogoda-0.1.0.dist-info/METADATA
pogoda-0.1.0.dist-info/WHEEL
pogoda-0.1.0.dist-info/RECORDcities.json внутри нет. Устанавливаем пакет и запускаем:
FileNotFoundError: [Errno 2] No such file or directory:
'/tmp/venv-pogoda/lib/python3.13/site-packages/pogoda/cities.json'Сборщик собирает код, а всё остальное — картинки, шрифты, словари, шаблоны писем, файлы данных — надо объявлять отдельно. Три строки в настройках проекта:
[tool.setuptools.package-data]
pogoda = ["*.json"]И в собранном пакете появляется нужное:
adding 'pogoda/__init__.py'
adding 'pogoda/cities.json'
adding 'pogoda/main.py'Это общая болезнь всех стеков: во Flutter ресурсы объявляются в pubspec.yaml
(статья 11.1), на фронтенде картинки надо либо импортировать, либо класть в
особую папку, в контейнер файлы надо копировать явной командой. Забыли
объявить — артефакт собрался без файла, и упадёт не при сборке, а при открытии
того экрана, где этот файл нужен.
5. Версии зависимостей плавают
В настройках проекта было написано так:
"react": "^19.1.1",
"vite": "^7.1.7"А установилось вот это:
$ npm ls react vite --depth=0
+-- react@19.3.1
`-- vite@7.3.7Значок ^ означает «эта версия или новее в пределах совместимости». То есть
один и тот же проект, установленный у тебя сегодня и на сервере через месяц,
получит разные версии библиотек. Обычно ничего не случается. Иногда
случается, и выяснять, что именно изменилось, приходится с нуля.
С Python то же самое, и наглядно: одна строка requests в списке зависимостей
притаскивает пять пакетов, каждый со своей версией:
certifi==2026.7.22
charset-normalizer==3.5.2
idna==3.20
requests==2.34.2
urllib3==2.8.0Четыре из пяти ты не выбирал. Они и есть источник сюрпризов.
Лекарство — файл с точными версиями, который создаётся при установке
автоматически: package-lock.json, pubspec.lock, poetry.lock,
requirements.txt, собранный через pip freeze. В нём записано ровно то, что
стояло у тебя, когда всё работало:
node_modules/react -> 19.3.1
node_modules/vite -> 7.3.7Этот файл надо коммитить в репозиторий и устанавливать зависимости
командой, которая его уважает: npm ci вместо npm install. Иначе он
бессмыслен.
Как сказать ИИ
Проект собирается у меня, но падает после установки.
Не предлагай правок наугад. Сначала перечисли, чем
запуск при разработке отличается от собранного артефакта
в этом проекте: пути к файлам, переменные окружения,
ресурсы, версии зависимостей. По пункту на строку.Такой запрос даёт список мест, где искать, вместо случайной правки.
Вот моя команда сборки и файл настроек окружения.
Перечисли, какие значения попадут в артефакт открытым
текстом и будут видны пользователю. Отдельно — что
станет undefined и почему.Перечисли все файлы, которые проект читает не как код:
данные, картинки, шрифты, шаблоны. Для каждого скажи,
объявлен ли он в настройках сборки. Если не объявлен —
напиши строку, которую надо добавить.Сборка падает с этой ошибкой: <текст>.
Нужны два варианта: правка кода и правка настроек сборки.
Для каждого скажи, что именно перестанет проверяться.
Выбирать буду я.Последний приём стоит запомнить. Любую «правку сборки» надо получать вместе с ценой: без этого вопроса отключение проверки выглядит как решение.
На что смотреть
Из команды сборки пропала проверка. Было tsc -b && vite build, стало
vite build. Сборка зелёная, типы не проверяются.
Появились флаги подавления. --force, --legacy-peer-deps,
--no-verify, --ignore-scripts, || true в конце команды. Каждый из них
что-то выключает, и это «что-то» надо назвать вслух.
Секрет в коде приложения. Ключ API, пароль, токен в файле, который собирается и уходит на устройство. Проверяется поиском по собранному файлу.
Относительные пути к файлам данных. open("data/x.json") вместо пути от
__file__.
Ресурсы не объявлены. Картинка, шрифт или файл данных есть в папке проекта, но не упомянут в настройках сборки.
Файл с точными версиями в .gitignore. Классическая «оптимизация»:
package-lock.json выглядит как мусор, потому что большой и меняется. Без него
воспроизводимой сборки нет.
Сборка на твоей машине как единственный способ. Пока проект собирается только у тебя и только руками, никто — включая тебя через полгода — не сможет его собрать.
Шпаргалка
Сборка — это не запуск: другой код, другие настройки, другие файлы.
Зелёная сборка не значит рабочий код: сборщик типы выбрасывает, а не проверяет.
Правка команды сборки вместо кода — почти всегда отключение проверки.
Настройки окружения запекаются в артефакт: секретам там не место.
Переменная без нужного префикса молча превращается в
undefined.Относительный путь считается от папки запуска, а не от файла с кодом.
Всё, что не код, надо объявлять в настройках сборки отдельно.
Проверяй установленный пакет из другой папки, иначе найдёшь исходники.
Файл с точными версиями коммить и ставить через
npm ci, а неinstall.