30. Deploying: by moving a name
tcm build <app>writes an immutable, content-addressed build to.tcm/apps/<app hash>/(an app with apwa, the hash of both). An existing build is reused unless--force, or another tcm made it (what tcm puts beside the app, its runtime, is part of the build: a new tcm builds the app again).--static <dir>writes it out as a static site, for any static host, every page rendered as it's built, when the app calls no server functions (static sites). A React component app's build also holds a server-rendering bundle (server/, never served), whichtcm startrenders each page's first paint with (React).- Builds at once don't clash. Each build is made in a directory of its own (
.tcm/build/<hash>.<pid>-<random>/) and put in its place whole, by one rename. Two builds of one app at once (two verifies, atcm buildwhiletcm startdeploys) both succeed: the first in place stays and the other drops its own, the same build (with--force, or another tcm's in place, it's replaced, the old one moved aside first). A server reads the build before or the build after, never one half-written; there's no lock around builds.tcm dev's files are its own too (.tcm/web/<pid>-<random>/, removed when it stops). - Browser code ships in a few chunks, kept across deploys. React, other npm packages, tcm's runtime and the app's
own code are each a chunk, named by its content, in
.tcm/apps/assets/(served under/_app/assets/, immutable), which every build shares. Chunks import each other through an import map in the page, so a chunk that didn't change keeps its URL even when one it imports did: a deploy that changes the app's code re-downloads only its chunk. So is each stylesheet<Stylesheet>links to (React), named by its hash. Assets are stored compressed (brotli and gzip) and served as the browser accepts. - An updated chunk arrives as the difference from the one the browser has. Each chunk is offered as a
compression dictionary for its next version (
Use-As-Dictionary), so a browser that has the app's previous chunk asks for the new one with it (Available-Dictionary), and gets it compressed against it (dcb): after a small change, a few hundred bytes instead of the chunk. Each difference is made once and kept (.tcm/apps/deltas/, pruned with the builds). Browsers without it (and anything in between that drops the headers) get plain brotli. tcm start <app>serves the build the app's name points to now. When the name moves (tcm update,mv,reset,pull), it builds the new version if needed and switches to it. Deploying and rolling back are both moving a name, like Unison's service names pointing at service hashes. It also runs the job worker and schedules, and serves HTTP routes with--http.- There is no version skew. Earlier builds stay served, and their server functions stay callable. A tab
opened before a deploy keeps loading its own assets and calling the exact code it was built against, until it
reloads.
tcmts.cli.test.e2e.proddeploys v2 and checks the v1 tab still works, then rolls back. tcm appslists builds;tcm apps prune --keep Nremoves old ones. It keeps the current build plus the N newest others;tcm start --keep Ndoes this after every deploy. A tab still on a pruned build, on a kept build whose code the codebase no longer has (as the web UI's after tcm changes), or on a build this server never had (served from another copy of the codebase, or before a deploy that started afresh) gets410 Gonefrom its next server call, and the browser runtime reloads the page onto the current version:tcm startanswers any hash it doesn't serve with a 410. Pruning leaves what's in use, in any process: the build a runningtcm startserves, and a build being made and its assets. Each holds a lease (.tcm/build/lease.<pid>-<random>.json), gone when it's done or its process ends.- Pushing is deploying.
tcm push prodto a server runningtcm startsends only the definitions it lacks. The server verifies them by re-hashing, then itstcm startsees the name move, builds and switches. There's no deploy script and no restart. - TLS.
tcm start --tlsserves HTTPS; cookies then default toSecure. - Errors. Unexpected errors are logged on the server and reach the browser only as "internal error" (server API). A call that fails is that call's 500: the server keeps running.
- Data changes are covered in data: old pages that read rows written by newer code get a 410 and reload.
30.1 Under a path of a site
tcm start <app> --base /examples/notes serves the app under that path, beside other things on one site (tcm's
docs site runs its examples so, behind tcm serve --proxy):
- Requests outside the path aren't the app's (404), and the path itself redirects to it with its slash.
- The page's assets, its server function calls (
<base>/__tcm/…) and its plain HTTP routes are under the path. The build doesn't know where it's served: the page is told (__TCM_BASE__) as it's sent, so one build serves any path, and its chunks stay the same. - Cookies are for the path unless they say otherwise (
setCookie(name, value, { path })), so apps on one site keep their sessions apart. - The react library's paths are within it:
useLocation().pathis/at the base,navigate("/about")goes to<base>/about, andhref("/about")is a link's address (<a href>, or React Aria'sRouterProvider useHref). Server rendering sees the same, so links in the first paint are right. - A plain HTTP route's
Requesthas the address the caller used: under the path, andhttps:and the host a proxy in front says (x-forwarded-proto,x-forwarded-host).