Immutable Web Apps
immutablewebapps.org
immutablewebapps.org
> Static assets must be hosted at locations that are unique and independent of the web application environment(s)
Why? Meaning a different domain or is path enough? What are "static assets" compared to every other file served by a web server? I assume meaning will never ever change, so images are not "static assets"? What if I name them by what they are and commit to changing the name when I change the image?
> Static assets are the files (javascript, css, images) that are generated from a build of a web application codebase. They become immutable when they do not contain anything environment-specific
> Static assets must not contain anything that is environment-specific
So, static assets are only immutable when they don't have anything environment specific. Oh, and static assets always don't have anything environment specific. I'm confused in the wording. Clarification required. So my build generates some environment-specific CSS file and in one paragraph that's a static asset and in another it's not?
> Why?
Broadly, for the same reason that separating content, structure, and style is considered a good idea. (We can debate separately about whether HTML/CSS/JS applied this principle appropriately.)
From another angle, this is just another way of saying that static assets should not depend directly on the environment, and that only `index.html` should differ, since it's the effective manifest of your application. It's okay for different environments to go to a different URL for `index.html`.
> I assume meaning will never ever change, so images are not "static assets"?
An individual image is a static asset. If you want to use a different image, you should change the URL. This is where the "immutable" part comes in.
> So, static assets are only immutable when they don't have anything environment specific. Oh, and static assets always don't have anything environment specific.
Not every static asset is a build artifact, but every build artifact should be a static asset. This is not true in general, but it is what the whole idea of an "immutable application" is trying to achieve. The environmental bindings are pushed out to the top level, where they belong.
I agree it could have been worded better; the phrase "static asset" is a bit overloaded here.
It only has to be cached as long as there exist deployments that need to reference it. But as long as there exist references to it, the content at the referenced address should not change. So yes, you should version that file -- or that deployment as a whole, if you'd rather not get that fine-grained -- but it only has to be "never-ever-changing" up to the point that nobody's observing it anymore.
If you only officially maintain one version, you'd want to keep the one prior for the sake of smooth deployment. Just document your support policy so downstream consumers aren't surprised (and they should be able to pin to an `index.html` address, generally, whose contents _are_ allowed to change over time.)
> Or should I start embedding that frequently changing JS in the pages that need it, thereby duplicating content, just to avoid breaking the rule that all build artifacts are static assets and all static assets are immutable?
Well, the article is about SPAs, so there's only one page by fiat. But in the general case (MPAs?), there would be a question about why that specific piece of JS must change so frequently, and whether that constitutes a dependency on the environment that can therefore be factored into the top-level.
This response skipped over the most important question "And if I make a change daily (or more frequently w/ CD), have 365 of those versions (i.e. maybe versioned as date or hash)?". Simply asked, is every generated JS file a static asset? Must the (indefinite) cache headers be present for every static asset be returned? So if I deploy a new static asset every hour via continuous deployment let's say, I must give it a new unique path? These questions apply to SPAs.
I would answer yes to that. More specifically, if an old cached component could reference the new file and break, then the old component should never know about the new content.
It's a good practice for any web application. You place huge caching times for your assets, and make their URLs unique.
At the backend, you either have a compilation step that adds the unique marker (a hash is more common) to your links or you have the backend add it at runtime.
If I understand correctly, the immutability of a particular asset is what makes it a "static" asset.
This is not always true. Many times frameworks are so heavily leveraged and the site-specific code so minimal on small SPAs that it is reasonable to have a single packed-and-minimized JS file. And it still may change frequently.
Also: other than index.html, it's worth putting hashes in every filename. Though you will surely run into problems when the app is updated but users are running old versions so make sure you build with that in mind. I.E. you want to be able to reload at any time without consequence, and you probably want to do it automatically when you can't find a chunk. That last part is tricky if you map all 404s to index.html :)
As a backend developer that relies heavily on environment variables for configuration, I was surprised that runtime configuration is not something these frameworks recommend.
In the few occasions that I have to do some front end (Angular) stuff, I had to write my own service just so that I can have runtime configuration.
Why don't these frameworks support this out of the box? Are there any issues I may be missing?
Not exactly the same as shell environment variables, of course. And you may disagree about exposing the `enableDebug` flag as a runtime config there.
Still it is doable, in a sort of roundabout way, using the browser as the environment.
The application I'm currently working on has a separate configuration project, more for application options for one client deployment vs. another at a feature level, as well as any customized localization/strings. Beyond this, there is are release variables generated that are also injected. Namely the endpoints for login/logout/user-management/api. The application itself doesn't handle authentication, it validates a token/key with the api endpoint. Other configuration options are loaded from the api endpoint on application startup.
The application itself will be loaded with the strings for the application and a small spinner and load-check. If the browser doesn't have JS or meet minimum requirements (ES2017 async function support), it will load another screen acknowledging as much. It will then verify the authentication token, load other parameters from the API/Database and proceed to client-side application routing/runtime.
It isn't particularly difficult to setup. Initially I had a complimentary server-side that would deliver the environment options at runtime. That was changed in order to facilitate static deployments of the application.
Back in University, in the beginning of the 90’s, a teacher explained us that separating configuration from code was a good practice because you don’t have to recompile your code to make several tests.
I guess it still is the case !
Make index.html the starting point, that pulls in the immutable web app, and configures it, (usually with a single endpoint URL that it would typically need).
Seams reasonable to me. I'm normally pessimistic about things like this (specifically the 12 factor app which is hopelessly one-sided to specific environments IMHO). But this is actually a useful and captured quite gracefully.
It would be nice to see some examples in each of the mentioned frameworks, and hopefully example webpack configuration specifically (since it is often used across all of them).
SPA's are a bad idea because 1) it greatly complicates good unit testing and requires gross things like headless browser drivers in your integration test suite, 2) it does not degrade gracefully to HTML (such as on assisted devices) 3) javascript is, at best these days, a sunny wasteland of dependency hell.
2) SPAs don't have to be pure HTML to be compatible with assistive technology.
This is basically 12-factor [1] though. For us, this was imperative to be able to do review apps on every merge request and promote those to production without having to re-run the build. We run the exact same Docker image that ran in CI, the environment is the only thing that changes.
These seem to neatly complement the other aspects of this proposed architectural pattern.
I clicked through and read the article because I though it would be about Clojure-style immutable data structures, like ImmutableJS uses. Or maybe how to use databases like Datomic or patterns like CQRS and event sourcing.
In multi-page applications (MPAs?), among the other things that would probably have to be accounted for, you could treat all of your pages as "entry points" in the same way as `index.html`.
See https://redux.js.org/recipes/serverrendering#inject-initial-...
for ideas.
So if there's no valid auth, it goes to our `service-dashboard-asset` system and requests a URL for the login app - injecting the script tags into the page, then the user can authenticate.
Then the page refreshes, the auth is present, and it goes to `service-dashboard-asset` and requests a URL for the actual dashboard, and now injects this script tag in instead.
> - Configuration from code.
> - Release tasks from build tasks.
I've built an open source product[1] that tries to address these same issues (and others). Configuration-at-build-time has especially been a pain point for my company, leading to Jenkinfile-s full of environment variables. I've also solved it by injecting a global (window.APP_CONFIG in my case) into the served index.html. It would be nice if a standard could emerge for this (I could definitely see adding support for window.env to my product).
I really like the idea of using absolute URIs for assets. Having relative URIs does indeed complicate routing quite a bit, and in fact to avoid having to specify the main URL of the application at build-time I had to implement some redirect magic, which you wouldn't have when porting the app to a "normal" static server like nginx or S3. I didn't find in the docs though, how would it work for resources linking other resources? For example, a css file loading an image via @url(...), or a js dynamically constructing the src of an img element. In the css case I guess you could hardwire the static assets URL at build time. The js case would be more tricky, but maybe it's just a practice to avoid.
I might actually deploy it like this, why not?
If anyone has any good book or other resources to get started in building a website like this for people who already have back-end and programming experience but lack html, css, js etc.. please reply them to me! Thank you
it's ridiculously good and if you don't have js experience that shouldn't get in your way. it's easily my favorite frontend language.
https://medium.com/the-node-js-collection/modern-javascript-...
Overall it seems like a good framework, but not making all resources self-contained + relative, seems like a step backwards in that you're locking your resources to a specific domain, which might or might not be available.
Where as `<script src="./main.js">` could potentially load different versions of `main.js` depending on the release version.
I wonder how this could work with server-side rendered apps or pages?
EDIT: Aha, since index.html (only) is dynamically generated and can be populated with pre-rendered state and content.
SPA bundler would generate all files as usual. index.html would be versioned. Configs would not be versioned (except for config examples).
I did a video showing off the web-app side. I'm probably going to do another one showing off the server-side stuff.
It was fun to do. The various pieces of tech are all now coming together nicely.
There's an AWS piece not mentioned here. Basically there's a way to hook GitHub and AWS together such that when you check items in, your website auto-updates using Amazon's SW and CDN tech. (I didn't cover that because it was outside the scope the question I was answering.)
Setting such long expiry times on the cache also sounds like a great way to pollute browser caches with obsolete resources.
I agree with your first complaint however. This seems like an over-complicated way of assuring that browser caches don't bite you.
Setting "forever" caching is standard practice for web development for many years. The browser doesn't really cache these forever - it is merely a hint to the browser that the resource won't change and you don't have to check it again.
In fact, the more resources you can put with such caching the better both for you and the browser's speed.
1. build js,css,img assets
2. upload assets to CDN under unique urls (at build time, for any build that may or may not produce a deployable container)
3. build docker container with server that serves dynamic html referencing those assets
4. deploy said container
Main advantages for me:
1. easy to rollback
2. graceful rollover, all assets any deployed container can reference will always exist, even if multiple versions are deployed at the same time during deployment
3. immutable === cacheable forever
4. CDN always "warm"