Jun 4, 2016
I’ve found myself in the unexpected situation of having to write a lot of browser apps/single page apps this year. I have some thoughts on configuration.
Centralize environment-dependent facts to simplify management & testing
Make it easy to manage app secrets.
@wlonk adds:
“Secrets”? What this means in a browser app is a bit different.
Which is unpleasantly true. In a freestanding browser app, a “secret” is only as secret as your users and their network connections choose to make it, i.e., not very secret at all. Maybe that should read “make it easy to manage app tokens and identities,” instead.
Keep config data & API tokens out of app’s source control
Integration point for external config sources (Aerobatic, Heroku, etc)
The forces described in 12 Factor App: Dependencies and, to a lesser extent, 12 Factor App: Configuration apply just as well to web client apps as they do to freestanding services.
Yes:
No:
There are a few ways to get configuration into the app.
<head>
<script>window.appConfig = {
"FOO_URL": "https://foo.example.com/",
"FOO_TOKEN": "my-super-secret-token"
};</script>
<script src="/your/app.js"></script>
</head>
window.appConfig.foo
will read them.
This can be a <script>window.appConfig = {some json}</script>
tag or a standalone config script loaded with <script src="/config.js">
Generating config scripts sets a minimum level of complexity for the deployment process: you either need a server to generate the script at request time, or a preprocessing step at deployment time.
It’s code generation, which is easy to do badly. I had originally proposed using JSON.stringify
to generate a Javascript object literal, but this fails for any config values with </script>
in them. That may be an unlikely edge case, but that only makes it a nastier trap for administrators.
There are more edge cases. I strongly suspect that a hazard-free implementation requires a full-blown JS source generator. I had a look at building something out of escodegen and estemplate, but
escodegen
’s node version doesn’t generate browser-safe code, so string literals with </script>
or </head>
in them still break the page, and
converting javascript values into parse trees to feed to estemplate
is some seriously tedious code.
<head>
<link rel="foo-url" href="https://foo.example.com/">
<script src="/your/app.js" data-foo-token="my-super-secret-token"></script>
</head>
fetch('/config') /* {"FOO_URL": …, "FOO_TOKEN": …} */
.then(response => response.json())
.then(json => someConfigurableService);
See for example clientconfig:
var config = require('clientconfig');