Monday, July 22, 2013

Auto-Generating Appcache Manifests for Dart Applications


With travel and prep for OSCON, I am going to try to keep posts brief this week. Still, the gods of the chain must be appeased, so posts there must be. In today's, I hope to nail down one of the two manual processes in deployment of the ICE Code Editor: generating the application cache manifest file (the other is copying Dart packages from system cache).

Application cache is a newish feature of modern browsers that allow sites to cache files in browsers. This is done with a manifest attribute on the <html> tag of a page:
<!DOCTYPE html>
<html manifest="editor.appcache">
  <!-- ... -->
</html>
The format of the appcache file is a plain text file. Inside, it typically contains three sections: the manifest (opens the manifest and contains meta-information), the cache (contains a list of files to be cached) and a listing of files that should be fetched from the network rather than cached (typically a wildcard to denote everything not explicitly listed as being cached).

I am going to build this with a Bash script. Sometimes there is nothing better than a simple Bash script to get the job done. I toyed with the idea of a script to replace just a portion of the existing appcache file, but complete regeneration seems more robust. If I ever have to make a change to the structure of the appcache file, I will not have to worry about breaking this script by moving around important anchor text.

I start by declaring a variable to hold the name of the appcache file:
#!/bin/sh

APPCACHE=editor.appcache
I have no intention of changing the name of the file—I use a variable as a matter of taste to keep the rest of the code clean.

Next up, I wipe the old appcache file and write the manifest section of the file:
##
# Manifest
echo 'CACHE MANIFEST' > $APPCACHE
date +'# %Y-%m-%d %H:%M:%S' >> $APPCACHE
echo >> $APPCACHE
I start with a single file redirect (>) so that echo 'CACHE MANIFEST' overwrites the previous contents of the file. All subsequent operations will be done with the append redirect (>>). I am not worrying about making a backup because the generated code will be checked into source code—I already have a backup.

After the manifest directive, I add a comment containing the date to the appcache file—the date command's support for strftime-like formatting comes in handy here. Browser application cache does not use this information—the leading pound sign is a comment in appcache files. The purpose of the time stamp is solely to force the web server to respond with a 200 instead of 304. That is, a change in the content of the file—even a comment—will tell the server that this appcache file has changed and that it should not respond that it is NOT MODIFIED (304).

It is vitally important that this timestamp change whenever cached files change. If the server replies that the appcache file is unchanged, then no updated files will be loaded by the browser. I could change each and every file that is listed in the appcache manifest, but if the appcache file is unchanged, the browser will continue to use the old versions. If files are added or subtracted from the list of cached files in the appcache file, that would count as a change to the appcache file. More often than not, changes involve only modifying existing files. And a timestamp comment is the best way to communicate that some change awaits.

With the preliminary work out of the way, it is time for the cached content. There are three different kinds of content that I want cached for the ICE Code editor: libraries used while editing code in ICE (e.g. Three.js, Tween.js), files explicitly referenced in SCRIPT tags of the ICE application web page, and the JavaScript files that actually give ICE its functionality.

For the first section of cache—the files I want to make available to the programmers using ICE to make 3D animations—I list them manually:
##
# Cache

echo 'CACHE:' >> $APPCACHE

# Fixed files used within the editor for creating Three.js worlds:
cat <<EOF >> $APPCACHE
/favicon.ico
/Three.js
/Tween.js
/Detector.js
/physi.js
/Mouse.js
/Scoreboard.js
/ChromeFixes.js
/ammo.js
/physijs_worker.js

EOF
I am cating a HEREDOC to accomplish this. The cat command would normally echo the contents of a file. By supplying it with a HEREDOC, I tell cat to read from here.

Next, I have to echo the main.dart file into the list of cached files in editor.appcache:
# Dart script referenced in a SCRIPT tag:
echo main.dart >> $APPCACHE
echo >> $APPCACHE
No production browser in the world is going to do anything with that file, but it is explicitly referenced in a SCRIPT tag. For this reason, it must be in application cache to prevent a network request.

Last, I use my find + grep chain from the other day to find the JavaScript files—both generated by Dart and included from the ICE library—and place them in the list of cached files:
# Dart and JS code used to make the editor:
find | \
  grep -e \\.js$ -e \\.map$ -e \\.html$ -e \\.css$ | \
  grep -v -e unittest -e /lib/ | \
  sed 's/^\.\///' \
  >> $APPCACHE
I add a small sed script to the chain to remove leading ./ from the find output:
./part.js
./index.html
./main.dart.js.map
...
That does it for the CACHED section of my application cache file.

All that remains is a wildcard entry in the NETWORK section:
##
# Network
cat <<EOF >> $APPCACHE

NETWORK:
*
EOF
That is all there is to it. After running the script, the only difference in editor.appcache is the timestamp:
➜  ice-beta git:(gh-pages) ✗ git diff -u editor.appcache
diff --git a/ice-beta/editor.appcache b/ice-beta/editor.appcache
index 90d2ba8..ebaedef 100644
--- a/ice-beta/editor.appcache
+++ b/ice-beta/editor.appcache
@@ -1,5 +1,5 @@
 CACHE MANIFEST
-# 2013-07-20
+# 2013-07-22 10:26:40

 CACHE:
 /favicon.ico
So I have successfully written an automated script that ought to keep up with the changing files in ICE. The full, working script is:
#!/bin/sh

APPCACHE='editor.appcache'

##
# Manifest
echo 'CACHE MANIFEST' > $APPCACHE
date +'# %Y-%m-%d %H:%M:%S' >> $APPCACHE
echo >> $APPCACHE

##
# Cache

echo 'CACHE:' >> $APPCACHE

# Fixed files used within the editor for creating Three.js worlds:
cat <<EOF >> $APPCACHE
/favicon.ico
/Three.js
/Tween.js
/Detector.js
/physi.js
/Mouse.js
/Scoreboard.js
/ChromeFixes.js
/ammo.js
/physijs_worker.js

EOF

# Dart script referenced in a SCRIPT tag:
echo main.dart >> $APPCACHE
echo >> $APPCACHE

# Dart and JS code used to make the editor:
find | \
  grep -e \\.js$ -e \\.map$ -e \\.html$ -e \\.css$ | \
  grep -v -e unittest -e /lib/ | \
  sed 's/^\.\///' \
  >> $APPCACHE

##
# Network
cat <<EOF >> $APPCACHE

NETWORK:
*
EOF
That is a fine stopping point for today. Up tomorrow, I will try to automate the copying of system pub packages into a deployment.

Day #820

No comments:

Post a Comment