Despite beginning life as a simple scripting language, modern Javascript (ES6+) has to be transpiled to run in the browser.
Compilers like Babel take in ES6 and spit out a jumbled mess of ES5-spec JS that's comprehensible to the client side. This is basically how all modern front-end JS frameworks operate.
It's a silly and needlessly complex workflow. You could even call it a devolution, as Javascript's transformation from a high-level scripting language to a compiled language certainly feels like a step backwards.
But that's not what this article is about.
When you write ES6+ spec JS using a front-end framework and transpile it, the code you write isn't actually the code that's running in the browser. Instead, your end-user is running a garbled, incomprehensible blob of minified ES5 that's versioned and hashed automatically. This certainly isn't to the benefit of the end-user, who can no longer make sense of what your client-side code is actually doing.
That's not what this article is about, either.
This article is about Javascript file caching.
A Babel-free caching strategy
Babel admittedy makes it so you don't have to think about cached JS at all. Since filenames in transpiled JS get a hash of the file contents appended to them, every change is easily detected by your browser because the filename itself has changed. The browser sees every modification as a new JS file by default. You're never going to have to deal with a stale <script>
or <style>
asset if you're using this workflow.
If you start writing JS outside of the confines of Babel and front-end build tools, however, you'll quickly run into caching issues with your JS files (and other static assets like CSS).
Thankfully, this is an easy fix - the way developers did this in the pre-framework "dark ages" was to append a version number to their static assets as a URL query string, and this strategy still works fine today.
In this workflow, myscript.js
becomes something like myscript.js?version=0.12.3
when imported. Every incrementation of your version
variable will be treated by the browser as a new file. No more stale static assets!
Versioning JS files in Flask
Of course, it doesn't make sense to increment this version number individually for every <script>
import, so serving this value as a global variable from the back-end makes sense.
In Flask, adding a context_processor
to your app factory and injecting your version number as a config will make it accessible across all your templates:
@app.context_processor
def inject_configs():
return dict(version='0.0.1')
From there, simply use Jinja2's url_for
to construct your static file import with your versioning scheme as a URL argument programmatically:
<script type="text/javascript" src="{{ url_for('static', filename='js/app.js', version=(version)) }}"></script>
Note: Any unknown arguments supplied to url_for
are treated as query strings by Jinja2.
That's it! Certainly easier than a mandatory compile step, no?
In closing
The strength of this cache management strategy lies in its simplicity, though it does come with a few drawbacks:
-
File versioning is not fully automated. The developer has to remember to increment the
version
variable with every new release. -
The browser will download all static assets whenever the version number is incremented, not just the static assets that have changed.
Neither of these constraints are very meaningful. Proper versioning upon every update to your web app is good practice, anyways. As for forcing the browser to re-download all static assets with every new release, some may argue that this is inefficient as it forces the browser to fetch files that may not have changed.
Unless you're working with megabytes of Javascript on a slow connection, however, this isn't going to meaningfully affect pageload times.
And let's face it: If you're forcing clients to download huge amounts of JS, you're probably using a front-end framework with Babel, anyways 😉