This documents how editor builds reach the CDN. There is no beta / pre-release
channel — we develop backwards-compatible and ship through the normal
master → latest flow, so every consumer can point at one stable URL.
Three things, three places:
src/editor.ts is the entry point.typograph-canvas), used as a pinned
dependency. Its TypeScript gets bundled into editor.js at build time.scripts/jsColorEngineWeb.js) — a standalone,
global-scope script (a WASM/webpack build that defines window.jsColorEngine).
It is not part of the esbuild bundle (see "The colour engine" below).typograph-cdn) served at
https://cdn.typograph.nl/. Files land in folders like editor/latest/ and
editor/1.2.0/.Each deploy syncs the dist/ folder into one version folder:
typograph-cdn (bucket) → https://cdn.typograph.nl/
│
└── editor/
├── latest/ ← current master build (what everyone uses)
│ ├── editor.js
│ ├── editor.min.js
│ ├── scripts/
│ │ └── jsColorEngineWeb.js ← the colour engine, shipped alongside
│ ├── stylesheets/ ← style.css (+ its ../images/ refs resolve here)
│ ├── images/ ← UI icons, cursors, colour-picker assets
│ └── manifest.json
└── 1.2.0/ ← a tagged release, frozen forever
└── ...same files...
Everything the browser loads at runtime — the bundle, the colour engine, the CSS,
and the icons — ships together on every merge. A change to a stylesheet or an icon
goes live through the same master → latest deploy as a code change; there is never
a manual build or upload step.
The canvas colour wrapper (typograph-canvas/scripts/colorengine.js) calls a
bare global — new jsColorEngine.Profile(). That global is provided by
scripts/jsColorEngineWeb.js, a self-contained script that must be loaded on the
page before the editor module runs. esbuild bundles the canvas TypeScript into
editor.js, but it does not pull in this global-scope engine file — nothing
imports it, and it is a webpack IIFE that assigns a top-level var jsColorEngine
which would become module-scoped (and thus invisible to the wrapper) if inlined
into the ESM bundle.
So the engine travels to the CDN as its own file next to editor.js:
npm run build:engine (part of build:all) copies
scripts/jsColorEngineWeb.js into dist/, so aws s3 sync dist/ uploads it
automatically./editor/<version>/jsColorEngineWeb.js
straight from scripts/ (the watch container only rebuilds dist/editor.js, so
the source file is the reliable place to serve from in dev).Same URL in both environments; only the on-disk source differs.
A host page (portal-ui via EDITOR_CDN_URL, the demo, a customer page) loads two
tags, engine first:
<!-- classic script: runs synchronously, defines window.jsColorEngine -->
<script src="https://cdn.typograph.nl/editor/latest/scripts/jsColorEngineWeb.js"></script>
<!-- module script: deferred, so the global is guaranteed to exist when it runs -->
<script type="module" src="https://cdn.typograph.nl/editor/latest/editor.js"></script>
Point the CSS <link> at the CDN copy too
(…/editor/latest/stylesheets/style.css); its own ../images/… references resolve
relative to that file, so the colour-picker/CSS icons load from the CDN
automatically.
Asset base URL. Icons/cursors referenced from JavaScript (e.g.
url(images/ui/openhand.svg)) are resolved through assetUrl() in src/assets.ts,
which builds new URL(path, import.meta.url). Because import.meta.url is the
editor bundle's own URL, these assets load from wherever editor.js is served —
the CDN — regardless of which host page embeds the editor. No host configuration is
required. Only editor-owned images/ assets go through assetUrl; host/backend
paths (php/…, userdata/…) and the user's uploaded images stay relative to the
host page on purpose.
npm run build:all runs, in order: editor.js (esbuild bundle) → editor.min.js →
build:assets (copy the colour engine into dist/scripts/, and stylesheets/ +
images/ into dist/) → docs.
Language assets (hyphenation patterns, spell-check dictionaries) are not built or
deployed here. They are served from https://cdn.typograph.nl/languages, which the canvas
library defaults to, and are generated in typograph-canvas via npm run copy:lang-assets.
See PLAN-Language_assets.md in that repo.
The canvas version baked into the bundle is pinned in package.json:
"typograph-canvas": "git+ssh://git@bitbucket.org/get-interactive/typograph-canvas.git#<commit-sha>"
npm install checks out exactly that canvas commit every time — predictable builds,
same philosophy as composer.lock in the PHP services. To adopt newer canvas code,
bump the SHA after the #, commit, and push.
bitbucket-pipelines.yml is a lookup table keyed on what was pushed:
| You push... | Pipeline that runs | Result on CDN |
|---|---|---|
| any feature branch | default |
nothing — build only, as a smoke test |
master |
build → deploy → docs | editor/latest/ updated |
a tag like v1.3.0 |
build → deploy → docs | editor/1.3.0/ created, latest/stable markers set |
Feature branches build but never deploy, so an experiment can never overwrite the
CDN. Work becomes live only by merging to master — which is exactly why every
change must stay backwards-compatible with existing consumers of editor/latest/.
Credentials: the deploy step declares deployment: staging, reusing the
CF_ACCOUNT_ID and AWS keys already stored in Bitbucket.
You change editor code:
commit → merge to master → Bitbucket builds & deploys automatically
→ ~2 min later, cdn.typograph.nl/editor/latest/editor.js is the new build
Someone changes canvas / colour-engine code:
The editor keeps building the pinned canvas commit until you bump it:
git log in the canvas repo).package.json: replace the SHA after the #.If the change touches scripts/jsColorEngineWeb.js itself, just commit the new file
here — build:engine picks it up on the next deploy.
Checking what's live:
https://cdn.typograph.nl/editor/latest/manifest.json records the exact commit,
branch, and build date currently deployed. If the browser serves an old bundle,
that is Cloudflare/browser caching — hard-refresh; the manifest is the truth.