The EffectSoup monorepo structure and rendering flow.
Monorepo Structure
text
effectsoup/
apps/
web/ — Next.js app (playground, editor, docs, home)
packages/
effectsCore/ — Pure TS image primitives
effectsPresets/ — Effect presets and schema
effectsWorker/ — Web Worker bridge
effectsoup/ — Meta-package (re-exports)All four packages are pure TypeScript, browser-safe, and tree-shakeable. No package has a DOM, Node.js, or framework dependency.
Package Boundaries
- The web app imports from packages; packages never import from the app.
- Packages depend only on
@effectsoup/corefor types and primitives. - The worker package is the only package that imports from both core and presets.
Render Flow
- Decode — User uploads an image → decoded into
PixelBuffervia Canvas 2D API. - Crop — User adjusts crop/zoom/offset →
CropConfigupdated in editor state. - Resolve — User selects a preset → intensity defaults applied. Editor calls
intensityMapper(intensity, overrides)→ResolvedPresetParameters. - Render — Client sends a
RenderJobto the Web Worker viapostMessage. The worker applies the viewport transform, builds the pipeline from the preset'screatePipeline, runs the effect, and transfers the result back via zero-copyArrayBuffertransfer. - Display — Main thread draws the result to a canvas for preview or export.
Preset Lifecycle
- Definition time: Each preset is a static
EffectPresetobject with anintensityMapper,advancedControlSchema, andcreatePipelinefactory function. - Registration: Presets are added to the
allPresetsarray inpackages/effectsPresets/src/index.ts. - Render time:
intensityMapperresolves parameters →createPipeline(params)returns anEffectPipeline→ pipeline is called with the source buffer and resolved params.
Worker Lifecycle
- Created when
EffectsWorkerClientis instantiated with a worker script URL. - Listens for
"render"messages containing aRenderJob. - Listens for
"cancel"messages to mark jobs as obsolete via version comparison. - Each render increments a version counter; stale results are silently discarded.
- Results are posted back as
"renderResult"with the output buffer. - Call
client.terminate()to clean up the worker when done.
Why Web Workers?
EffectSoup runs rendering in a Web Worker by default to keep the UI thread responsive during heavy computation. The worker handles crop, pipeline execution, and buffer creation. Canvas drawing and input decoding remain on the main thread (they require DOM APIs that workers don't have access to).
Why not WebGL or WASM?
The engine is pure TypeScript for portability, tree-shakeability, and ease of contribution. No WebGL, no WASM, no native dependencies. This means:
- Every function can be called from any context: main thread, worker, Node.js, or edge runtime.
- The engine is fully deterministic — no GPU driver variance, no floating-point inconsistencies across platforms.
- Bundlers can tree-shake unused functions.
See Also
- Core API Reference — PixelBuffer and all primitives
- Presets API Reference — EffectPreset lifecycle
- Worker API Reference — EffectsWorkerClient internals
- Performance Guide — optimization tips