This project demonstrates how to convert to an Immutable Web App from an Angular app that was generated with Angular CLI version 7.0.3.
This project was created by running:
> npx @angular/cli new ng-immutable-example
? Would you like to add Angular routing? (y/N) N
? Which stylesheet format would you like to use? (Use arrow keys)
❯ CSS
SCSS [ http://sass-lang.com ]
SASS [ http://sass-lang.com ]
LESS [ http://lesscss.org ]
Stylus [ http://stylus-lang.com ]
> cd ng-immutable-example
Converting this this new application to build an Immutable Web App requires the following steps:
- Referencing environment variables defined on
window
- Rendering an
index.html
template for deployments - Running locally
Angular has documentation for using environment-specific variable in your app. This pattern compiles environment-specific values into the javascript bundles at build-time. To make the assets immutable, the setting of values must be shifted from build-time to run-time. Fortunately, the Angular environments can still be leveraged by assigning them to an environment object defined on window
and then the values can be defined in the index.html that is unique to each deploy.
environment.prod.ts
:
- export const environment = {
- production: false
- };
+ export const environment = (window as any).env;
A benefit of this approach is that you can continue to use multiple Angular environments with hard-coded values for local development.
In theory, the role of index.html
in an Immutable Web App is to be a deployment manifest that only contains configuration that is unique to the environment where it is deployed.
In practice, there is often a need to include markup or scripts in the index.html
that are more than just configuration, or that doesn't vary by environment, or that does change between versions of the app. This issue can be resolved by creating an immutable index.html
template, that is published with the other immutable assets.
This example uses EJS as the templating language to render index.html
, but any templating language can be used.
-
Rename
src/index.html
tosrc/index.ejs
. -
Identify the parts of the
index.html
that vary by environment: -
Set
deployUrl
andbaseHref
to an EJS template string inangular.json
and update the name ofindex
:
{
"projects": {
"ng-immutable-example": {
...
"architect": {
"build": {
...
"configurations": {
"production": {
...
+ "deployUrl": "<%=deployUrl%>",
+ "baseHref": "<%=baseHref%>",
- "index": "src/index.html",
+ "index": "src/index.ejs",
- Update the
href
offavicon.ico
insrc/index.ejs
:
- <link rel="icon" type="image/x-icon" href="favicon.ico">
+ <link rel="icon" type="image/x-icon" href="<%=deployUrl%>favicon.ico">
- Add a new script tag to render the javascript environment-specific variables in
src/index.ejs
:
<head>
...
+ <script>
+ env = <%-JSON.stringify(env)%>;
+ </script>
</head>
The source template that is converted into an immutable template during the build.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>NgImmutableExample</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="<%=deployUrl%>favicon.ico">
<script>
env = <%-JSON.stringify(env)%>;
</script>
</head>
<body>
<app-root></app-root>
</body>
</html>
The immutable template that is published along with the other versioned, immutable assets. It is combined with production-config.json
to render the index.html
that is a deployment manifest.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>NgImmutableExample</title>
<base href="<%=baseHref%>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="<%=deployUrl%>favicon.ico">
<script>
env = <%-JSON.stringify(env)%>;
</script>
<link rel="stylesheet" href="<%=deployUrl%>styles.3bb2a9d4949b7dc120a9.css"></head>
<body>
<app-root></app-root>
<script type="text/javascript" src="<%=deployUrl%>runtime.3afa4a1fd69496214142.js"></script><script type="text/javascript" src="<%=deployUrl%>polyfills.c6871e56cb80756a5498.js"></script><script type="text/javascript" src="<%=deployUrl%>main.61370ef751f4da2a56bc.js"></script></body>
</html>
The environments-specific values that vary. This is combined with the published index.ejs
to be rendered into index.html
. It is not an immutable asset and should be managed as deployment-specific configuration.
{
"baseHref": "/",
"deployUrl": "https://assets.ng-immutable-example.com/1.0.0/",
"env": {
"production": true,
"api": "https://api.ng-immutable-example.com/"
}
}
The environment-specific deployment manifest. It is the product of rendering an immutable template against the config.json
. Publishing this file to the web application environment is an atomic deployment.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>NgImmutableExample</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="https://assets.ng-immutable-example.com/1.0.0/favicon.ico">
<script>
env = {production:true,api:"https://api.ng-immutable-example.com/"};
</script>
<link rel="stylesheet" href="https://assets.ng-immutable-example.com/1.0.0/styles.3bb2a9d4949b7dc120a9.css"></head>
<body>
<app-root></app-root>
<script type="text/javascript" src="https://assets.ng-immutable-example.com/1.0.0/runtime.3afa4a1fd69496214142.js"></script><script type="text/javascript" src="https://assets.ng-immutable-example.com/1.0.0/polyfills.c6871e56cb80756a5498.js"></script><script type="text/javascript" src="https://assets.ng-immutable-example.com/1.0.0/main.61370ef751f4da2a56bc.js"></script></body>
</html>
Running ng serve
with the environment-specific defaults in src/environments/environments.ts
will continue to run as designed. The only part that is missing, after the immutable conversion, is the src/index.html
has been replaced with an EJS template. Until there is better support for Immutable Web Apps in Angular CLI, an index.html
must be rendered from the EJS template prior to the build.
-
Run
npm i --save-dev @immutablewebapps/ejs-cli
: This module rendersindex.html
provided a template and JSON file. The module@immutablewebapps/ejs-cli
is just a lightweight wrapper aroundejs
that only serves this purpose. As mentioned earlier, any templating language may be used. -
Create
.immutablewebapps/config.json
: This file will store the default configuration values for running locally. Often it is simply:
{
"deployUrl": "",
"baseHref": "",
"env": {}
}
- Update the
npm start
script:
"scripts": {
- "start": "ng serve",
+ "start": "cat src/index.ejs | iwa-ejs --d .immutable/config.json > .immutable/index.html && ng serve",
}
- Update
angular.json
to use.immutable/index.html
:
{
"projects": {
"ng-immutable-example": {
"architect": {
"build": {
"options": {
- "index": "src/index.html",
+ "index": ".immutable/index.html",
- Ignore
.immutable/index.html
.gitignore
:
+ # Immutable Web Apps
+ .immutable/index.html
- Run
npm start
and you are running locally!
This project will be updated as better patterns for building Immutable Web Apps are established and as Angular CLI changes.