Как я пытался Vite 6 c CSS image-set() подружить
На днях я занимался обновлением Nuxt на одном из проектов. Обновиться нужно было с уже довольно устаревшей (по меркам фронта) версии 3.11
на актуальную 3.16
, при этом просто обновить версию Nuxt было бы недостаточно, нужно было также обновить и все созависимости.
Для начала я решил пройтись по всем release-постам в блоге Nuxt и посмотреть, что может зааффектить работу проекта. В целом никаких критичных breaking changes я не увидел, поэтому решил сразу переходить к обновлению.
Как обновить
Скрипт для обновления всегда можно найти в конце каждого release-поста, версия 3.16
здесь не стала исключением. Но если раньше (до 3.16), рекомендовалось запускать обновление с флагом force
, то сейчас вместо него почему-то указан dedupe
, т. е. итоговый скрипт выглядит уже вот так:
npx nuxi@latest upgrade --dedupe
Но поскольку на проекте в качестве пакетного менеджера мы используем yarn
, то при запуске я сразу столкнулся вот с такой ошибкой:
error The dedupe command isn't necessary. `yarn install` will already dedupe.
info Visit https://yarnpkg.com/en/docs/cli/dedupe for documentation about this command.
ERROR corepack yarn dedupe failed.
Поэтому имейте в виду, если вы тоже используете yarn
, нужно запускать команду без опции dedupe
и затем выбирать recreate node_modules and yarn.lock
, либо просто делать как раньше через npx nuxi@latest upgrade --force
(по сути, это одно и то же).
➜ npx nuxi@latest upgrade
ℹ Package manager: yarn 1.22.19 nuxi 15:59:28
ℹ Current Nuxt version: 3.16.0 nuxi 15:59:28
❯ Would you like to dedupe your lockfile (recommended) or recreate node_modules and yarn.lock? This can fix problems with hoisted dependency versions and
ensure you have the most up-to-date dependencies.
○ dedupe lockfile
● recreate node_modules and yarn.lock
○ skip
После обновления
В остальном процесс обновления прошел довольно гладко, никаких неожиданных проблем не возникало: проект (на первый взгляд) корректно работал как в dev, так и в build режиме.
Но затем я решил проверить вывод в консоль и увидел большое количество вот таких однотипных ворнингов:
WARN __VITE_ASSET__BRZ1AtrA__ referenced in __VITE_ASSET__BRZ1AtrA__ didn't resolve at build time, it will remain unchanged to be resolved at runtime
WARN __VITE_ASSET__Jbf5efbb__ referenced in __VITE_ASSET__Jbf5efbb__ didn't resolve at build time, it will remain unchanged to be resolved at runtime
Суть ошибки была одна, менялся только хэш (или алиас) конкретного файла, который Vite не смог разрезолвить во время сборки.
Так как ошибка явно говорила о проблеме с какими-то файлами из ~/assets
, я пошел смотреть все импорты оттуда, которые потенциально могли мешать Vite разрезолвить пути.
Не знаю почему, но первым делом я начал грешить на относительные пути в template
вида src="./assets/images/image.png"
. Здесь ./assets
— это не то же самое, что и ~/assets/
, это относительная папка, которая лежит рядом с компонентом.
Эмпирическим путем я понял, что проблема не в этом и не в динамических импортах svg
иконок, которые меня тоже смущали. Ворнинги все также продолжали сыпаться при nuxt build
.
Причина
Как обычно, когда с ходу решить проблему не удалось, я пошел искать ответ в GitHub Issues. Не знаю, чем я смотрел, но в первый раз нужный issue я не нашел (хотя он был, да, без ответов, но был).
Не обнаружив ничего дельного в интернетах, я решил продолжать искать причину проблемы у себя в коде.
Спустя пару часов, путем нехитрого поэтапного удаления всего, что связано с ~/assets
, я, к моему удивлению, обнаружил, что проблему вызывает вот такой CSS:
<style scoped lang="scss">
.product {
background-image: image-set(
url('~/assets/images/content@x2.webp') 2x,
url('~/assets/images/content@x1.webp') 1x,
url('~/assets/images/content@x2.jpg') 2x,
url('~/assets/images/content@x1.jpg') 1x
);
}
</style>
А именно использование CSS-функции image-set
. Причем неважно, используется там алиас ~
или просто относительный путь — ворниги возникали всегда.
Решения нет (я не нашел)
Воодушевленный тем, что теперь точно знаю причину, я пошел изучать, как работает image-set
и почему она вдруг стала конфликтовать с Vite 6.
Но сколь стремительно пришло воодушевление, столь же стремительно оно меня покинуло. Пару часов спустя я понял, что мне не дано решить эту проблему и пора искать другие варианты. К тому же я наконец нашел тот самый issue, о котором писал выше, и понял, что:
- проблема не только у меня;
- на свежем Nuxt проекте она также возникает;
Lightning CSS
Из интересного, что я обнаружил, это то, что если заменить PostCSS на Lightning CSS, то проблема с image-set
уходит, никаких ворнингов нет и изображения корректно работают. Но использовать у нас на проекте это было нельзя, так как Lightning CSS не поддерживает препроцессоры CSS, которые мы активно используем на проекте (SCSS).
export default defineNuxtConfig({
vite: {
css: {
transformer: 'lightningcss',
}
}
})
Скрываем warnings
Поскольку быстро решить проблему не удалось, а на работоспособность проекта эти ворнинги не влияли, я решил поступить как настоящий мужчина и просто скрыть их. Уж с чем с чем, а с этим я должен был справиться на изи. Здесь стоит отдельно похвалить отличную документацию Vite, в которой я очень быстро нашел, как это сделать.
Подключаем custom logger
Проблема в том, что мы используем не просто Vite, а Nuxt + Vite. В документации Nuxt, как правильно подключать customLogger через nuxt.config.ts
, я не нашел, поэтому решил, что супер очевидно было бы сделать это вот так (TS типы были со мной согласны):
export default defineNuxtConfig({
vite: {
customLogger: customLogger()
}
})
Но, увы и ах, так оно работать не будет. Разбираться зачем и почему я не стал, так как времени на задачу и так уже было потрачено больше, чем нужно. Тем более что после некоторых упражнений с конфигами мне удалось завести логгер через хук:
export default defineNuxtConfig({
hooks: {
'vite:extendConfig'(config) {
config.customLogger = customLogger()
},
},
})
Сам customLogger в тот момент у меня выглядел вот так:
import { createLogger } from 'vite'
export const customLogger = () => {
const logger = createLogger()
const { warn } = logger
const WARN_MESSAGE = "didn't resolve at build time"
logger.warn = (msg, options) => {
if (msg.includes(WARN_MESSAGE)) return
warn(msg, options)
}
return logger
}
Стартуем
Логгер написан, подключен — запускаем nuxt build
. И… получаем уже знакомые нам ворнинги:
__VITE_ASSET__BRZ1AtrA__ referenced in __VITE_ASSET__BRZ1AtrA__ didn't resolve at build time, it will remain unchanged to be resolved at runtime
__VITE_ASSET__Jbf5efbb__ referenced in __VITE_ASSET__Jbf5efbb__ didn't resolve at build time, it will remain unchanged to be resolved at runtime
Чтобы не тратить время, даже рассказывать, что я испытывал в тот момент, не буду (сердечку было очень больно).
Если посмотреть на логи до подключения кастомного логгера и после, то можно увидеть, что теперь это не WARN
, а INFO
уровень лога. Осталось только понять, откуда и почему они продолжают лететь.
Привет, меня зовут warnOnce
Как я позже выяснил, кроме обычного warn
есть ещё warnOnce
, который отличается от обычного тем, что выводит только уникальные предупреждения, т. е. если летит пачка ворнингов с идентичным текстом, то warn
выведет их все, а warnOnce
только один.
А поскольку изначально я переопределил только warn
, а warnOnce
не трогал, он ожидаемо продолжал отправлять логи в консоль, и чтобы полностью их скрыть, его тоже нужно переопределить.
Дописываем логгер и в итоге получаем рабочую версию:
import { createLogger } from 'vite'
export const customLogger = () => {
const logger = createLogger()
const { warn, warnOnce } = logger
const WARN_MESSAGE = "didn't resolve at build time"
logger.warn = (msg, options) => {
if (msg.includes(WARN_MESSAGE)) return
warn(msg, options)
}
logger.warnOnce = (msg, options) => {
if (msg.includes(WARN_MESSAGE)) return
warnOnce(msg, options)
}
return logger
}
Итого
- Проблему с
image-set
и Vite 6 мы не решили; - Замаскировать её с горем пополам нам удалось;
- Живём дальше и ждём решения вот тут;