From 4f71e19db7536933ad7f968252329eb694fe7b5a Mon Sep 17 00:00:00 2001 From: liquidev Date: Mon, 9 Sep 2024 20:39:29 +0200 Subject: [PATCH] post: buildsome --- content/about-treehouse.tree | 2 + content/programming.tree | 4 + content/programming/blog/buildsome.tree | 692 ++++++++++++++++++ content/programming/languages/javascript.tree | 4 +- content/treehouse/new.tree | 13 + .../rustc-ice-2024-07-20T21_00_23-69819.txt | 70 ++ treehouse.toml | 1 + 7 files changed, 784 insertions(+), 2 deletions(-) create mode 100644 content/programming/blog/buildsome.tree create mode 100644 static/text/rustc-ice-2024-07-20T21_00_23-69819.txt diff --git a/content/about-treehouse.tree b/content/about-treehouse.tree index 51118af..e789089 100644 --- a/content/about-treehouse.tree +++ b/content/about-treehouse.tree @@ -1,3 +1,5 @@ +%% title = "about the treehouse" + % id = "01H89RFHCQCD3E1XS5XAPW86J5" - liquidex's treehouse can be thought of as many things. diff --git a/content/programming.tree b/content/programming.tree index 82e17fc..5230717 100644 --- a/content/programming.tree +++ b/content/programming.tree @@ -11,6 +11,10 @@ id = "01J4J4PAXRWZDP9PAZNGCQ9S3D" + [featured]{.badge .blue} :page: haku - writing a little programming language for fun + % content.link = "programming/blog/buildsome" + id = "01J7BYKQHZKYQ969T3PH3V8HF1" + + :page: not quite buildless + % content.link = "programming/blog/lvalues" id = "01HY5R1ZW0M0Y5KQ1E8F0Q73ZT" + :page: places, or what is up with `*x` not always meaning the same thing diff --git a/content/programming/blog/buildsome.tree b/content/programming/blog/buildsome.tree new file mode 100644 index 0000000..89fa656 --- /dev/null +++ b/content/programming/blog/buildsome.tree @@ -0,0 +1,692 @@ +%% title = "not quite buildless" + +% id = "01J7BYKQGYPF50050K67N2MP1G" +- ...buildsome? + +% id = "01J7BYKQGYBH7DGS459RAHMTN5" +- I've seen a few articles about going _buildless_ with programming Web applications. + + % id = "01J7BYKQGYCB5WWQ8MZE1FPZE4" + - there's of course the classic [Vanilla JS](http://vanilla-js.com/) + + % id = "01J7BYKQGYT8YW993V21WP9CVY" + - a couple weeks ago I saw someone's idea for [_Vanilla Prime_](https://github.com/morris/vanilla-prime), which is an opinion on how to make Vanilla JS somewhat optimal and more pleasant + + % id = "01J7BYKQGY9WAXZYSQAQWGGXSE" + - and yesterday I saw [_Going Buildless_](https://mxb.dev/blog/buildless/), a blog post which evaluates how far you can go without _any_ builds. + +% id = "01J7BYKQGYTM78Y1170ERMPE5F" +- as a big fan of simplicity, I really like that there's a corner of the Web which values simplicity over 100% convenience. + + % id = "01J7BYKQGYFZ5RTRYXKMR0V815" + - a lot of focus in modern day Web development seems to be on delivering apps ASAP but implemented inefficiently, and then trying to iron over that with complex tooling such as minifiers. + + % id = "01J7BYKQGYK1YKB0XTWAN4F9FA" + - I think there's a genuine use case for that---if so many people are using it, that means there's a demand for it. + but I honestly don't like the complexity. + I deal with [enough of it](https://www.unrealengine.com/) at work, and I wouldn't wanna have to understand a complex build toolchain for my little homegrown website. + + % id = "01J7BYKQGYW5DS0PP13QA2WXDV" + - I mean, if the browser does it for you... then it's probably smart to [conserve that energy](https://anilist.co/anime/12189/Hyouka/), and do something more interesting! + + % id = "01J7BYKQGY7BA7M2WM92H4Q7W6" + - be it your energy, or _the literal power that flows through your wall to fuel your computer running that complicated website build._ + +% id = "01J7BYKQGYK2RC8YEFS3GEGBAT" +- so I built my own static website generator! + + % id = "01J7BYKQGYVW9NGN7HMWNNHF9Q" + + but I promise this blog post is not _just_ about that---I've written about it [before][branch:01H89RFHCQAXJ0ST31TP1A104V], and I don't really see value in me writing _yet another_ blog post in the vein of "here's an overview of my statically generated blog's tech stack okay bye," + + % id = "01J7BYKQGYW5CJPBVH5N00352Q" + + which isn't to say _you_ shouldn't write a blog post about your tech stack! + I think there's a lot of value in writing about how you made your blog. + it's another thing to _write_ about, and if you want to learn writing... you should write a lot! + + % id = "01J7C1ASXR72HYP2P3R5YGP230" + - write write write a write a write. + +% id = "01J7BYKQGYC6M88FFBKM7NSK7W" +- so here are some stories about my handiwork: a website built [with my very own two paws](https://www.youtube.com/watch?v=KVqwvU49JLg){.secret}. +enjoy! + +% id = "01J7BYKQGYPSWQ9KB4V6361PD1" +- ### [I Ate My Themplate Engine](https://www.youtube.com/watch?v=lZktMGvW-rk){.secret}, and why not quite buildless + + % id = "01J7BYKQGY7QEAN677Q9AT4T0V" + - I'll put _probably_ the most interesting bit right at the start. + the treehouse is _not quite_ buildless. + + % id = "01J7BYKQGYRV6TKEACZ0DXNDG4" + - one might even call it... buildsome? + I mean, it's not quite buildful, and definitely not buildless...? + + % id = "01J7BYKQGYE3AVESR1MBSA11JT" + - initially the treehouse started off as a completely statically generated website---to reduce the work needed to be done by the Web server, I decided to make it do as little as it possibly had to. + so static generation it ended up being. + + % id = "01J7BYKQGY7TPY5D31GDVY86ZJ" + - static generation is super cool, because all you have to end up writing is a single function that deletes the existing output directory, creates a new one, and fills it in with a bunch of files. + + % id = "01J7BYKQGYCE9YBTGAR88CPZ1Y" + - in case of the treehouse, the sources for the statically generated files are [`.tree` files][branch:01H8V55APDWN8TV31K4SXBTTWB], a bunch of [Handlebars][] templates, and a bunch of assorted static files, including JavaScript. + + [Handlebars]: https://handlebarsjs.com/ + + % id = "01J7BYKQGYB2BHKE1CZGDXEA52" + - but honestly... I don't really like that choice of templating engine, or rather templating engine implementation! + the Rust implementation in particular has been kind of a pain in the butt, because it tries _very_ hard to be multithreading friendly. + that of course means some annoying `Send + Sync` bounds... + + % id = "01J7BYKQGYA7FFV3ZT0RTC8R6Y" + - Handlebars allows you to write _helpers_, which are custom functions for processing text. + I use them a bit in the treehouse---for example, I have `include_static`, which pastes in a file from my `static` directory into the template. + + % id = "01J7BYKQGY303Y8NA3VMWW5D5D" + - however, if a helper has to reference some outside data, or wants to cache its results because it's expensive to run... + you're pretty much out of luck, because the `Handlebars` registry does not encode any lifetime bounds on the helpers. + + % id = "01J7BYKQGYT5JR68BZQ1FQX9HY" + - in a single-threaded setting this would mean you'd need to wrap your shared state in an `Rc`... + but in a single-threaded setting, only one thread can ever access the Handlebars instance, so you may as well let the helper mutate itself, or reference outside data! (as in `&`, not `Rc`)! + + % id = "01J7BYKQGY4VRPQ0YB2SPQ3468" + - my personal opinion is that it would be neater if there was an initial setup step, which _freezes_ some parts of the registry---these parts become immutable after you set them up, and then you can send them out to threads---for example, behind an `Arc`. + afterwards, you construct the _render-time_ part, which requires mutable access---and therefore every thread that needs to render templates gets its own instance of that. + + % id = "01J7BYKQGYKFM99J8HSF9GYP3H" + - using this architecture, you can forgo any locking at all, which is really cool for ergonomics, and great for performance! + + % id = "01J7BYKQGYZPFFQYFTKE229141" + - I've looked at some other popular templating engines for Rust, and none of them really seem to solve this problem very neatly... + + % id = "01J7BYKQGYBZRG3BSX9RP22YE2" + - so much so that I ended up writing my own little templating engine for the `.tree` format, which is stupid, but yeah. + I need to access my generator's shared state somehow (why? I don't remember :oh:), and I'm not gonna `Clone` _all that_, or put that beind a `Mutex`. + + % id = "01J7BYKQGYZBM45J0HE07FCW9R" + - I'd love to write a more proper templating engine that implements that dream architecture of mine, but it's not a priority. + Handlebars works well enough where it does, and I don't really feel like spending my precious engineering hours on something that doesn't have much of a benefit. + + % id = "01J7BYKQGYNNWVA8S560HANZVP" + - other than being really heckin' fun of course. + which is why I may do it someday anyways. + + % id = "01J7BYKQGYGZ0S2AMHSKRBZKB1" + - at some point I wanted to add [OpenGraph](https://ogp.me/) metadata, so that branches you link to via permalinks [such as this one][branch:01J7BYKQGYGZ0S2AMHSKRBZKB1] would get nice embeds in Discord and other chat apps, including the branch's text---which prompted me to drop the [`try_files`](https://nginx.org/en/docs/http/ngx_http_core_module.html#try_files) and [`proxy_pass`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to an axum server instead. + but it was fun and simple while it lasted! + +% id = "01J7BYKQGYJF77C68FPHTSCR0W" +- ### not incremental by design + + % id = "01J7BYKQGYB6E2HFB38ZW92X0W" + - did you know: you don't need incremental builds, if your non-incremental builds are really fast! + + % id = "01J7BYKQGY1MCKWKCJKVDHMJ13" + - on my Linux box at home, the treehouse manages to build itself in around 250ms. + on my work PC running Windows, it takes about 800ms, which is still very acceptable. + + % id = "01J7BYKQGYDJ839P5MG0HNVG93" + - ...and that's a debug build!! + + % id = "01J7BYKQGYVV2WRHT1J2HEJG0H" + - the bottleneck on Windows is probably due to IOps being much slower there. + I believe it's deleting and copying around files that takes so long, because there's a barely noticable but present delay on that task in the log output. + + % id = "01J7BYKQGYBADRFGGY65G83BB7" + - this is the idea I built the treehouse generator in mind with---computers are pretty heckin' fast. + for the 56 unique `.tree`-derived pages the treehouse generates, most of the time is not actually spent parsing and generating text files, but rather... + reading metadata for image files, so that I can generate proper `width="" height=""` attributes on `` elements. + + % id = "01J7BYKQGY5QGYYR4NJYWW1CQ9" + - I don't have any profiling infrastructure set up just yet, but I suspect it's something with the [`image` crate](https://lib.rs/crates/image) doing more work than it needs to in order to obtain size metadata. + + % id = "01J7BYKQGYSZ6FKG9E5GAFB13H" + - shame on me, but I don't have image size probing implemented for SVG just yet. + which I use for some emoji, such as :smile: :sparkles: + + % id = "01J7BYKQGY0PR13G71PK163976" + - if I _wanted_ to implement incremental builds, I feel like the dependency tracking would get pretty hellish pretty quickly. + + % id = "01J7BYKQGY75XN1HBVZ110D16P" + - say a `.tree` file uses the `include_static` template directive to include some file into itself. + now in addition to compiling the `.tree` file when it changes, I'd also need to recompile the `.tree` file when that `include`d`_static` file changes too---and that sort of dependency tracking is ripe for bugs as the codebase grows more complex! + + % id = "01J7BYKQGYSYJD97AQ3ZJB7B8J" + - not with a well-built abstraction of course, but do I really want to invest my time into that abstraction, if my debug builds take a quarter of a second? + + % id = "01J7BYKQGY75NF07X03VSK3XX1" + - I'll probably keep using this non-incremental build system, at least until my build times get unbearably long---I'd say 2 or 3 seconds would be testing my patience already. + I really don't like using slow software. + +% id = "01J7BYKQGYMEQXWFVXETNZGJRB" +- ### Vanilla JS + + % id = "01J7BYKQGYESGP0ZKSYVNTEKCF" + - TypeScript is cool, but it's yet another build step. + and with `tsc` it's a pretty slow one at that! + + % id = "01J7BYKQGYWNPK11XFN30DFZ74" + - _and_ it's a dependency---one you have to install outside of `rustc` and `cargo`, which isn't great... + + % id = "01J7BYKQGYAGD2V31HP66SHZ91" + - _and_ if you want to use something else like [`swc`](https://swc.rs/), you're out of luck unless you use npm. yuck. + + % id = "01J7BYKQGY7EBD6MF2T3A07GCF" + - I'm sure there'll be a lot of people thinking "it's not _that_ slow," but consider me a fanatic who _despises_ long build times. + adding an additional 100ms of Node.js warmup to that would be like comitting a cardinal sin! + + % id = "01J7BYKQGY8MCJA4DZ3904Q6ZT" + + so I decided to go with Vanilla JS for the treehouse. + + % id = "01J7BYKQGYZNWKA398QHHNC0M6" + - in fact, not just for the treehouse. + [the app I'm building currently][def:rkgk/repo] is also completely Vanilla JS, for the exact same reasons. + + % id = "01J7BYKQGYZ0WC3YS5NMKCT3V7" + - it actually hasn't been that bad! + aside from type errors being a little harder to debug, and IDE support not being as great---I honestly do miss the latter...---you can program in Vanilla JS just fine. + + % id = "01J7BYKQGY8X9SWKYFJ62GSBBG" + + so I don't really get hystericising over not having TypeScript. + I've had worse experiences writing code. + the best part about plain vanilla JavaScript is that the iteration times are really quick, and you have stack traces, and a pretty darn good graphical debugger to help you out in trying times. + + % id = "01J7BYKQGYY3B66TJDHYTQ1V2R" + - not a _replay_ debugger mind you... but it's still a pretty good debugger! + way snappier than most tools for debugging C++ on Windows. (I gotta try out RAD Debugger one day >--<) + + % id = "01J7BYKQGYZB7DM185YM80633H" + - my experience writing rakugaki only confirms that TypeScript isn't really needed---it's a pretty sizable Web app, and aside from me wishing JavaScript had more strict error handling---which TypeScript _does not_ solve by the way, it's _really not that bad_. + + % id = "01J7BYKQGYRT9125BTEJPEWBWB" + - [I've written about my feelings towards JavaScript before though][page:programming/languages/javascript], so I won't repeat myself too much here. + +% id = "01J7BYKQGYSD6SEA5CEF64YB28" +- ### not minified + + % id = "01J7BYKQGYZTR6JE316HXB6F4J" + - did you know: your browser has a handy DevTools panel. ain't that neat? + + % id = "01J7BYKQGY3HHC5FFCTJ4XHJHB" + - too bad it's useless if you minify your code! + + % id = "01J7BYKQGYD8VFKHGR8FP05MPS" + - remember: a lot of people can learn from reading someone else's source code, including you! + so try not minifying your source code. + + % id = "01J7BYKQGYA94T9AR4FDSP3Y6Y" + - let compression do most of the heavy lifting, and liberate yourself with an improved debugging experience, as well as letting people see how the cool parts of your website are implemented. + + % id = "01J7BYKQGYG1993HRJMDDBJ0W8" + - even if they're really janky! + +% id = "01J7BYKQGYTBEQFJ1AK0SRP0ZG" +- ### release mode? what's that? + + % id = "01J7BYKQGY1EFG6HSNJEJXKCWB" + - tying into the previous point, the treehouse builds mostly the same code, both in debug mode and release mode. + + % id = "01J7BYKQGYAVY2JGSYR90M0FQZ" + - the reason is that I don't want to waste time testing my website separately on release mode, so having it behave differently in subtle ways means more bugs! + + % id = "01J7BYKQGY6KJ6ZZX4EREEAB79" + - there are only two parts of the treehouse that behaves differently between debug and release mode. + + % id = "01J7BYKQGYB4G0J7TZQCF4X7W6" + - generation speed, and... + +% id = "01J7BYKQGYFM6668GH8W7RH1A1" +- ### live reloading + + % id = "01J7BYKQGYS3KAXCAXW066M32K" + - in debug mode, I have the treehouse reload itself on change automatically. + + % id = "01J7BYKQGYMVS2PYT5YNAEG8FR" + - I can't count the number of precious developer seconds this has saved me---not having to refocus my window to the browser is really nice! + + % id = "01J7BYKQGY6AX2C8CR0BSDFR2E" + - I actually learned the Web ecosystem has such cool mechanisms for app development back when I was trying out React.js for a little project of mine. + I didn't end up building anything in the end because the project idea _just didn't end up vibing with me_, but it provided me with lots of useful knowledge. + especially related to how frickin' cool React is in terms of developer experience. + + % id = "01J7BYKQGY16FEAPZ11DD6JYTM" + - here's the script I use for [rkgk][def:rkgk/repo], hosted under `static/live-reload.js`: + + ```javascript + // NOTE: The server never fulfills this request, it stalls forever. + // Once the connection is closed, we try to connect with the server until we establish a successful + // connection. Then we reload the page. + await fetch("/auto-reload/stall").catch(async () => { + while (true) { + try { + let response = await fetch("/auto-reload/back-up"); + if (response.status == 200) { + window.location.reload(); + break; + } + } catch (e) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + }); + ``` + + % id = "01J7BYKQGYF8Y1E013XTNT5Q32" + - I then import it into any HTML page which I want to reload on change. + + ```html + + ``` + + % id = "01J7BYKQGYMWFP0YQK92MB1133" + - on the Rust side, these `/stall` and `/back-up` endpoints are implemented like so: + + ```rust + use std::time::Duration; + + use axum::{routing::get, Router}; + use tokio::time::sleep; + + pub fn router() -> Router { + let router = Router::new().route("/back-up", get(back_up)); + + #[cfg(debug_assertions)] + let router = router.route("/stall", get(stall)); + + router.with_state(()) + } + + #[cfg(debug_assertions)] + async fn stall() -> String { + loop { + // Sleep for a day, I guess. Just to uphold the connection forever without really using any + // significant resources. + sleep(Duration::from_secs(60 * 60 * 24)).await; + } + } + + async fn back_up() -> String { + "".into() + } + ``` + + % id = "01J7BYKQGYDY8E84BEM5V7MMGB" + - the `/stall` route is only enabled in debug builds, because in release mode, rkgk uses a smarter exponential backoff system with some random noise added to the reload timeout, to prevent the server from DDoSing itself if it dies. + + % id = "01J7C16RSZMAJ5TKZ1H2MMTCXK" + - or right after deploying, which also causes all existing WebSocket connections to close. + + % id = "01J7BYKQGYKYMZA4HZEXV117G8" + - in the treehouse, I use [`tower-livereload`](https://lib.rs/crates/tower-livereload), but I wouldn't recommend it. + + % id = "01J7BYKQGYC579CYCJH0WJN8MT" + - as you can see above, there's not a lot of client-side JavaScript, and it's not hard to write the HTTP routes either! + + % id = "01J7BYKQGYVS4CZGVC4ZHGJ7KF" + - also, `tower-livereload` also has a couple disadvantages compared to the solution I've shown here. + + % id = "01J7BYKQGY3P95RM10ZZGRP2Y3" + - it's slow, because it only polls the server's `/back-up` endpoint every second, which increases reload latency. + I've bumped that up to polling every 100ms in my script, because I don't like slow. + + % id = "01J7BYKQGY8GVZQS6EP9D8WMNA" + - it [cats](https://en.wikipedia.org/wiki/Concatenation) the JavaScript payload directly to your server's `Content-Type: text/html` responses, which produces invalid HTML. + as far as I can tell, all major browsers seem to parse it correctly, but it's a pretty ugly hack nevertheless. + + % id = "01J7BYKQGY8AX7298QPHR208BZ" + - it's 500 lines of [dependency](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=568s) for something you can do with 12 lines of JavaScript, 3 lines of HTML, and 28 lines of Rust! + + % id = "01J7BYKQGY1VBDW7TDVV47XK2T" + - to trigger the reloads, I use [`cargo-watch`](https://lib.rs/crates/cargo-watch), mostly because it's really convenient. + + ``` + cargo watch -x run + ``` + + and you're done! + your site will now reload when you [`:w`](https://vim.rtorr.com/). + +% id = "01J7BYKQGYX9B0RYN4KWSMEJV6" +- ### Web Component shenanigans + + % id = "01J7BYKQGYNDQSZ7NW4MJBH6WQ" + - as the treehouse uses Vanilla JS, I needed some solution for building reusable components that wasn't React. + luckily for me, I already knew about [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)---in particular, [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements). + + % id = "01J7BYKQGYK91F1Z45YJGGNX8Q" + - if you're using them for your own website, I'd recommend skipping shadow DOM, because it's not really useful in case you have control over all styles. + it's good to know what it does in case you ever need it, but it shouldn't be your go-to tool for building components. + + % id = "01J7BYKQGYKBNJYZB1ASBJRGD3" + - _custom elements_ are just that---custom HTML elements that you can include in your page. + according to [the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements), these have two flavours: + + % id = "01J7BYKQGYWEXYDGRFT0P8RMH6" + - _autonomous custom elements_, which always extend `HTMLElement` in JavaScript. + these can be used to implement standalone components composed out of smaller pieces. + these are used like any HTML element---``. + + % id = "01J7BYKQGYM9HD00VY2BVEZPZ0" + - _customized built-in elements_, which extend _any_ built-in element. + these are applied using the `is=""` attribute on a base built-in element, like `
  • `. + + % id = "01J7BYKQGY90D6C0KF9CFSMF1A" + - unfortunately customized built-in elements are practically useless, because Safari doesn't implement them, [and doesn't even plan to do so](https://github.com/WICG/webcomponents/issues/509#issuecomment-222860736)... + + but that doesn't diminish the usefulness of autonomous custom elements either way! + + % id = "01J7BYKQGYND1AMZ6PG3M67BN2" + - to implement a custom element, I usually use this pattern: + + ```javascript + class YourElement extends HTMLElement { + // You can use the constructor of a custom element to require some + // parameters from other JS code. + // Note that adding *required* parameters here makes your element + // practically unusable from HTML, because there's no way to pass them in! + constructor() { + super(); + } + + connectedCallback() { + // Read attributes and add children here. + } + } + + // Choose a different prefix; `owo` is just an example to get you going. + // You *must* choose a prefix, because custom elements require at least one dash `-` in their names. + customElements.define("owo-your-element", YourElement); + ``` + + and off we go! + + % id = "01J7BYKQGY6YE329DB9PMY8KNW" + - I've found a couple useful idioms for working with custom elements. + + % id = "01J7BYKQGYDG6XYVK8ZFAM4KBJ" + - styling: custom elements start off with `display: inline;`, which is probably not what you want. + therefore, you'll usually want to replace that with `display: block;`. + + ```css + owo-custom-element { + display: block; + } + ``` + + % id = "01J7BYKQGY65233M8NCPBM0N63" + - DOM construction: a useful idiom is passing `createElement` to `appendChild`. + this allows you to append a new element to the component (or really anything, it's a useful idiom overall). + + I usually follow it up with adding a CSS class for easy styling, naming the CSS class after the object field name on the JavaScript side. + + ```javascript + this.textArea = this.appendChild(document.createElement("textarea")); + this.textArea.classList.add("text-area"); + ``` + + it's kind of verbose, but if you don't like it, you're free to wrap that up in a helper function---I personally don't mind, since it's simple code that I usually follow this up with more initialization logic. + + % id = "01J7BYKQGYT5RPTQVZ0S9G18AM" + - patching everything together: I usually use plain DOM `Event`s for event handlers that don't need to return any data back to the component. + I prefix their names with `.` to not confuse them with built-in DOM events such as `mousedown`. + + it's pretty convenient to construct events with `Object.assign`, too. + + ```javascript + this.dispatchEvent( + Object.assign( + new Event(".codeChanged"), + { newCode }, + ), + ); + ``` + + you can wrap the event construction in a function too if you mind the verbosity much, but again---I personally don't. + +% id = "01J7BYKQGYYPXJVESTMGAA0TB8" +- ### cache bust a little, cache bust some more + + % id = "01J7BYKQGYBRSHQT8HJ3KNY9NA" + - _cache busting_ is a super cool technique for ensuring the browser does not download assets that haven't changed. + essentially, for each asset you serve to the user, you compute a hash that's then included in all URLs referencing the asset from your website. + + % id = "01J7BYKQGYR6A6N3P1EG4383RN" + - for example, as of writing this, treehouse's CSS stylesheets are linked into the main page more or less like so: + + ```html + + + ``` + + % id = "01J7BYKQGY3070025QXPDCF4N6" + - my server sees the `cache` query parameter, and adds in a little HTTP header, that basically tells the client "don't redownload this, that'd be stupid. this asset isn't going to change like, ever." + + ```http + Cache-Control: public, max-age=31536000, immutable + ``` + + % id = "01J7BYKQGYYBY9RDFJG8BP61QP" + - and that's it! the actual value of the `?cache` parameter is never interpreted by anyone, anyhow. + it's only there so that whenever something _does_ change, we change the URL, and the browser thinks that "hey, that's a different asset! gotta download it." + + that way, the browser only ever downloads files that changed since your last visit. + + % id = "01J7BYKQGYZ6MS7CKVDFQDVMQ8" + - initially I implemented cache busting for most static assets, because that's pretty easy to do: add a helper to your templating engine that can derive these `?cache`-augmented URLs by computing a hash of the linked file. + + % id = "01J7BYKQGY4VC1JRT671YJ1509" + - in my case I use [BLAKE3](https://en.wikipedia.org/wiki/BLAKE3)---as indicated by the `b3-` prefix---but the choice of hash function shouldn't matter that much; I just chose a fast crypto hash for lower likelihood of collisions. + which would of course cause assets _not_ to get redownloaded, if that ever happened. + + % id = "01J7BYKQGYE3AKGWS8RTZKAJPE" + - which is kind of bad, but eh. + happens rarely enough we don't need to care about it. + + % id = "01J7C16RSZKDBC3427V4BAV7VB" + - (and yes, I know that I'm increasing the likelihood of collisions by truncating the hash. + as I said: it doesn't matter, I don't care.) + + % id = "01J7BYKQGY5ASNC96MCHF1TRB8" + - the far bigger challenge was making this work for JavaScript files. + + % id = "01J7BYKQGYHDT3AZ78WA5WYGJC" + - HTML we generate, so that's easy---add a template helper, and replace all occurrences of `/static` URLs with that helper. + + % id = "01J7BYKQGYRWTWQZX0B86QMSH6" + - CSS doesn't refer to too many assets---there's fonts and a couple images in [that one blog post's stylesheet][page:programming/blog/tairu], which will probably never change, so we can hardcode those. + + % id = "01J7BYKQGYTDQEEH58DDGCVG8M" + - but JavaScript. man. + where do I even begin. + + % id = "01J7BYKQGYSPVQK1AM2XKV39QC" + - treehouse is built on ES modules. + as I mentioned before, I don't bundle or minify anything, because HTTP/2 makes using plain ES modules quite efficient, as long as you import them all from your main HTML file. + the problem is that if you're referring to modules like this... + + ```javascript + import "treehouse/vendor/codejar.js"; + ``` + + how the heck are you going to add that `?cache` parameter in there? + + % id = "01J7BYKQGYMCBHPH645GMQCKWJ" + - as you can see in the previous example, the treehouse had already used [import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) by that point. + for those of you who don't know, these are handy little bits of JSON that tell the browser's JavaScript runtime where to source your modules from. + + % id = "01J7C16RSZS6GK1JPHFEV2MZXH" + - before implementing cache busting, I'd use a simple import map hardcoded right into my Handlebars template: + + ```html + + ``` + + % id = "01J7BYKQGYJDRF3FXHE23ADK21" + - so the challenge was to turn _that_ puny import map into something that lists all the individual modules, with a `?cache` parameter! + and initially I thought, "well this sure is gonna be simple, just walk through all my `.js` files, compute those hashful URLs," + + % id = "01J7C16RSZ7NHE5N3KV57NGWT0" + - okay cool we're getting somewhere, + + % id = "01J7C16RSZQPMABF0SBDH1RHA7" + - "and then we could even cache that import map with a `?cache` parameter too---" + + % id = "01J7BYKQGYBB81J2XGXPG4WQS5" + - and then [reality struck](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap#syntax): + + > The `src`, [...and other] attributes must not be specified. + + Fuck. + + % id = "01J7BYKQGYSB23S0G2G2M09YNK" + - so, "sure," I say. + "I'll have to include the whole import map verbatim in each `.html` file. + no big deal, we don't cache `.html`s anyways..." + + % id = "01J7BYKQGYPXKQFGYHQRG17TFF" + + it's kind of sad, because it'd allow me to cache linked branches (such as [this one][branch:about])---I'd love it if I could get rid of the `Loading...` text entirely if you've ever loaded a branch, but while that _is_ feasible, it's probably going to benefit snappiness less than I'd like, due to import maps influencing the hash of each `.html` file. + and therefore each time I add a `.js` file, all cached HTML files would get busted... + + oh well. + + % id = "01J7C16RSZ1KRZ63HXGN3G3HF3" + - on the other hand, I do understand why browser vendors wouldn't want to implement it---it's a performance pitfall. + it adds in an additional dependency towards evaluating `