Use runtime environment variables in bundled/minified javascript apps.
🔬🚧 This is a reasearch project. Results so far indicate that it's not generalizing to different JS frameworks as gracefully as one might hope.
A Heroku app uses this buildpack + an npm module.
JS_RUNTIME_TARGET_BUNDLE
must be set to the path glob pattern for the javascript bundle containing the heroku-js-runtime-env. For example:
- create-react-app:
JS_RUNTIME_TARGET_BUNDLE=/app/build/index.*.js
- ember-cli (example):
JS_RUNTIME_TARGET_BUNDLE=/app/dist/assets/vendor-*.js
- vue-cli with webpack (example):
JS_RUNTIME_TARGET_BUNDLE=/app/dist/static/js/vendor.*.js
JS_RUNTIME_
-prefixed environment variables will be made available in the running Heroku app via npm module heroku-js-runtime-env.
Example Vue app, created in this experiment.
npm run dev
mode does not pass arbitrary env vars instead requiring settings in config/dev.env.js
. So, dev mode seems to be broken. (Help?)
✏️ Replace $APP_NAME
with your app's unique name.
npm install -g vue-cli
vue init webpack $APP_NAME
cd $APP_NAME
git init
git add .
git commit -m '🌱 create Vue app'
heroku create $APP_NAME
heroku buildpacks:add https://github.com/mars/heroku-js-runtime-env-buildpack
heroku config:set JS_RUNTIME_TARGET_BUNDLE=/app/dist/static/js/vendor.*.js
heroku buildpacks:add heroku/nodejs
heroku buildpacks:add https://github.com/heroku/heroku-buildpack-static
# Serve it with static site buildpack
echo '{ "root": "dist/" }' > static.json
git add static.json
git commit -m 'Serve it with static site buildpack'
Add Heroku build hook to package.json
. Merge the following "heroku-postbuild"
property into the existing "scripts"
section:
{
"scripts": {
"heroku-postbuild": "npm run build"
}
}
Then, commit this change:
git add package.json
git commit -m 'Add Heroku build hook to `package.json`'
In the Vue component src/components/HelloWorld.vue
:
<script>
import runtimeEnv from '@mars/heroku-js-runtime-env'
export default {
name: 'HelloWorld',
data () {
const env = runtimeEnv()
return {
msg: env.JS_RUNTIME_MESSAGE || 'JS_RUNTIME_MESSAGE is empty. Here’s a donut instead: 🍩'
}
}
}
</script>
Then, install the npm module, commit, and deploy the app:
npm install @mars/heroku-js-runtime-env --save
git add .
git commit -m 'Implement runtimeEnv() in a component'
git push heroku master
heroku open
Once deployed, you can set the JS_RUNTIME_MESSAGE
var to see the new value take effect immediately after the app restarts:
heroku config:set JS_RUNTIME_MESSAGE=🌈
heroku open
Failed to find a valid digest in the 'integrity' attribute for resource 'https://example-ember-runtime-env.herokuapp.com/assets/vendor-05f75ec213143035d715ab3c640a3ff4.js' with computed SHA-256 integrity 'oSQ3RCkKyfwVgWjG0HDlTzDFreoQnTQCUCqJoiOJEMs='. The resource has been blocked.
Example Ember app, created in this experiment.
✏️ Replace $APP_NAME
with your app's unique name.
npm install -g ember-cli
ember new $APP_NAME
cd $APP_NAME
heroku create $APP_NAME
heroku buildpacks:add https://github.com/mars/heroku-js-runtime-env-buildpack
heroku config:set JS_RUNTIME_TARGET_BUNDLE=/app/dist/assets/vendor-*.js
heroku buildpacks:add heroku/nodejs
heroku buildpacks:add https://github.com/heroku/heroku-buildpack-static
# Serve it with static site buildpack
echo '{ "root": "dist/" }' > static.json
git add static.json
git commit -m 'Serve it with static site buildpack'
Add Heroku build hook to package.json
. Merge the following "heroku-postbuild"
property into the existing "scripts"
section:
{
"scripts": {
"heroku-postbuild": "ember build --environment=production"
}
}
Then, commit this change:
git add package.json
git commit -m 'Add Heroku build hook to `package.json`'
Create a component that uses JS Runtime Env:
npm install --save-dev ember-browserify
npm install --save @mars/heroku-js-runtime-env
ember generate component runtime-env
Edit the component app/components/runtime-env.js
to contain:
import Component from '@ember/component';
import { computed } from '@ember/object';
import runtimeEnv from 'npm:@mars/heroku-js-runtime-env';
export default Component.extend({
message: computed(function() {
const env = runtimeEnv();
return env.RUNTIME_JS_MESSAGE || 'RUNTIME_JS_MESSAGE is empty. Here’s a donut instead: 🍩';
})
});
Edit the component template app/templates/components/runtime-env.hbs
to contain:
<h2>{{message}}</h2>
{{yield}}
Edit the application template app/templates/components/runtime-env.hbs
to contain:
{{runtime-env}}
{{!-- The following component displays Ember's default welcome message. --}}
{{welcome-page}}
{{!-- Feel free to remove this! --}}
{{outlet}}
Then, commit and deploy the app:
git add .
git commit -m 'Implement runtimeEnv() in a component'
git push heroku master
heroku open
Once deployed, you would ideally set the RUNTIME_JS_MESSAGE
var to see the new value take effect immediately after the app restarts:
heroku config:set JS_RUNTIME_MESSAGE=🌈
heroku open
Failed to find a valid digest in the 'integrity' attribute for resource 'https://example-ember-runtime-env.herokuapp.com/assets/vendor-05f75ec213143035d715ab3c640a3ff4.js' with computed SHA-256 integrity 'oSQ3RCkKyfwVgWjG0HDlTzDFreoQnTQCUCqJoiOJEMs='. The resource has been blocked.
Normally javascript apps are compiled into a bundle before being deployed. During this build phase, environment variables may be embedded in the javascript bundle, such as with Webpack DefinePlugin.
When hosting on a 12-factor platform like Heroku, these embedded values may go stale when setting new config vars or promoting through a pipeline.
Originally developed as part of create-react-app-buildpack, this buildpack aims to solve this problem in a generalized way.
When developing a JavaScript app, use the npm module to access runtime environment variables in client-side code.
Then, each time the app starts-up on Heroku, a .profile.d
script (installed from the buildpack) is executed which fills in a JSON placeholder (with a REACT_APP_
legacy name) in the JavaScript bundle with the runtime environment variables. The result is 🍃fresh runtime environment variables in the production javascript bundle without recompiling.
The program which performs the bundle injection is written in Ruby 2.3.1, the version included in the Heroku-16 stack.
gem install bundler
bundle install
bundle exec rake