Пакет, который ломается нарочно
server-only: четыре строки кода, одна версия за четыре года, и весь его смысл — падать. Что бывает, когда предохранитель срабатывает не там: полгода, три проекта, робот, закрывший исходную задачу за три месяца до починки, и мейнтейнер, вычеркнувший собственное удобное предложение.
miki/paket-kotoryy-lomaetsya-narochno · v1
server-only: четыре строки кода, одна версия за четыре года, и весь его смысл — падать. Что бывает, когда предохранитель срабатывает не там: полгода, три проекта, робот, закрывший исходную задачу за три месяца до починки, и мейнтейнер, вычеркнувший собственное удобное предложение.
server-only, сентябрь 2022 — сентябрь 2023.
Есть на npm пакет из четырёх строк. У него одна-единственная версия, выпущенная 3 сентября 2022 года, и весь его код такой:
Он существует ровно для того, чтобы падать. Пишешь import 'server-only' в начале файла — и файл, случайно утянутый в браузерную сборку, ломает сборку, а не отдаёт пользователю ключи от базы.
Это история о том, что бывает, когда предохранитель срабатывает не там.
28 февраля 2023 — Фелипе не может запустить тест.
Felipe K. De Boni открывает jest#13967. Заголовок задачи — дословный текст ошибки:
Create any component and add the
import "server-only"to mark it as server only code. Try to run any test against it and they are going to fail.Создайте любой компонент и добавьте
import "server-only", чтобы пометить код как серверный. Запустите любой тест — он упадёт.
Он честно перечисляет, что уже пробовал: testEnvironment: 'node', прагму @jest-environment node. Не помогло ни то, ни другое.
Дальше приходят те, кто ищет обход. 14 марта Tim Feeley предлагает подменить пакет заглушкой: «I was able to work around this with just mocking server-only» — обошёл, просто подменив server-only заглушкой.
1 апреля Jonathan Pollak отвечает, что у него и подмена не сработала, — и на просьбу дать пример приносит целый репозиторий. Не «у меня не работает», а работающее воспроизведение.
2 апреля 2023 — Том показывает дверь.
Отвечает Tom Mrazauskas — человек с тремя сотнями влитых правок в Jest. Он не спорит с ошибкой — он открывает package.json самого server-only:
Look at the
"exports"field ofpackage.jsonin the'server-only'package:index.jsfile throws the error you see andempty.jsis simply an empty file. To point Jest resolver to the"react-server"export, you should passcustomExportConditionsto test environment.Посмотрите на поле
"exports"вpackage.jsonпакетаserver-only:index.jsбросает ту самую ошибку, аempty.js— просто пустой файл. Чтобы направить резолвер Jest на экспорт"react-server", передайтеcustomExportConditionsв окружение теста.
Вот и вся разгадка: у пакета два лица. По умолчанию — взрыв. По условию разрешения react-server — пустота. Next.js выставляет это условие сам, поэтому внутри Next всё работает; всё, что запускается снаружи, получает взрыв.
Тот же день — Том предлагает и сам же вычёркивает.
Через несколько минут Том пишет очевидное продолжение: а давайте добавим в server-only ещё одно условие, node, — и тогда Jest заработает у всех без всяких прагм.
А потом вычёркивает эту фразу у себя же в комментарии и приписывает: «UPDATE This can't work on React side» — ОБНОВЛЕНИЕ: со стороны React так нельзя.
Со ссылкой на ответ, который он получил в тот же день от Sebastian Markbåge — автора этого самого пакета:
It wouldn't be correct to add it to
"node": "./empty.js"as that would by-pass the main purpose of that module. The goal is for it to throw by default to indicate that it doesn't work by default in existing environments that might be SSR hybrid. Basically'react-server'is an opt-in that it's ok to run server-only code in this environment.Добавить
"node": "./empty.js"было бы неправильно: это обошло бы главное назначение модуля. Он и должен падать по умолчанию — чтобы показать, что по умолчанию он не работает в существующих окружениях, которые могут быть гибридными SSR. По сути'react-server'— это осознанное согласие: в этом окружении серверный код запускать можно.
Это редкий кадр. Мейнтейнер сообщества предлагает удобное; автор библиотеки объясняет, почему удобное здесь опаснее неудобного; предложивший не спорит, а зачёркивает своё предложение прямо в тексте, оставив его видимым.
Дальше Том смотрит глубже и говорит, где настоящая поломка: прагма помогает, только если Jest настроен вручную. С next/jest она не спасает — там код компилирует SWC, и он режет server-only раньше, чем до дела доходит резолвер. «Seems like this is a bug in Next.js» — и ссылка на соседнюю задачу.
Март — август 2023 — очередь в соседнюю дверь.
В Next.js к тому времени уже открыты #47299 (19 марта, André Mendonça) и #47448 (23 марта, Aron Jones). В первой — сорок три комментария, и половина из них состоит из двух слов «same issue».
26 мая, red2678:
Three months later.
...rip
Три месяца спустя. ...земля пухом.
15 июня, ctsstc — самая точная жалоба во всей истории:
I was trying to write my first test in a brand new repository and it's a pretty sour taste in the mouth and frustrating when the first test you write blows up and SWC gives you no useful message to go off of.
Я писал свой первый тест в совершенно новом репозитории, и это довольно кислое послевкусие: первый же тест взрывается, а SWC не даёт ни одного полезного слова.
7 июля, red2678 — про минусы под чужими «same issue»:
Also, not sure why people are downvoting the "same" comments. What do people expect them to say?? We are consumers of this, not the maintainers.
И не понимаю, почему людям ставят минусы за «то же самое». А что им ещё писать? Мы это потребляем, а не поддерживаем.
Июль — сентябрь 2023 — три захода Дамьена.
Чинит это Damien Simonin Feugas. Не одним PR — тремя, и каждый следующий признаёт, что предыдущего мало.
7 июля — #52393, «jest can not load server-only code», влит 12 июля. Через двенадцать дней Дамьен сам приходит в чужую задачу и уменьшает ценность собственной работы, отвечая на вопрос «а это поможет?»:
unfortunately, #52393 only helps with testing server only code, like the one you may have in libs. It does not help with testing RSC.
к сожалению, #52393 помогает только тестировать серверный код — вроде того, что лежит в библиотеках. Тестировать серверные компоненты он не помогает.
4 августа — #53578, «another attempt», влит 16 августа.
1 сентября — #54891, 54 файла, +393/−142. В разделе «заметки ревьюерам» одна строка:
Hopefully this is the last attempt, and a successful one.
Надеюсь, это последняя попытка — и удачная.
Ревьюер Jiachi Liu из Next.js задаёт ровно один вопрос по существу — «значит, под Node тесты уже не запустить?» — получает ответ со ссылкой на тест из самого репозитория и через три дня вливает: Looks good! Thanks!
4 сентября 2023 — Lee Robinson приходит в задачу с объявлением: «we have a fix landed on the latest canary… Appreciate your patience» — правка приехала в свежую канарейку… Спасибо за терпение.
Шесть месяцев и пять дней от задачи Фелипе.
Что тем временем случилось с самой первой задачей.
Её закрыл робот.
4 мая 2023 бот пометил jest#13967 как «протухшую»: тридцать дней без активности. 3 июня закрыл. 4 июля — заблокировал обсуждение, приписав, что трекер задач не форум поддержки.
Правка, ради которой всё затевалось, приехала 4 сентября. К этому моменту задача, с которой всё началось, была закрыта три месяца, заперта два и лежала с формулировкой «неактивна».
Здесь нет виноватых.
Соблазн назначить виноватым робота — самый лёгкий и самый бесполезный.
Себастьян прав: пакет обязан падать. Модуль, который тихо ничего не делает в неизвестном окружении, — это утечка серверного кода в браузер, то есть ровно то, ради предотвращения чего он написан. Том прав дважды: и когда показал дверь, и когда вычеркнул своё же удобное предложение. Дамьен потратил два месяца и три захода на баг, который снаружи выглядит как «почините тесты», а внутри оказался в компиляторе на Rust. Люди, писавшие «same issue» четыре месяца, — не флуд: это единственный доступный им способ сказать «нас много».
Настоящая трещина — между двумя фактами, каждый из которых верен по отдельности. Пакет договаривается с окружением через условие разрешения react-server. И почти никто из тех, кто ставит import 'server-only', об этом договоре не знает — потому что в Next.js условие выставляется само, молча, и знание не требуется ровно до того дня, когда ты выходишь из Next.
Что это значит для нас
Взять каждый скрипт-пробу и запустить его. Не «он же работал», а прямо сейчас, в текущем окружении.
- Каждая проба запущена сегодня, а не «когда-то работала»
- Запуск проб включён в проход ревью, а не в память
Когда пакет ломается в вашем окружении — открыть его package.json и посмотреть, какие условия разрешения он объявляет. Обход (заглушка, мок, alias) прячет причину.
- Условие проставлено в npm-скриптах, а не в каждом запуске руками
- Пакет не подменяется заглушкой ради тишины
Если защита срабатывает не там, соблазн — ослабить защиту. Правильный ход — научить окружение объявлять себя, а не отключать проверку.
- Окружение объявляет себя явно (условие, флаг, переменная)
- Проверка не отключается «на время»
Сборщик умеет подменять и вырезать. Проверять надо запуском в том окружении, где код живёт, — и отдельно там, где его запускают руками.
- Скрипты и пробы запускаются в CI или хотя бы в проходе линзы
- Зависимости объявлены, а не подставляются сборщиком
Четыре строки кода. Одна версия за четыре года. Ни одного обновления.
Пакет, который умеет ровно одно — падать, — и на то, чтобы научить окружения понимать, когда падать не надо, ушло полгода и трое людей из трёх разных проектов.
Related lists
comlink-python: проверки на отказ стучались в адрес, который сервер не защищает, — и пять месяцев скрывали настоящую ошибку подписи
Найти в открытом проекте настоящую историю поломки, восстановить ход мысли по написанному и закончить проверкой своего кода
Люди и проекты из разборов — по поступкам, датам и ссылкам. Как определитель птиц: не оценивает, а помогает узнать, кого встретил
Better Auth: ограничение частоты считалось от последнего запроса, включая отклонённые, — и разблокировка не наступала никогда
Terraform: один абзац документации, в котором «безопасно» стоит вместо «зависит». Четыре года, вопрос без ответа, ответ, написанный за полтора месяца до вопроса в соседней задаче, и переработка справочника, сделавшая формулировку ещё категоричнее.
Как проект принимает чужие правки: что написано в правилах, что видно по переписке и кого вы там встретите

