14. Оптимизация и lazy-loading
Одна из главных суперсил Qwik — автоматическая оптимизация бандла и ленивая загрузка. Понимание того, как работает оптимизатор Qwik, помогает писать максимально быстрые приложения.
Символ $ — основа оптимизации
Заголовок раздела «Символ $ — основа оптимизации»Каждый раз, когда вы добавляете $ к функции в Qwik, оптимизатор автоматически:
- Выносит функцию в отдельный файл-чанк
- Создаёт QRL (URL-ссылку) на этот чанк
- Загружает чанк только при необходимости
// Что вы пишете:export const Counter = component$(() => { const count = useSignal(0);
return ( <button onClick$={() => count.value++}> {count.value} </button> );});
// Что создаёт оптимизатор (концептуально):
// chunk-counter.js — компонентexport const Counter = component$(qrl('./chunk-counter_onClick.js', 'Counter_onClick'));
// chunk-counter_onClick.js — обработчик (отдельный файл!)export const Counter_onClick = () => { count.value++; };Как работает $
Заголовок раздела «Как работает $»Символ $ → Qwik Optimizer → Отдельный чанк──────────────────────────────────────────────────────component$() → chunk-comp.js → Загружается при рендереonClick$() → chunk-handler.js → Загружается при кликеuseTask$() → chunk-effect.js → Загружается при измененииonInput$() → chunk-input.js → Загружается при вводеПравила использования $
Заголовок раздела «Правила использования $»1. Функции должны быть захватываемыми
Заголовок раздела «1. Функции должны быть захватываемыми»// ✅ Правильно — функция захватывает только сериализуемые данныеconst name = useSignal('Qwik');const onClick = $(() => { console.log(name.value); // Signal — сериализуемый});
// ❌ Неправильно — Promise не сериализуемconst promise = fetch('/api');const onClick = $(() => { promise.then(r => r.json()); // Ошибка оптимизатора!});
// ✅ Правильно — создаём Promise внутри $const onClick = $(() => { fetch('/api').then(r => r.json()); // OK!});2. Импорты в $ должны быть статическими
Заголовок раздела «2. Импорты в $ должны быть статическими»// ✅ Правильноimport { someUtil } from './utils';
const handler = $(() => { someUtil(); // Статический импорт — OK});
// ❌ Избегайте динамических импортов внутри $const handler = $(() => { import('./heavy-lib').then(lib => lib.doSomething()); // Проблема});Prefetching стратегии
Заголовок раздела «Prefetching стратегии»Qwik поддерживает prefetching — предварительную загрузку чанков:
// vite.config.ts или qwik-city конфигурацияexport default defineConfig({ plugins: [ qwikCity(), qwikVite({ client: { // Стратегия prefetch prefetchStrategy: { implementation: { linkInsert: 'html-append', // Вставить <link rel="prefetch"> linkRel: 'prefetch', // Тип prefetch } } } }) ]});Bundle Splitting анализ
Заголовок раздела «Bundle Splitting анализ»# Анализ бандла после сборкиnpm run build
# Размеры бандлов в dist/build/dist/ build/ q-manifest.json # Манифест всех чанков q-abc123.js # Компонент (0.5 КБ) q-def456.js # Обработчик onClick (0.2 КБ) q-ghi789.js # Эффект (0.3 КБ)Service Worker и Prefetch
Заголовок раздела «Service Worker и Prefetch»Qwik использует Service Worker для кеширования и prefetch:
import { setupServiceWorker } from '@builder.io/qwik-city/service-worker';
setupServiceWorker();
// Qwik автоматически предзагружает вероятные следующие взаимодействия// на основе паттернов использованияОптимизация с noSerialize
Заголовок раздела «Оптимизация с noSerialize»Для браузерных объектов, которые не нужно сериализовать:
import { noSerialize, useStore } from '@builder.io/qwik';
const store = useStore({ // Регулярные данные сериализуются items: [] as string[], count: 0,
// noSerialize — только клиентский объект audioContext: noSerialize<AudioContext | undefined>(undefined),});