Worklet-ы: CSS Houdini, Audio Worklet

Worklet представляет собой специализированный механизм выполнения кода в отдельном, изолированном контексте с минимальным доступом к глобальному окружению браузера. В отличие от традиционных Web Workers, worklet-ы ориентированы на выполнение высокопроизводительных, коротких и частых операций, таких как рендеринг графики, обработка аудио или вычисления, связанные с анимациями.

Ключевая особенность worklet-ов заключается в их интеграции в критические пути браузера: рендеринг, аудио-граф и анимационные циклы. Это делает их крайне чувствительными к производительности и архитектуре кода.


Общая модель выполнения Worklet

Worklet выполняется в отдельной среде, но с более ограниченным API по сравнению с Web Worker:

  • отсутствует доступ к DOM
  • отсутствует доступ к window
  • отсутствует доступ к синхронным сетевым операциям
  • допускается только строго определённый набор API

Код worklet-ов загружается через метод addModule(), который принимает путь к JavaScript-файлу и выполняет его в специализированном контексте.

CSS.paintWorklet.addModule('/paint-worklet.js');

Архитектурно worklet-ы ориентированы на многократное выполнение небольших функций, а не на длительные фоновые задачи.


CSS Houdini и Worklet-ы

CSS Houdini представляет собой набор низкоуровневых API, позволяющих разработчикам напрямую вмешиваться в процесс рендеринга CSS. Worklet-ы являются центральным механизмом этой технологии.

Paint Worklet

Paint Worklet позволяет создавать пользовательские CSS-изображения, генерируемые программно во время рендеринга.

Регистрация paint-worklet:

registerPaint('diagonalLines', class {
  paint(ctx, geom, properties) {
    const step = 10;

    for (let i = 0; i < geom.width; i += step) {
      ctx.beginPath();
      ctx.moveTo(i, 0);
      ctx.lineTo(i - geom.height, geom.height);
      ctx.stroke();
    }
  }
});

Использование в CSS:

.box {
  background-image: paint(diagonalLines);
}

Особенности Paint Worklet:

  • выполняется при каждом пересчёте layout или repaint
  • имеет доступ к canvas-подобному API
  • не может обращаться к DOM
  • должен быть максимально оптимизирован

Layout Worklet

Layout Worklet позволяет полностью переопределять алгоритмы раскладки элементов.

Пример регистрации:

registerLayout('simpleGrid', class {
  async intrinsicSizes() {}

  async layout(children, edges, constraints, styleMap) {
    let inlineOffset = 0;
    let blockOffset = 0;

    const childFragments = [];

    for (const child of children) {
      const childFragment = await child.layoutNextFragment();

      childFragment.inlineOffset = inlineOffset;
      childFragment.blockOffset = blockOffset;

      inlineOffset += childFragment.inlineSize;

      childFragments.push(childFragment);
    }

    return {
      childFragments
    };
  }
});

Особенности Layout Worklet:

  • управляет потоковой раскладкой элементов
  • позволяет создавать собственные модели flex/grid
  • требует строгого контроля производительности
  • сложен для отладки из-за асинхронной природы

Animation Worklet

Animation Worklet используется для создания высокопроизводительных анимаций, работающих вне основного thread’а.

Пример:

registerAnimator('parallaxScroll', class {
  constructor(options) {
    this.speed = options.speed || 1;
  }

  animate(currentTime, effect) {
    const offset = currentTime * this.speed;
    effect.localTime = offset;
  }
});

Использование:

const effect = new KeyframeEffect(
  element,
  [{ transform: 'translateY(0px)' }, { transform: 'translateY(100px)' }],
  { duration: 1000, fill: 'both' }
);

new WorkletAnimation('parallaxScroll', effect, document.timeline, { speed: 0.5 }).play();

Audio Worklet

Audio Worklet предназначен для обработки аудиопотока в реальном времени с минимальной задержкой. Он является заменой устаревшего ScriptProcessorNode.

Главное требование — детерминированная и быстрая обработка аудиобуфера.


Базовая структура AudioWorkletProcessor

class GainProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    const input = inputs[0];
    const output = outputs[0];

    const gain = parameters.gain ? parameters.gain[0] : 1;

    for (let channel = 0; channel < input.length; channel++) {
      const inputChannel = input[channel];
      const outputChannel = output[channel];

      for (let i = 0; i < inputChannel.length; i++) {
        outputChannel[i] = inputChannel[i] * gain;
      }
    }

    return true;
  }
}

registerProcessor('gain-processor', GainProcessor);

Использование AudioWorklet

const audioContext = new AudioContext();

await audioContext.audioWorklet.addModule('/gain-processor.js');

const node = new AudioWorkletNode(audioContext, 'gain-processor', {
  parameterData: { gain: 0.5 }
});

const source = audioContext.createMediaElementSource(audioElement);

source.connect(node).connect(audioContext.destination);

Особенности Audio Worklet

  • выполняется в аудиорендеринг-потоке
  • гарантирует низкую задержку обработки
  • не допускает блокирующих операций
  • требует строгой оптимизации циклов обработки
  • использует фиксированные аудиофреймы (обычно 128 сэмплов)

Ограничения Worklet-ов

Архитектура worklet накладывает жёсткие ограничения:

  • отсутствие DOM-доступа
  • невозможность использования fetch в синхронном режиме
  • ограниченный набор встроенных API
  • строгие требования к сериализации данных
  • невозможность длительных вычислений

Эти ограничения обусловлены необходимостью сохранения стабильного FPS и минимальной задержки в критических подсистемах браузера.


Интеграция Worklet-ов в сборку Parcel

При использовании сборщика Parcel worklet-файлы рассматриваются как отдельные entry-point модули. Это связано с тем, что worklet-скрипты загружаются через addModule() и должны существовать как самостоятельные JavaScript-ресурсы.

Структура проекта

src/
  index.js
  paint-worklet.js
  audio/
    gain-processor.js

Подключение в коде приложения

import paintWorkletUrl from './paint-worklet.js?url';

CSS.paintWorklet.addModule(paintWorkletUrl);

Parcel обрабатывает ?url как запрос на генерацию отдельного файла и возвращает путь к собранному артефакту.


Code splitting worklet-ов

Parcel автоматически выносит worklet-ы в отдельные чанки при корректной конфигурации входных точек. Это важно для:

  • lazy loading
  • уменьшения initial bundle size
  • изоляции execution context

Особенности сборки AudioWorklet

AudioWorklet модули также должны быть доступны по URL:

import audioWorkletUrl from './audio/gain-processor.js?url';

await audioContext.audioWorklet.addModule(audioWorkletUrl);

Parcel обеспечивает:

  • транспиляцию современного JS при необходимости
  • генерацию стабильного URL
  • корректное кеширование файлов

Производственные аспекты использования Worklet-ов

Эффективное применение worklet-архитектуры требует учёта ряда факторов:

  • минимизация передачи данных между потоками
  • использование структурированных данных вместо сложных объектов
  • избегание частых пересозданий worklet-контекста
  • оптимизация вычислений внутри process и paint
  • контроль частоты вызовов через браузерные события

Особенно критично это для Audio Worklet, где даже небольшие задержки приводят к слышимым артефактам.


Сравнение Worklet-ов и Web Workers

Характеристика Worklet Web Worker
Доступ к DOM отсутствует отсутствует
Производительность выше в спец. задачах универсальная
Задержка минимальная средняя
Использование рендер/аудио/анимации фоновые задачи
Гибкость ограниченная высокая

Worklet-ы не заменяют Web Workers, а дополняют их в узкоспециализированных сценариях.


Архитектурная роль Worklet-ов в современных приложениях

Worklet-ы формируют слой низкоуровневой оптимизации, позволяющий переносить критические вычисления ближе к внутренним конвейерам браузера. Их использование оправдано в случаях, где:

  • требуется стабильный frame rate
  • важна минимальная задержка аудиообработки
  • необходимы кастомные алгоритмы рендеринга
  • стандартные CSS и JS API оказываются недостаточными

В таких сценариях worklet-ы становятся частью архитектуры производительного frontend-слоя, интегрируясь с системой сборки и модульности JavaScript-приложений.