Immutable Web Applications is a framework-agnostic methodology for building and deploying static single-page applications that:
The methodology is based on the principles of strictly separating:
The following concepts define the core requirements for an Immutable Web Application. They are framework and infrastructure agnostic.
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 and they are published to a unique, independent location.
All of the leading application frameworks (Angular CLI, Create React App, Ember CLI, Vue CLI 3) recommend defining environment values at compile time. This practice requires that the static assets are generated for each environment and regenerated for any change to an environment.
Immutable Web Applications reference environment variables that are defined on the global scope and reference one of two ways:
window
object export class UserService {
webServiceUrl: String;
constructor(
private http: HttpClient
) {
- // remove any configuration that is hardcoded or included during compilation
- this.webServiceUrl = 'https://api.myapp.com'
+ // use globally scoped environment variables that are unique to the deployment
+ this.webServiceUrl = window.env.API
}
getUsers() {
return this.http.get(`${this.webServiceUrl}/users`);
}
}
The values for the environment variables are set on an index.html
that is unique to each environment.
Static assets that do not contain anything environment-specific can be built once, published to a unique location, and then used in multiple environments of the web application.
These static assets share the same qualities as hosted javascript libraries on content delivery networks (CDN) (Google Hosted Libraries, cdnjs, jsDelivr, UNPKG):
https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
The location of the jquery library above, referenced by innumerable web applications, is both independent of the applications and uniquely versioned.
https://assets.myapp.com/apps/1.0.2/main.js
Similarly, the location of the web application javascript files are unique and hosted at a location that is dedicated to static assets. The static asset web server is a repository for versions of the web application.
Static assets that do not contain anything environment-specific and are hosted at a unique and permanent location may be configured to be cached by the browser (almost) indefinitely:
cache-control: public, max-age=31536000, immutable
index.html
is deployable configurationThe HTML document of a single-page application (often index.html
) is not static. It varies from environment to environment, and deploy to deploy. The HTML document is a composition of the environment-specific configuration and immutable static assets that define the web application.
index.html
contains fully-qualified references to the static assets- <!-- not unique, not independent -->
- <script src="main.js" type="text/javascript"></script>
+ <!-- permanent, reuseable -->
+ <script src="https://assets.myapp.com/apps/1.0.2/main.js" type="text/javascript"></script>
index.html
defines the values of the globally scoped environment variables<script>
env = {
API: 'https://api.myapp.com',
GA: 'UA-126263119-1'
}
</script>
index.html
must never be cachedTo allow for web application environments to be changed instantly, index.html
must never be cached by the browser or a public cache that cannot be purged on-demand:
cache-control: no-store
The index.html
of most single-page web applications is typically a small document. By including versioned references to the web application static assets and setting the environment variables it is effectively a deployment manifest:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My App</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<!-- environment variables -->
<script>
env = {
API: 'https://api.myapp.com',
GA: 'UA-126263119-1'
}
</script>
<!-- application binding -->
<app-root></app-root>
<!-- fully-qualified static assets -->
<script src="https://assets.myapp.com/apps/1.0.2/main.js" type="text/javascript"></script>
</body>
</html>
The Immutable Web App separates release tasks from build tasks into two distinct workflows.
The codebase of an Immutable Web App has the responsibility of building static assets and publishing them to a static web server. Each state of the codebase can be represented by a set of static assets at a unique location. Not every state of the codebase needs to be published, but no single state of the codebase should ever need to be published more than once.
Generally the codebase is a source controlled code repository integrated with a continuous integration system that is capable of building, versioning, and publishing static assets to a static web server.
An example of this might be:
An Angular project hosted in a GitHub repo. The repo is integrated with TravisCI to build and version assets when commits are pushed to the master branch. Versioned assets are published to a unique location in an AWS S3 bucket.
The index.html
files are managed independently of the codebase and they serve as a manifest for each environment. They should be considered configuration files and managed accordingly. Additionally, There needs to be a mechanism for modifying or replacing the index.html
in each web application environment. The act of changing an index.html
is effectively a deployment.
An example of this might be:
A set of index.html
files, one for each environment, hosted in a Github repo. The repo is integrated with TravisCI to publish an index.html
file when it is modified to an AWS S3 bucket. There is an index.html
and S3 bucket dedicated to each web application environment.
The infrastructure to support Immutable Web Apps is composed of three parts:
index.html
.The two static web servers have different behaviors:
Web Application Server | Static Asset Server | |
---|---|---|
Content | index.html |
Static assets (*.js ,*.css , images, etc) |
Routing | always index.html |
the physical file at the Url |
Cache-Control | no store |
cache-control: public, max-age=31536000, immutable |
Instances | One per web application environment | One per web application |
Building static assets is a complicated process that often involves:
These processes are time consuming, rely heavily upon external dependencies, and often behave in a seemingly non-deterministic way. They are not processes that should be concluded by immediately publishing the generated assets to a production environment without validation. Even the act of publishing the multiple large static assets is a process that may be interrupted and leave the web application environment in a corrupt state.
Immutable Web Applications are generated once and published once to a permanent location. This process happens in advance of a live release. They can be validated in staging environments and promoted to the production environment without being regenerated at significantly lower risk.
A live releases of an Immutable Web App is the act of publishing a single index.html
file. The deployment is instantaneous and all assets are available immediately without any risk of cache being corrupted at the time of the release.
Rollbacks are as risky as deployments and often riskier. For Immutable Web Apps, the same qualities of a deploy apply to a rollback. Notably, in the event of a rollback most browsers will still have the previous assets cached.
In the unlikely event that a browser attempts to load a stale version of index.html
, all of the assets of the previous version will still be available and uncorrupted.
Managing cache-control
headers can be intimidating, especially when the web application infrastructure leverages public caches like the ones used by CDNs. The two simplest concepts in caching are: “Always cached”, and “Never cached”. Immutable Web Apps embraces these concepts completely separating and the code that can be “Always cached” from configuration that is “Never cached”.
The leading application frameworks do not separate the location of static assets from the index.html
in their deployment recommendations. Instead, they recommend adding routing rules to the web server that returns index.html
for all paths that do not resolve to a physical file. The implementation of these routing rules may differ between web servers and mistakes often result in paths that resolve to the wrong resource.
Separating the hosting of index.html
and the static assets eliminates this risk. The static assets server always serves a physical file represented by the url and the web application server always serves index.html
for any url.
Immutable Web Applications methodology builds on several trends in web application development:
Modern Application Frameworks: Angular, React, Vue, and Ember have enabled teams to build increasingly complex single-page static apps. Tools like webpack have improved the ability to create, optimize, and manage build artifacts.
DevOps: The culture of DevOps has enabled web application developers to decompose and reevaluate their web application infrastructure to better serve the requirements of their web applications.
Maturing Application Patterns & Practices: Backend applications and services are converging around a set of best practices that support portability, scalability, and high availability. This trend has dramatically increased the tools and services available, especially related to containers and container orchestration. Many of these practices are just now starting to be applied to static single-page web applications.
The Twelve-Factor App: This methodology for building web apps is based on separation of concerns in order to achieve portability and robustness. Immutable Web Apps separate several of the same concerns in order to achieve similar objectives.
JAMstack: The Immutable Web Apps methodology is completely aligned with the best practices of the JAMstack.
ng-immutable-example: Converts a project generated by Angular CLI into an Immutable Web App.
react-immutable-example: Converts a project generated by Create React App into an Immutable Web App.
unpkg-immutable-example: An example Immutable Web App hosted on npm, UNPKG, and GitHub Pages.
aws-lambda-edge-example: Deploys an Immutable Web App to AWS Lambda@Edge with Serverless.
“Single-page App Deployments”, Gene Connolly, NH.js, November 14th 2018
“Risk-free Deployments with Immutable Web Apps”, Gene Connolly, underthehood.meltwater.com, December 3rd 2018
Immutable Web Applications is not yet actively supported by any application framework, tools, or services. Most static, single-page applications should be able to adopt the Immutable Web Applications methodology by making several minor structural changes to the codebase, build process, and infrastructure. Until there is widespread support, however, web applications that use advanced code-splitting techniques may find adoption to be complicated or impractical.
Achieving widespread support will require:
Building tutorials, documentation, and examples of how to build Immutable Web Applications using the leading application frameworks and tools.
Proposing changes to existing frameworks and tools to support Immutable Web Applications.
Advocacy and education of Immutable Web Applications through blog posts and presentations.
Immutable Web Apps was origionally developed by Meltwater.