Разработка межсредового кода JavaScript часто обнаруживает тонкие ошибки и может вызвать пульсирующую головную боль и бесконечное разочарование. При создании оболочки выборки, предназначенной для согласованной работы в браузерах, узлах и тестовых средах, я столкнулся с неожиданной проблемой: относительные URL-адреса работали нормально в браузере, но не работали в JSDOM.
JSDOM — это реализация DOM и веб-стандартов на языке JavaScript, позволяющая коду Node.js взаимодействовать со средой виртуального браузера. Он широко используется в средах тестирования, таких как Jest и Vitest.
Как старший разработчик полного стека и участник открытого исходного кода, специализирующийся на инструментах JavaScript и Golang, я поддерживаю такие библиотеки, как ffetch, и пишу о межсредовой надежности и практических настройках тестирования. Эта тема важна для меня, потому что и на работе, и в моих побочных проектах мне часто приходится создавать библиотеки, которые должны вести себя одинаково в Node, браузерах, пограничных средах выполнения и средах тестирования — обманчиво сложная цель, которая привела к этой истории отладки.
Проблема
В браузерах относительные URL-адреса автоматически преобразуются в window.location.origin. Но в некоторых тестовых средах это не гарантируется. В необработанном JSDOM window.location.href по умолчанию имеет значение about:blank, что приводит к сбою относительных URL-адресов. Однако в Jest или Vitest среда JSDOM автоматически устанавливает базовый URL-адрес по умолчанию (обычно именно поэтому относительные URL-адреса там часто «просто работают»).
Happy DOM (еще одна популярная альтернатива JSDOM) по умолчанию также инициализируется с помощью about:blank, поэтому относительные URL-адреса будут нарушены, если вы явно не зададите window.location.href или не укажете базовый URL-адрес во время установки.
Первый урок — убедиться, что ваша тестовая среда настроена правильно. Но на этом история не заканчивается.
Обнаружение проблемы в обертке
Несмотря на то, что Vitest и Jest предварительно настраивают JSDOM с действительным базовым URL-адресом (оболочка все равно обнаруживает ошибку TypeError: не удалось проанализировать ошибки URL-адреса при создании запросов с относительными URL-адресами в средстве выполнения тестов.
После некоторого копания я нашел эту старую проблему JSDOM, которая объясняет, как относительное разрешение URL-адресов может не работать из-за различий в объекте местоположения.
Оказывается, причина не в самом базовом URL — JSDOM правильно устанавливает globalThis.location.origin — а в том, как JSDOM реализует globalThis.location. Его объект location не полностью идентичен объекту window.location браузера, и Request может отклонять определенные значения при разрешении относительных URL-адресов. Это происходит потому, что Request(input) внутренне использует конструктор URL-адресов спецификации, который обеспечивает строгую проверку объекта Location.
Другими словами, среда предоставляет базовый URL-адрес, но оболочке по-прежнему необходимо явно разрешать относительные URL-адреса (например, через новый URL-адрес(relative, globalThis.location.origin)), чтобы гарантировать совместимость.
Исправление
Зная это, исправить это просто (хотя и некрасиво): явно определить относительные URL-адреса и разрешить их относительно globalThis.location.origin перед созданием запроса. Таким образом, оболочка ведет себя одинаково во всех браузерах, Node и всех альтернативах DOM, независимо от небольших различий в их реализации местоположения:
пусть URL = вход; if (typeof input === ‘string’ && !/^([a-z][a-z0-9+.-]*:)/i.test(input)) { // Обнаружен относительный URL if (typeof globalThis.location?.origin === ‘string’) { url = new URL(input, globalThis.location.origin).href; }} Const req = новый запрос (url, init); 12345678 let url = input;if (typeof input === ‘string’ && !/^([a-z][a-z0-9+.-]*:)/i.test(input)) { // Обнаружен относительный URL if (typeof globalThis.location?.origin === ‘string’) { url = new URL(input, globalThis.location.origin).href; }}const req = новый запрос (url, init);
Это гарантирует, что оболочка будет работать во всех средах, где определен globalThis.location.origin, включая браузеры, Node с jsdom (если вы его настроили) и средства запуска тестов, такие как Jest и Vitest.
Я применил этот патч к chaos-fetch, моей библиотеке тестирования хаоса, чтобы надежно обрабатывать относительные URL-адреса в разных средах.
Извлеченные уроки
Этот опыт позволил извлечь два важных урока:
Примечание: Если бы мы использовали Happy DOM вместо JSDOM и он был настроен с использованием правильного базового URL-адреса, относительные URL-адреса могли бы работать без патча, но сохранение логики в оболочке повышает надежность и немного большую защиту от тонких различий в среде.
Заключение
Межсредовая разработка JavaScript раскрывает скрытые сложности в, казалось бы, простых операциях. Относительные URL-адреса при извлечении в браузере просты, но среды тестирования, такие как JSDOM и Happy DOM, выявляют странные крайние случаи. Тщательно диагностируя проблему, реализуя более надежную логику разрешения и правильно настраивая среду, авторы библиотек могут создавать более надежные и предсказуемые абстракции, которые работают (почти) везде.
Этот опыт подчеркивает более широкий урок: всегда проверяйте предположения в нескольких средах и проектируйте свои библиотеки так, чтобы они корректно справлялись с неожиданностями.
ТЕНДЕНЦИОННЫЕ ИСТОРИИ YOUTUBE.COM/THENEWSTACK Технологии развиваются быстро, не пропустите ни одной серии. Подпишитесь на наш канал YouTube, чтобы смотреть все наши подкасты, интервью, демонстрации и многое другое. ПОДПИСАТЬСЯ Группа, созданная в Sketch. Габор Коос — инженер-программист, специализирующийся на Go, JavaScript/TypeScript, распределенных системах и системной архитектуре. Он участвует в разработке программного обеспечения с открытым исходным кодом и ведет блоги о параллелизме, производительности и практических методах разработки программного обеспечения на InfoQ. Узнайте больше от Габора Кооса