Skip to content

0011 Defaults: one install, TipTap by default, lazy elements

  • Status: accepted (2026-10-29)
  • Amended 2026-10-29: the first-load budget is 96 KiB raw / 32 KiB gzip (was 90 / 30); the group palette took astrolabe-player.js to 29.9 KiB gzip, and pictures, the URL policy and open answers belong in the first load too.
  • Amended 2026-10-30: each element chunk is at most 32 KiB raw / 10 KiB gzip, and the editor's first load (TipTap aside) at most 370 KiB raw / 112 KiB gzip plus 10 KiB raw / 3.5 KiB gzip per built-in type: the editor loads every type's definition, manifest, catalogs and descriptor (live issues, the preview), so its budget grows with each type. packages/astrolabe/scripts/check-bundles.mjs holds the numbers.

Hosts wired everything by hand: createEngine({ components: builtInDefinitions }), definePlayer({ elements }) and the authoring kit around their own editor, with TipTap only as an example. The student bundle carried every element (180.6 KiB raw / 51 KiB gzip for six types, about 420 / 125 projected for 21). The library has to be easy to use, with defaults that are easy to override.

  • E3, the editor. A package @astrolabe/editor-tiptap with a ready <astl-editor>: a TipTap surface, the insert menu, the inspector, the issues list and the preview. @astrolabe/editor stays free of any WYSIWYG engine (the adapter contract, the kit and the textarea adapter); another editor is another adapter, with its element in slot="surface". Only editor-tiptap imports TipTap: its main entry through a dynamic import() of the surface only, and its /tiptap entry (astrolabeExtensions, createTiptapAdapter) for hosts with their own TipTap editor.
  • E4, one install. packages/cdn becomes the umbrella astrolabe (a working name), with the entries astrolabe/engine, /player, /editor and /server and the script-tag bundles astrolabe.js, astrolabe-player.js and astrolabe-editor.js. The defaults are every built-in component, the playful and notebook looks next to the default look, English and Polish, and TipTap. Nothing is defined on import: definePlayer() and defineEditor() with no arguments wire everything, and the bundles call them on load. The engine secret is optional and only compile needs it; there is no default secret. The @astrolabe/* packages stay for lean installs.
  • E5, lazy elements. The player loads component elements per tag (LazyElement { tag, load }): a page with only gaps downloads only the gap element. The built-in lists are lazy one-liners.
  • Options replace defaults. An option replaces its default and is never merged with it. The built-in lists are exported (builtInDefinitions, builtInElements, builtInEditors, builtInStyleTemplates, builtInManifests): extend with [...builtIns, mine], restrict with .filter(...). In defineEditor, editors and elements default to the built-in entries of the components tags, so { components: [gap, choice] } needs no other option; a passed editors or elements still replaces its default. Always on, not options: each package's English and Polish messages, the engine's built-in slot kinds and DEFAULT_PROFILE; catalogs, slotKinds and profile add to them.
  • One install and no options cover the common case, and every default is one option away.
  • One rule for every option, and one that can restrict: a merge could only ever add.
  • Defining on import would run during server-side imports (Astro, Next) and break sideEffects: false.
  • A known secret would undo the keyed shuffle; for astl-order the source order is the answer.
  • TipTap is about 385 KiB raw / 117 KiB gzip. A page that brings its own editor must not download it, so the surface is a dynamic import and the TipTap building blocks are a separate entry.
  • Lazy elements keep the student's first load flat as components are added: about 84 KiB raw / 28 KiB gzip with 21 types instead of about 420 / 125. Measured with the six built-ins: astrolabe-player.js loads 88.2 KiB raw / 29.5 KiB gzip first (budget 90 / 30), and each element adds 12.6 to 27.7 KiB raw (5.1 to 9.4 gzip) when a view first uses it.
  • Merging options with defaults ({ components: [mine] } meaning the built-ins plus mine): nothing could be restricted, and every option would need two rules.
  • Defining elements on import: server-side imports would register custom elements.
  • A default secret: it undoes the keyed option order.
  • TipTap inside @astrolabe/editor, or any WYSIWYG engine in every install: hosts with their own editor would load it for nothing (0006 stays for the kit).
  • Eager built-in elements in the student bundle: it grows with every component.
  • Folding the calm look into the default look: that would change the default. calm is dropped instead, and a source that still names it plays in the default look with the warning STYLE_TEMPLATE_UNKNOWN.
  • Shared chunks that import an entry: a page loading astrolabe.js?v=2 would run a second instance and throw "already defined".
  • definePlayer() from astrolabe/player plays every built-in, and defineEditor() from astrolabe/editor gives TipTap with everything; the quick start lists every default and its override.
  • Element chunks wait for the view to be parsed (one round trip). A chunk that fails fails the view with load_failed, and the next view retries it.
  • A missing secret fails at the first compile, not at startup.
  • Server-only users of the umbrella still install TipTap; @astrolabe/* stay for lean installs.
  • The published name of the umbrella waits for the npm scope: unscoped astrolabe is a 2014 Protractor package.
  • After every build, packages/astrolabe/scripts/check-bundles.mjs checks no answer logic in astrolabe-player.js, no editor code in either player bundle, TipTap only behind a dynamic import in astrolabe-editor.js, no chunk that imports an entry, and the budgets of the first loads and the element chunks.