Плагин в Vite представляет собой прослойку между системой сборки, dev-сервером, трансформацией модулей и механизмом Rollup. Ошибки внутри плагинов редко проявляются как обычные исключения. Чаще возникают более сложные проблемы:
serve и build;Отладка плагинов требует понимания внутреннего жизненного цикла Vite и Rollup, а также особенностей dev-сервера.
Наиболее распространённый тип проблем связан с хуком
transform.
Пример:
export default function myPlugin() {
return {
name: 'my-plugin',
transform(code, id) {
if (id.endsWith('.js')) {
return code.replace('foo', 'bar');
}
}
};
}
Проблемы:
Проблемы в resolveId особенно трудно
диагностировать.
Пример:
resolveId(source) {
if (source === 'virtual:config') {
return '\0virtual:config';
}
}
Типичные ошибки:
\0 для виртуального модуля;Хук load может приводить к:
Самый простой способ диагностики — вывод информации в консоль.
transform(code, id) {
console.log('[transform]', id);
return code;
}
Полезно выводить:
id;Без структурирования логи быстро становятся нечитаемыми.
Хороший вариант:
console.log(
'[my-plugin:transform]',
{
id,
length: code.length
}
);
console.group('[my-plugin]');
console.log('id:', id);
console.log('code:', code);
console.groupEnd();
Для крупных плагинов console.log становится неудобным. В
экосистеме Vite широко используется библиотека debug.
Установка:
npm install debug
Использование:
import debug from 'debug';
const log = debug('vite:my-plugin');
export default function myPlugin() {
return {
name: 'my-plugin',
transform(code, id) {
log('transform %s', id);
return code;
}
};
}
Запуск:
DEBUG=vite:my-plugin vite
Хорошая практика:
vite:plugin-name:resolve
vite:plugin-name:transform
vite:plugin-name:hmr
vite:plugin-name:load
Для диагностики важно понимать последовательность хуков.
Пример:
export default function myPlugin() {
return {
name: 'my-plugin',
options() {
console.log('options');
},
buildStart() {
console.log('buildStart');
},
resolveId(id) {
console.log('resolveId', id);
},
load(id) {
console.log('load', id);
},
transform(code, id) {
console.log('transform', id);
}
};
}
Это позволяет увидеть:
Хук configureServer даёт доступ к экземпляру Vite Dev
Server.
configureServer(server) {
console.log(server.config);
}
Через него можно диагностировать:
configureServer(server) {
server.middlewares.use((req, res, next) => {
console.log(req.url);
next();
});
}
configureServer(server) {
server.ws.on('connection', () => {
console.log('ws connected');
});
}
handleHotUpdate(ctx) {
console.log(ctx.file);
return ctx.modules;
}
Контекст содержит:
{
file,
modules,
server,
timestamp,
read
}
Проблемы:
Полезно логировать:
handleHotUpdate(ctx) {
console.log({
file: ctx.file,
modules: ctx.modules.map(m => m.url)
});
}
const virtualId = 'virtual:my-module';
const resolved = '\0' + virtualId;
export default function myPlugin() {
return {
name: 'my-plugin',
resolveId(id) {
console.log('resolve', id);
if (id === virtualId) {
return resolved;
}
},
load(id) {
console.log('load', id);
if (id === resolved) {
return 'export const value = 42';
}
}
};
}
\0Без него Rollup и Vite могут пытаться искать реальный файл.
Ошибка:
return 'virtual-module';
и:
if (id === '\0virtual-module')
Такие идентификаторы никогда не совпадут.
Неправильная трансформация ломает:
transform(code, id) {
return {
code: transformed,
map: null
};
}
Если указать map: null, Vite считает sourcemap
отсутствующим.
Наиболее безопасный способ модификации кода:
import MagicString from 'magic-string';
transform(code, id) {
const s = new MagicString(code);
s.replace('foo', 'bar');
return {
code: s.toString(),
map: s.generateMap({
hires: true
})
};
}
Многие ошибки связаны с неправильным окружением.
config(config, { command, mode }) {
console.log(command);
console.log(mode);
}
Возможные значения:
serve
build
Некоторые хуки работают по-разному:
| Хук | Serve | Build |
|---|---|---|
| configureServer | Да | Нет |
| handleHotUpdate | Да | Нет |
| generateBundle | Нет | Да |
| writeBundle | Нет | Да |
node --inspect-brk ./node_modules/vite/bin/vite.js
После запуска можно подключиться через Chrome DevTools.
Breakpoint особенно полезен внутри:
transform;resolveId;load;generateBundle;handleHotUpdate.async transform(code, id) {
debugger;
const result = await processCode(code);
return result;
}
Vite использует Rollup при production-сборке, поэтому необходимо проверять:
generateBundle;renderChunk;outputOptions;buildEnd.generateBundle(options, bundle) {
console.log(Object.keys(bundle));
}
generateBundle(options, bundle) {
for (const [fileName, chunk] of Object.entries(bundle)) {
console.log(fileName);
if (chunk.type === 'chunk') {
console.log(chunk.modules);
}
}
}
configureServer(server) {
console.log(server.moduleGraph);
}
const mod = server.moduleGraph.getModuleById(id);
console.log(mod);
Полезные поля:
url;importers;importedModules;transformResult;lastHMRTimestamp.Очень часто проблема вызвана конфликтом.
plugins: [
pluginA(),
// pluginB(),
pluginC()
]
Для сложных проблем создаётся минимальный проект:
src/
vite.config.js
package.json
Без:
export default function myPlugin() {
return {
name: 'my-plugin',
enforce: 'pre'
};
}
Варианты:
pre
post
Если другой плагин изменяет код раньше:
transform(code) {
console.log(code);
}
можно увидеть уже модифицированный результат.
Некоторые плагины используют фильтрацию файлов.
transform(code, id) {
if (!id.endsWith('.js')) {
return;
}
}
Ошибки:
.vue;Vite добавляет параметры:
Component.vue?vue&type=script
Проверка:
id.endsWith('.vue')
не сработает.
const cleanId = id.split('?')[0];
transform(code, id) {
const start = performance.now();
const result = heavyTransform(code);
console.log(
id,
performance.now() - start
);
return result;
}
Особенно важны:
Они вызываются очень часто.
async transform(code, id) {
try {
return await compile(code);
} catch (error) {
console.error(id);
throw error;
}
}
catch (error) {
error.message =
`[my-plugin] ${id}\n` +
error.message;
throw error;
}
В Rollup/Vite рекомендуется использовать встроенный API ошибок.
transform(code, id) {
if (!isValid(code)) {
this.error(
`Invalid syntax in ${id}`
);
}
}
Преимущества:
Для неблокирующих проблем:
this.warn(
`Deprecated API in ${id}`
);
Vite активно кеширует:
rm -rf node_modules/.vite
или:
vite --force
Проблемы pre-bundling часто маскируются под ошибки плагина.
Диагностика:
configResolved(config) {
console.log(config.optimizeDeps);
}
Один из наиболее полезных хуков для диагностики.
configResolved(config) {
console.log(config.plugins);
}
Позволяет:
transform(code, id, options) {
console.log(options?.ssr);
}
В SSR:
Проблема:
C:\project\src\main.js
vs
C:/project/src/main.js
import { normalizePath } from 'vite';
const normalized = normalizePath(id);
Один из самых полезных инструментов диагностики.
Установка:
npm install vite-plugin-inspect -D
Подключение:
import Inspect from 'vite-plugin-inspect';
export default {
plugins: [
Inspect()
]
};
Инструмент показывает:
После запуска доступен адрес:
/__inspect/
Там можно анализировать:
console.log('transform', id);
console.log(id);
console.log(command);
console.log(ssr);
return {
code,
map
};
configResolved(config) {
console.log(
config.plugins.map(
p => p.name
)
);
}
Используется vite-plugin-inspect.
| Симптом | Возможная причина |
|---|---|
| transform вызывается дважды | SSR + client |
| Бесконечный HMR | invalidate внутри update |
| Не работает virtual module | отсутствует \0 |
| Не срабатывает transform | неверный id |
| Ломаются breakpoint | некорректный sourcemap |
| Ошибка только в build | Rollup hook |
| Ошибка только в serve | Vite dev pipeline |
| Дублирование кода | повторная трансформация |
| Не обновляется модуль | кеш moduleGraph |
| Плагин игнорируется | неправильный enforce |