[]previous and next page

Bundle size

How CanvasFlow keeps your bundle small, what each import costs, and how the budgets are enforced.

CanvasFlow is one package with several entry points and no side effects at module level. Your bundler keeps what you import and drops the rest, down to individual functions.

What each import costs#

Each row is a small app that imports only that, bundled against the published package and minified (before gzip):

You import Size
Vec from canvasflow 1.9 KB
SyncDoc from canvasflow 3.5 KB
Convex functions from canvasflow/convex 7.2 KB
The WebSocket server from canvasflow/server 7.8 KB
CollabClient and a transport from canvasflow/collab 15.7 KB
createEditor from canvasflow 173 KB
createCanvasFlow from canvasflow/dom 229 KB
The headless canvas of any binding (<CanvasFlowCanvas/>, createFlowCanvas, mountCanvasFlowCanvas) 230 KB
canvasflow/dom with canvasflow/ui 303 KB
The canvas with the default UI, in any binding 304 to 305 KB

The binding itself costs next to nothing: the headless canvas is the same size in React, Vue, Solid, Svelte, Angular, Preact and ShadowJS, within a kilobyte. The framework is a peer, so it is never counted in this table.

A full editor with every shape, tool, action, the renderer, input handling, text editing and the default UI is about 300 KB before compression, with zero dependencies behind it. Compressed with gzip it is a fraction of that: this site's own landing page measures it when it is built.

The guarantees#

  • A web build never contains React Native. canvasflow/react-native is its own entry, and only Metro reads its react-native condition.
  • The core, the DOM host, the UI, collaboration and Convex never import a framework. Each binding imports its own framework and nothing else: the Vue entry never imports React, and the React entry never imports Vue.
  • A headless canvas doesn't pull in the UI. <CanvasFlowCanvas/> and its siblings in every binding never reference canvasflow/ui. (<CanvasFlow ui={false}/> mounts no UI, but still references it.)
  • A server pays for what it uses. Syncing on Convex costs the CRDT, not the editor.

How it's enforced#

The CanvasFlow repository runs a tree-shaking check on every change. It builds each of the apps above against the built package and fails when:

  • an entry imports something it must never import (React Native in a web build, a framework in the core, React in the Vue entry, Node built-ins in browser code),
  • a layer leaks into a bundle that shouldn't contain it (the UI in a headless build, the DOM host in a server build),
  • any bundle grows past its budget.

Contributors keep it green with two habits: no top-level work at module scope, and /* @__PURE__ */ on any top-level call that builds a value. Both are written down in the package's AGENTS.md.

Tips for your app#

  • Import from the narrowest entry: canvasflow for the editor and types, canvasflow/dom for the web, canvasflow/collab only where you sync.
  • Load the canvas lazily on pages where it's below the fold (import('canvasflow/dom') inside an effect), like the demos on this site do.
  • Type-only imports (import type { Editor } from 'canvasflow') cost nothing.