Исправление ReferenceError: HTMLElement в FFmpeg WebAssembly приложениях Quasar
Решение ошибки 'ReferenceError: HTMLElement is not defined' при загрузке ffmpeg в WebAssembly в приложениях Quasar SSR с помощью правильных методов загрузки на стороне клиента.
Как исправить ошибку “ReferenceError: HTMLElement is не определен” при загрузке ffmpeg в WebAssembly в приложении Quasar?
Я создаю приложение Quasar и пытаюсь обрабатывать видео с помощью ffmpeg в WebAssembly на стороне клиента. Я установил библиотеку и настроил всё необходимое, но при запуске ffmpeg.load() я столкнулся со следующей ошибкой:
ReferenceError: HTMLElement is not defined client:116:1
https://<my_url>:9443/@vite/client:116
Вот мой текущий код для загрузки ffmpeg:
if (!ffmpeg) {
const mod = await import('@ffmpeg/ffmpeg');
const FFmpegCtor = (mod as any).FFmpeg;
ffmpeg = new FFmpegCtor();
ffmpeg.on('log', ({ message }: { message: string }) => console.log('[ffmpeg]', message));
}
if (!ffmpegUtils) {
const utilMod = await import('@ffmpeg/util');
ffmpegUtils = {
fetchFile: utilMod.fetchFile,
toBlobURL: utilMod.toBlobURL,
};
}
// Загрузка ядра FFmpeg из локальных публичных ресурсов и использование выделенного локального воркера
const baseURL = '/ffmpeg';
const coreURL = await ffmpegUtils.toBlobURL(
`${baseURL}/ffmpeg-core.js`,
'text/javascript'
);
const wasmURL = await ffmpegUtils.toBlobURL(
`${baseURL}/ffmpeg-core.wasm`,
'application/wasm'
);
const workerURL = await ffmpegUtils.toBlobURL(
`${baseURL}/worker.js`,
'text/javascript'
);
console.log('ffmpeg loading');
await ffmpeg.load({ coreURL, wasmURL, workerURL });
Я уже попробовал следующие шаги по устранению неполадок:
- Запуск без
workerURL- та же проблема - Получение файлов WASM и JS с CDN или локально - тот же результат
- Обновление
tsconfigс:json"compilerOptions": { "baseUrl": ".", "lib": ["dom"] } - Обновление
quasar.config.jsс:javascriptviteConf.optimizeDeps = viteConf.optimizeDeps || {}; const excluded = new Set([ ...(viteConf.optimizeDeps.exclude || []), '@ffmpeg/ffmpeg', '@ffmpeg/util', '@ffmpeg/core', '@ffmpeg/core-mt', '@ffmpeg/core-st', ]); viteConf.optimizeDeps.exclude = Array.from(excluded);
Некоторые ИИ-агенты предполагают, что это может быть связано с тем, что процесс выполняется в воркере, у которого нет доступа к элементам DOM. Как я могу решить эту проблему и успешно загрузить ffmpeg в WebAssembly в моем приложении Quasar?
Ошибка “ReferenceError: HTMLElement is not defined” возникает из-за того, что ffmpeg.wasm пытается получить доступ к элементам DOM во время инициализации, но это происходит в среде без браузерских API — вероятно, в контексте Quasar SSR или в потоке worker. Для решения этой проблемы необходимо убедиться, что ffmpeg.wasm загружается только в браузерной среде.
Содержание
- Понимание ошибки
- Решения для приложений Quasar SSR
- Конфигурация Worker и альтернативы
- Полный пример реализации
- Дополнительные шаги по устранению неполадок
Понимание ошибки
Ошибка возникает из-за того, что ffmpeg.wasm пытается получить доступ к браузерным API таким, как HTMLElement, во время инициализации. В приложениях Quasar с серверным рендерингом (SSR) процесс серверного рендеринга не имеет доступа к этим браузерным API, и если ffmpeg загружается до гидратации на клиенте, произойдет сбой.
Документация Quasar SSR объясняет, что SSR-компоненты создают и манипулируют DOM в браузере, но во время серверного рендеринга эти API недоступны.
Решения для приложений Quasar SSR
1. Условная загрузка на стороне клиента
Наиболее надежное решение — убедиться, что ffmpeg загружается только в браузерной среде:
let ffmpeg = null;
let ffmpegUtils = null;
export const loadFFmpeg = async () => {
// Проверяем, находимся ли мы в браузерной среде
if (typeof window === 'undefined') {
console.log('Пропускаем загрузку FFmpeg - не в браузерной среде');
return;
}
if (!ffmpeg) {
const mod = await import('@ffmpeg/ffmpeg');
const FFmpegCtor = (mod as any).FFmpeg;
ffmpeg = new FFmpegCtor();
ffmpeg.on('log', ({ message }: { message: string }) => console.log('[ffmpeg]', message));
}
if (!ffmpegUtils) {
const utilMod = await import('@ffmpeg/util');
ffmpegUtils = {
fetchFile: utilMod.fetchFile,
toBlobURL: utilMod.toBlobURL,
};
}
const baseURL = '/ffmpeg';
const coreURL = await ffmpegUtils.toBlobURL(
`${baseURL}/ffmpeg-core.js`,
'text/javascript'
);
const wasmURL = await ffmpegUtils.toBlobURL(
`${baseURL}/ffmpeg-core.wasm`,
'application/wasm'
);
const workerURL = await ffmpegUtils.toBlobURL(
`${baseURL}/worker.js`,
'text/javascript'
);
console.log('загрузка ffmpeg');
await ffmpeg.load({ coreURL, wasmURL, workerURL });
};
2. Использование хуков жизненного цикла Quasar
В вашем Vue-компоненте используйте хуки жизненного цикла Quasar для обеспечения загрузки только на клиенте:
import { onMounted } from 'vue';
export default {
setup() {
const ffmpeg = ref(null);
onMounted(async () => {
await loadFFmpeg();
});
return { ffmpeg };
}
}
3. Динамический импорт с проверкой SSR
Если вам нужно загрузить ffmpeg в компоненте, который может быть отрендерен на сервере:
<script setup>
import { ref, onMounted } from 'vue';
const ffmpeg = ref(null);
const isLoading = ref(false);
const error = ref(null);
const loadFFmpeg = async () => {
if (typeof window === 'undefined') return;
isLoading.value = true;
try {
const mod = await import('@ffmpeg/ffmpeg');
const utilMod = await import('@ffmpeg/util');
const FFmpegCtor = mod.FFmpeg;
ffmpeg.value = new FFmpegCtor();
ffmpeg.value.on('log', ({ message }) => {
console.log('[ffmpeg]', message);
});
const baseURL = '/ffmpeg';
const coreURL = await utilMod.toBlobURL(
`${baseURL}/ffmpeg-core.js`,
'text/javascript'
);
const wasmURL = await utilMod.toBlobURL(
`${baseURL}/ffmpeg-core.wasm`,
'application/wasm'
);
await ffmpeg.value.load({ coreURL, wasmURL });
} catch (err) {
error.value = err;
console.error('Не удалось загрузить FFmpeg:', err);
} finally {
isLoading.value = false;
}
};
onMounted(loadFFmpeg);
</script>
Конфигурация Worker и альтернативы
1. Конфигурация без Worker
Если проблема связана с потоками worker, попробуйте загрузить ffmpeg без использования workers:
await ffmpeg.load({
coreURL,
wasmURL,
// Удалите workerURL для использования inline worker
});
2. Решение с предсобранным Worker
Создайте отдельный файл worker и правильно ссылайтесь на него:
// worker.js
self.importScripts('/ffmpeg/ffmpeg-core.js');
// В основном коде
const workerURL = URL.createObjectURL(
new Blob([`
importScripts('${baseURL}/ffmpeg-core.js');
`], { type: 'application/javascript' })
);
3. Загрузка на основе CDN
Рассмотрите возможность использования CDN-версии, которая может иметь лучшую совместимость с браузерами:
const coreURL = 'https://unpkg.com/@ffmpeg/core@0.12.4/dist/ffmpeg-core.js';
const wasmURL = 'https://unpkg.com/@ffmpeg/core@0.12.4/dist/ffmpeg-core.wasm';
Полный пример реализации
Вот полное решение для приложения Quasar SSR:
// utils/ffmpeg.js
import { ref } from 'vue';
// Реактивное состояние
export const ffmpegReady = ref(false);
export const ffmpegError = ref(null);
let ffmpegInstance = null;
let ffmpegUtils = null;
export const getFFmpeg = () => {
if (!ffmpegReady.value) {
throw new Error('FFmpeg не готов. Сначала вызовите loadFFmpeg().');
}
return ffmpegInstance;
};
export const loadFFmpeg = async () => {
// Пропускаем на сервере
if (typeof window === 'undefined') {
console.log('Пропускаем загрузку FFmpeg - серверная сторона');
return;
}
if (ffmpegReady.value) {
return ffmpegInstance;
}
try {
// Динамические импорты
const mod = await import('@ffmpeg/ffmpeg');
const utilMod = await import('@ffmpeg/util');
// Создание экземпляра FFmpeg
ffmpegInstance = new mod.FFmpeg({
log: true,
});
ffmpegInstance.on('log', ({ message }) => {
console.log('[ffmpeg]', message);
});
// Сохранение утилит
ffmpegUtils = {
fetchFile: utilMod.fetchFile,
toBlobURL: utilMod.toBlobURL,
};
// Настройка URL
const baseURL = '/ffmpeg';
const coreURL = await ffmpegUtils.toBlobURL(
`${baseURL}/ffmpeg-core.js`,
'text/javascript'
);
const wasmURL = await ffmpegUtils.toBlobURL(
`${baseURL}/ffmpeg-core.wasm`,
'application/wasm'
);
// Загрузка FFmpeg
await ffmpegInstance.load({
coreURL,
wasmURL,
});
ffmpegReady.value = true;
console.log('FFmpeg успешно загружен');
} catch (err) {
ffmpegError.value = err;
console.error('Не удалось загрузить FFmpeg:', err);
throw err;
}
};
// Функция очистки
export const cleanupFFmpeg = () => {
if (ffmpegInstance) {
ffmpegInstance = null;
ffmpegUtils = null;
ffmpegReady.value = false;
}
};
И в вашем компоненте:
<template>
<div>
<q-btn
:loading="!ffmpegReady"
:disable="!ffmpegReady"
@click="processVideo"
>
Обработать видео
</q-btn>
<div v-if="ffmpegError" class="text-negative">
Ошибка загрузки FFmpeg: {{ ffmpegError.message }}
</div>
</div>
</template>
<script setup>
import { onMounted, onBeforeUnmount } from 'vue';
import { loadFFmpeg, getFFmpeg, ffmpegReady, ffmpegError, cleanupFFmpeg } from 'utils/ffmpeg';
onMounted(async () => {
await loadFFmpeg();
});
onBeforeUnmount(() => {
cleanupFFmpeg();
});
const processVideo = async () => {
try {
const ffmpeg = getFFmpeg();
// Ваш код обработки видео здесь
console.log('Обработка видео...');
} catch (err) {
console.error('Обработка видео не удалась:', err);
}
};
</script>
Дополнительные шаги по устранению неполадок
1. Обновление конфигурации Vite
Обновите ваш quasar.config.js для правильной обработки зависимостей ffmpeg:
// quasar.config.js
const { configure } = require('quasar/wrappers');
configure(({ ctx }) => {
return {
// ... другая конфигурация
vite: {
optimizeDeps: {
exclude: [
'@ffmpeg/ffmpeg',
'@ffmpeg/util',
'@ffmpeg/core',
'@ffmpeg/core-mt',
'@ffmpeg/core-st'
]
},
// Добавьте это для правильной обработки worker
define: {
global: 'globalThis',
},
// Добавьте это для поддержки SharedArrayBuffer при необходимости
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
}
}
}
};
});
2. Конфигурация TypeScript
Убедитесь, что ваш tsconfig.json включает правильные типизации DOM:
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["node"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
3. Настройка публичных ассетов
Убедитесь, что ваши файлы ffmpeg правильно размещены в публичной директории:
public/
└── ffmpeg/
├── ffmpeg-core.js
├── ffmpeg-core.wasm
└── worker.js
4. Переменные окружения
Добавьте переменные окружения для контроля загрузки ffmpeg:
// .env
VITE_FFmpeg_ENABLED=true
VITE_FFmpeg_DEBUG=true
// .env.ssr
VITE_FFmpeg_ENABLED=false
Затем в вашем коде:
const shouldLoadFFmpeg = import.meta.env.VITE_FFmpeg_ENABLED !== 'false' && typeof window !== 'undefined';
Источники
- Документация Quasar SSR
- Проблемы на GitHub FFmpeg.wasm - решения ReferenceError
- Поддержка SharedArrayBuffer в Cloudflare
- Руководство по устранению неполадок FFmpeg.wasm
- Конфигурация Quasar SSR
- SSR с WebAssembly
- Проблемы загрузки FFmpeg.wasm в Vanilla JavaScript
Заключение
Ошибка “ReferenceError: HTMLElement is not defined” в ffmpeg.wasm в приложениях Quasar в основном вызвана попыткой загрузки кода, зависимого от браузера, в контексте серверного рендеринга. Ключевые решения:
- Всегда проверяйте браузерную среду с помощью
typeof window !== 'undefined'перед загрузкой ffmpeg - Используйте хуки жизненного цикла Quasar такие как
onMountedдля обеспечения инициализации на стороне клиента - Правильно настраивайте vite с правильными исключениями и настройками worker
- Рассмотрите конфигурацию без worker если проблема сохраняется с потоками worker
- Реализуйте правильную обработку ошибок и состояния загрузки для лучшего пользовательского опыта
Следуя этим подходам, вы можете успешно интегрировать ffmpeg.wasm в ваше приложение Quasar SSR, избегая ошибок ссылки на HTMLElement. Ключ заключается в том, чтобы убедиться, что ffmpeg.wasm загружается и выполняется только в браузерной среде, где все необходимые браузерные API доступны.