|
1 | | -# RustCFML project website |
| 1 | +# rustcfml.org |
2 | 2 |
|
3 | | -A small brochure site for [RustCFML](https://github.com/RustCFML/RustCFML), written in |
4 | | -CFML and served by RustCFML itself. No build step, no framework, no generator: templates, |
5 | | -one stylesheet, one script, and a rewrite file. |
| 3 | +The website for [RustCFML](https://github.com/RustCFML/RustCFML), written in CFML and |
| 4 | +served by RustCFML itself — on a Cloudflare Worker, compiled to WebAssembly. |
6 | 5 |
|
7 | 6 | ## Run it |
8 | 7 |
|
9 | 8 | ```bash |
10 | | -rustcfml --serve . --port 8517 # development, picks up edits as you save |
11 | | -rustcfml --serve . --production # warm caches, restart to reload |
| 9 | +rustcfml --serve . --port 8517 |
12 | 10 | ``` |
13 | 11 |
|
14 | | -Then open <http://localhost:8517>. Add `?reload` to any URL to rebuild the application |
15 | | -scope without restarting the server. |
| 12 | +Then <http://localhost:8517>. Edits are picked up as you save; add `?reload` to any URL |
| 13 | +after changing `Application.cfc`, which is cached in the application scope. |
16 | 14 |
|
17 | | -```bash |
18 | | -./tools/lint.py # hash/interpolation lint, must say "clean" |
19 | | -./tools/check.sh 8517 # fetches every page and asserts it rendered |
20 | | -./tools/layout.sh 8517 # headless Chrome: overflow, reveals, menu, theme |
21 | | -./tools/import-examples.py # re-port /try's examples from the engine repo |
22 | | -./tools/set-version.py # bump to GitHub's latest release |
23 | | -./tools/set-version.py --check # exit 1 if the site is a release behind |
24 | | -``` |
25 | | - |
26 | | -All three should pass before committing. `lint.py` catches the mistakes specific to this |
27 | | -engine; the other two need a server running on the port you pass them. `layout.sh` needs |
28 | | -Chrome and needs to sit in the web root for the duration, so it copies itself in and out |
29 | | -again. It is the only reason `zz-render-probe.html` ever appears. |
30 | | - |
31 | | -They have earned their place. Between them they found: a code sample interpolating its own |
32 | | -variable, an `href="#anchor"` that swallowed the rest of its page, inline SVG icons with no |
33 | | -intrinsic size filling a phone screen with orange, `1fr` grid tracks letting one long |
34 | | -`curl` URL make the whole page horizontally scrollable, and a reveal animation that left |
35 | | -content invisible with JavaScript switched off. |
36 | | - |
37 | | -`layout.sh` also reports `CLIPPED`: a panel narrower than the content inside it. Page-level |
38 | | -overflow is only the loudest symptom of a fixed minimum in a flexible track. The quiet |
39 | | -version hides the end of a line and leaves the document measuring clean. That check found |
40 | | -seventeen page/width combinations at once, including a `--serve` command showing 213 of the |
41 | | -687 pixels it needed. Anything that is *meant* to scroll is listed in `MAY_CLIP` at the top |
42 | | -of `tools/layout.html`; everything else that clips is a bug. |
43 | | - |
44 | | -## Deploying to Cloudflare Workers |
45 | | - |
46 | | -The site runs at <https://rustcfml.org> as a Worker: the engine compiled to WebAssembly, |
47 | | -with the templates embedded in the binary and the stylesheet, scripts and images served |
48 | | -as static assets. The wiring follows the reference host, |
49 | | -[RustCFML-Cloudflare-worker](https://github.com/RustCFML/RustCFML-Cloudflare-worker). |
| 15 | +To run the deployed shape instead — the real Worker, in Cloudflare's runtime: |
50 | 16 |
|
51 | 17 | ```bash |
52 | | -npm install # postgres/mysql2 for the JSPI snippet, esbuild |
53 | | -rustup target add wasm32-unknown-unknown |
54 | | -wrangler deploy # runs scripts/stage-assets.sh, then worker-build |
| 18 | +npm install |
| 19 | +npx wrangler dev --port 8788 --local |
55 | 20 | ``` |
56 | 21 |
|
57 | | -`wrangler dev` previews the same build locally. Neither needs a `rustcfml` binary: the |
58 | | -engine arrives as the `cfml-worker` crate, pinned by tag in `Cargo.toml`. |
| 22 | +That one recompiles the engine on every change, so it is for checking deployment |
| 23 | +behaviour (clean URLs, static assets, redirects), not for writing content. |
59 | 24 |
|
60 | | -**Two halves, split deliberately.** `cfml_worker::handle_fetch` runs every file it |
61 | | -resolves through the CFML VM, so a stylesheet embedded alongside the templates would be |
62 | | -*executed* rather than served, and `#` in the CSS would be read as interpolation. So |
63 | | -`build.rs` embeds only `.cfm` and `.cfc`, and `scripts/stage-assets.sh` copies `assets/`, |
64 | | -`robots.txt` and `sitemap.xml` into a generated `public/` that Workers Static Assets |
65 | | -serves. A request matching a static file never invokes the Worker; everything else falls |
66 | | -through to it. |
| 25 | +## Contributing |
67 | 26 |
|
68 | | -**`urlrewrite.xml` is not read by the Worker host.** Its rules are ported into `resolve` |
69 | | -in `src/lib.rs`: `/features` and `/features/` map to `features.cfm`, `Application.cfc` |
70 | | -and the `includes/`, `lib/`, `logs/` and `tools/` directories are refused, and anything |
71 | | -unresolvable renders `404.cfm` (which sets its own 404 status). Keep the two in step — |
72 | | -the native server reads the XML, the Worker reads the Rust. |
| 27 | +Pull requests are welcome. Fork, branch, open a PR against `main`. |
73 | 28 |
|
74 | | -**For a local deploy,** put the engine's browser build in `assets/wasm/` first. It is |
75 | | -16 MB, it is gitignored, and `/try` is a dead page without it: |
| 29 | +Worth knowing before you write CFML here, because the engine is stricter than a JVM |
| 30 | +one and both rules bite silently: |
76 | 31 |
|
77 | | -```bash |
78 | | -# in the engine repo |
79 | | -wasm-pack build crates/wasm --target web |
80 | | -cp -R crates/wasm/pkg/* /path/to/rustcfml-site/assets/wasm/ |
81 | | -``` |
82 | | - |
83 | | -A single asset may be up to 25 MiB, so it is served as a static file like anything else. |
84 | | -CI builds this for you, so copying by hand is only for deploying from a laptop. |
85 | | - |
86 | | -### Following the engine |
| 32 | +- **Nothing interpolates outside `<cfoutput>`.** `#variable#` in plain markup renders as |
| 33 | + literal text. |
| 34 | +- **Inside `<cfoutput>`, `#` always starts something.** `href="#pricing"` is read as a |
| 35 | + variable. Literal hashes must be doubled: `##pricing`. |
87 | 36 |
|
88 | | -One number drives the whole build: the `cfml-worker` tag in `Cargo.toml`. The Worker |
89 | | -compiles against it, `/try`'s browser engine is built from it, and `Application.cfc` |
90 | | -quotes it in the footer and every download link. They are kept equal deliberately — |
91 | | -they drifted 30 releases apart once, and the site told visitors it was a v0.653.14 |
92 | | -application while the Worker serving that page ran v0.685.5. |
| 37 | +House style is to interpolate only as `#( … )#`, and to keep code samples containing |
| 38 | +hashes outside output blocks. |
93 | 39 |
|
94 | | -| Workflow | When | What it does | |
95 | | -|---|---|---| |
96 | | -| `.github/workflows/update-rustcfml.yml` | hourly, or by hand | Bumps `Cargo.toml` and `Application.cfc` to the engine's current **stable** release, proves wasm32 still compiles, commits to `main`, then calls the deploy | |
97 | | -| `.github/workflows/deploy.yml` | push to `main`, by hand, or called | Builds `/try`'s engine from the pinned tag, builds the Worker, deploys | |
| 40 | +Pages are flat: each one sets `request.section`, `request.title` and `request.desc`, |
| 41 | +includes `_head` and `_header`, writes its markup, and includes `_footer`. Content that |
| 42 | +is quoted in more than one place — nav, stats, FAQ, docs index — lives in |
| 43 | +`Application.cfc`. |
98 | 44 |
|
99 | | -"Stable" means the release a human promoted, not the newest tag: RustCFML publishes |
100 | | -every version tag as a prerelease, so following tags would bump this site a dozen times |
101 | | -in two days. Neither workflow needs any access to the engine repository. |
| 45 | +## Deploying |
102 | 46 |
|
103 | | -Two repository secrets are required: |
| 47 | +Pushing to `main` deploys. An hourly job also follows the engine's stable releases and |
| 48 | +rebuilds when one lands. |
104 | 49 |
|
105 | | -| Secret | For | |
106 | | -|---|---| |
107 | | -| `CLOUDFLARE_API_TOKEN` | Deploying. Needs *Edit Cloudflare Workers* on the account | |
108 | | -| `CLOUDFLARE_ACCOUNT_ID` | The account to deploy into | |
| 50 | +One number drives the build: the `cfml-worker` tag in `Cargo.toml`. The Worker compiles |
| 51 | +against it, `/try`'s browser engine is built from it, and `Application.cfc` quotes it, so |
| 52 | +the three cannot drift apart. |
109 | 53 |
|
110 | | -`GH_PAT` is optional, and only matters if the org stops `GITHUB_TOKEN` pushing to `main`. |
111 | | - |
112 | | -## Layout |
113 | | - |
114 | | -``` |
115 | | -Application.cfc project facts, nav, docs index, platforms, FAQ; one file to bump |
116 | | - (`this.version` drives every download link and asset name; |
117 | | - set it with tools/set-version.py, never by hand at release time) |
118 | | -urlrewrite.xml /features -> features.cfm, plus the rule that hides the plumbing |
119 | | -index.cfm home |
120 | | -features.cfm capability tour, with code |
121 | | -performance.cfm benchmarks and methodology |
122 | | -docs.cfm an index of the repo's docs/ directory, plus recipes |
123 | | -download.cfm per-platform binaries, WASM demo, build from source |
124 | | -community.cfm how contributing works, and the projects this one stands on |
125 | | -about.cfm background, aims, architecture, FAQ |
126 | | -404.cfm rendered for a missing template, with a 404 status |
127 | | -includes/_head.cfm <head>, theme bootstrap, meta |
128 | | -includes/_header.cfm sticky nav, active state from request.section |
129 | | -includes/_footer.cfm four-column footer, trademark note, closes the document |
130 | | -lib/Icons.cfc the inline SVG icon set, so an icon is one call, not a paste |
131 | | -assets/site.css the whole design system, both themes |
132 | | -assets/site.js theme switch, mobile nav, copy buttons, reveals, star count |
133 | | -assets/crab.png the mark: the project crab, trimmed and squared to 96px |
134 | | -assets/crab-mark.svg the earlier line-drawn mark, kept but no longer referenced |
135 | | -tools/ lint and smoke test (blocked from HTTP by urlrewrite.xml) |
136 | | -assets/wasm/ the engine's WASM build, gitignored; 16 MB, copied from the |
137 | | - engine repo's crates/wasm/pkg, powers /try |
138 | | -assets/try-examples.js generated by tools/import-examples.py; the demo's own |
139 | | - examples, ported verbatim rather than rewritten |
140 | | -
|
141 | | -Cargo.toml the Cloudflare Worker crate; pins the cfml-worker engine version |
142 | | -build.rs embeds every .cfm/.cfc into the Worker at build time |
143 | | -src/lib.rs Worker entry: urlrewrite.xml's rules, in Rust |
144 | | -wrangler.toml bindings, custom domain, static assets, build command |
145 | | -scripts/stage-assets.sh stages assets/ into public/ for Workers Static Assets |
146 | | -scripts/esbuild-wrapper.sh, jspi-patch.mjs, package.json |
147 | | - carried over verbatim from the reference worker host |
148 | | -``` |
149 | | - |
150 | | -Pages set `request.section`, `request.title` and `request.desc` at the top, include |
151 | | -`_head` and `_header`, write their markup, and include `_footer`. Nothing else is shared. |
152 | | - |
153 | | -## Notes on writing for RustCFML |
154 | | - |
155 | | -Two rules cost more time than everything else combined, because the engine does not behave |
156 | | -like a JVM CFML server here: |
157 | | - |
158 | | -1. **There is no implicit output.** `#variable#` in markup is *literal text*; it renders |
159 | | - as source rather than interpolating. Every interpolation must be inside |
160 | | - `<cfoutput>`/`</cfoutput>`. |
161 | | -2. **Inside `<cfoutput>`, `#` always starts something.** `href="#pricing"` interpolates a |
162 | | - variable called `pricing…`, and a code sample containing `#name#` quietly evaluates it. |
163 | | - Literal hashes need `##`. |
164 | | - |
165 | | -The house style this repo follows to stay safe: interpolate only with `#( … )#`, because the |
166 | | -opening `#(` must always be closed by `)#`, otherwise everything up to the next `)#` in the |
167 | | -file is swallowed and the parse error points somewhere else entirely. Code samples that |
168 | | -contain hashes live outside output blocks; if one has to sit inside, its hashes are doubled. |
169 | | -`tools/lint.py` enforces both rules, including the odd-hash-count case in tag attributes. |
170 | | - |
171 | | -Two more things worth knowing, both measured rather than remembered: |
172 | | - |
173 | | -* `onMissingTemplate` is not called for URLs that resolve to no file; the server answers |
174 | | - those with its own 404 page. `404.cfm` is reached through `urlrewrite.xml`, and sets its |
175 | | - own status so it is correct either way. |
176 | | -* `<cfinclude>` inside `<cfoutput>` works and does not duplicate output, so includes can |
177 | | - wrap themselves in their own output block. That is why every include here starts with one. |
178 | | - |
179 | | -## Refreshing the project facts |
180 | | - |
181 | | -The numbers, links and release version live at the top of `Application.cfc`. To refresh |
182 | | -them from the repository: |
183 | | - |
184 | | -```bash |
185 | | -curl -s https://api.github.com/repos/RustCFML/RustCFML/releases/latest | jq -r .tag_name |
186 | | -curl -s https://raw.githubusercontent.com/RustCFML/RustCFML/main/README.md | less |
187 | | -``` |
| 54 | +Deploying needs two repository secrets, `CLOUDFLARE_API_TOKEN` (from the *Edit Cloudflare |
| 55 | +Workers* template) and `CLOUDFLARE_ACCOUNT_ID`. |
188 | 56 |
|
189 | | -Bump `this.version`, and every download URL, changelog link and footer badge follows. |
190 | | -Benchmark figures come from the project README. If they change there, change them here, |
191 | | -and keep the methodology note that says where they came from. |
192 | | - |
193 | | -## Design |
194 | | - |
195 | | -Dark by default with a light theme on request, remembered in `localStorage` and applied |
196 | | -before first paint by a three-line inline script so the page cannot flash. One accent |
197 | | -colour, a rust-to-ember gradient, on a slate ground; Inter for prose, JetBrains Mono for |
198 | | -everything that is code. Everything is legible and navigable without JavaScript, so the |
199 | | -script only adds the theme button, the mobile menu, copy buttons and scroll reveals. |
200 | | - |
201 | | -Three CSS habits keep it from breaking, all of them enforced by `layout.sh`: |
202 | | - |
203 | | -* every grid track is `minmax(0, 1fr)`, never `1fr`, because an `auto` minimum lets one unwrapped |
204 | | - URL widen the whole page past the width of the screen, and where the page cannot widen it |
205 | | - starves the track next door instead. The hero was the one grid left on a bare `fr`: the |
206 | | - install bar's `curl` URL is a single 869px token, so the text column claimed all 869 and |
207 | | - the code window rendered at 159px. It read as a cramped sample; it was a starved track |
208 | | -* every inline SVG gets a size in CSS, because an SVG with only a `viewBox` has no |
209 | | - intrinsic size and will take the width of whatever it is dropped into |
210 | | -* the nav collapses to a hamburger at 1060px, where six links and a button stop fitting, |
211 | | - rather than at a rounder number that looks tidier in the stylesheet |
212 | | -* a fixed minimum width is opt-in, never global. `table` carries no `min-width`; the wide |
213 | | - benchmark tables ask for one with `.table-wrap.wide`, so a three-column table in a |
214 | | - half-width track keeps its last column instead of scrolling it out of sight |
215 | | -* code samples wrap (`pre-wrap` plus `break-word`) rather than scroll. A horizontal |
216 | | - scrollbar inside a panel is invisible until you look for it, so a clipped line reads as |
217 | | - broken rather than scrollable; wrapping cannot hide anything, and the copy buttons are |
218 | | - unaffected because `innerText` reports required line breaks, never soft ones |
219 | | -* a grid of terminals reflows on the width a shell line needs (`420px`), not the width that |
220 | | - suits cards. Three-up inside a padded CTA left each one about 34 characters wide |
| 57 | +Two things that are less obvious than they look, if you touch the Worker: |
221 | 58 |
|
| 59 | +- Only `.cfm` and `.cfc` are embedded in it. Everything under `assets/` is served by |
| 60 | + Workers Static Assets, because the engine runs *every* file it resolves through the VM — |
| 61 | + an embedded stylesheet would be executed rather than served. |
| 62 | +- `urlrewrite.xml` is read by the native server only. The Worker has its own copy of those |
| 63 | + rules in `src/lib.rs`. Change one, change the other. |
222 | 64 |
|
223 | 65 | ## Licence |
224 | 66 |
|
225 | | -Same as the project it documents: MIT. The wording about ColdFusion being Adobe's |
226 | | -trademark, and RustCFML not being affiliated with it, is kept in the footer of every page |
227 | | -and again on the About page. |
| 67 | +MIT. ColdFusion is a registered trademark of Adobe Inc.; RustCFML is an independent |
| 68 | +project and is not affiliated with or endorsed by Adobe. |
0 commit comments