Skip to content
This repository was archived by the owner on Jun 11, 2022. It is now read-only.

Move bundling to runtime; fix for stateless builds #8

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .buildpacks
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
https://github.com/heroku/heroku-buildpack-nodejs.git
https://github.com/mars/create-react-app-inner-buildpack.git#v1.3.0
https://github.com/mars/create-react-app-inner-buildpack.git#stateless-build
https://github.com/heroku/heroku-buildpack-static.git
10 changes: 10 additions & 0 deletions .profile.d/create-react-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash

# Fail immediately on non-zero exit code.
set -e

# Build the javascript bundle, when var is not set or equal to 'false'.
if [ 'false' == "${REACT_APP_STATEFUL_BUILD-false}" ]; then
# Performed at runtime to pickup current `REACT_APP_*` environment variables.
/app/.heroku/node/bin/npm run build
fi
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,31 @@ Set [config vars on a Heroku app](https://devcenter.heroku.com/articles/config-v
heroku config:set REACT_APP_HELLO='I love sushi!'
```

♻️ The app must be re-deployed for this change to take effect, because the automatic restart after a config var change does not rebuild the JavaScript bundle.
#### Add-on config vars

To use the config vars directly from an add-on, create it with a custom `REACT_APP_` prefix.

Example with the `fixie` add-on:

```bash
git commit --allow-empty -m "Set REACT_APP_HELLO config var"
git push heroku master
heroku addons:create fixie --as REACT_APP_FIXIE
```

See: [Creating an add-on](https://devcenter.heroku.com/articles/managing-add-ons#creating-an-add-on)

#### Stateless builds

Environment variables may be different between various instances of an app, such as dev, staging, & production. This buildpack follows Heroku's stateless app architecture by bundling the JavaScript with these values everytime a dyno starts-up.

If bundling the JavaScript takes longer than the [boot timeout limit](https://devcenter.heroku.com/articles/limits#boot-timeout) (currently 60-seconds), then the app will [fail to start-up](https://devcenter.heroku.com/articles/error-codes#h20-app-boot-timeout).

Here are a few options to work around this limitation:

* limit use of environment vars to values that do not change between instances of the app
* `heroku config:set REACT_APP_STATEFUL_BUILD=true` will switch bundling the JavaScript to the build phase, where it has a time limit of 30-minutes, but updating environment variables will require redeployment, potentially breaking things like [Pipeline](https://devcenter.heroku.com/articles/pipelines) promotions
* contact Heroku support to have the boot timeout for the app lifted up to 120-seconds, but this will cause slower recovery from app crashes


Version compatibility
---------------------

Expand All @@ -147,11 +165,12 @@ This buildpack composes three buildpacks (specified in [`.buildpacks`](.buildpac
* `node_modules` cached between deployments
2. [`mars/create-react-app-inner-buildpack`](https://github.com/mars/create-react-app-inner-buildpack)
* generates the [default `static.json`](#customization)
* performs the production build for create-react-app, `npm run build`
3. [`heroku/static` buildpack](https://github.com/heroku/heroku-buildpack-static)
* [Nginx](http://nginx.org/en/) web server
* handy static website & SPA (single-page app) [customization options](https://github.com/heroku/heroku-buildpack-static#configuration)

4. This buildpack builds the JavaScript bundle in one of two ways:
1. when config var `REACT_APP_STATEFUL_BUILD` is set to `true`, then [`npm run build` is executed during compile](bin/compile) once for the deploy
2. otherwise, [`npm run build` is executed in the runtime](.profile.d/create-react-app.sh) each time a [dyno](https://devcenter.heroku.com/articles/dynos) starts-up

### General-purpose SPA deployment

Expand Down
28 changes: 28 additions & 0 deletions bin/compile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ function indent() {
esac
}

function export_env_dir() {
local env_dir=$1
local whitelist_regex=${2:-''}
local blacklist_regex=${3:-'^(PATH|GIT_DIR|CPATH|CPPATH|LD_PRELOAD|LIBRARY_PATH|IFS)$'}
if [ -d "$env_dir" ]; then
for e in $(ls $env_dir); do
echo "$e" | grep -E "$whitelist_regex" | grep -qvE "$blacklist_regex" &&
export "$e=$(cat $env_dir/$e)"
:
done
fi
}

BUILD_DIR=$1
CACHE_DIR=$2
ENV_DIR=$3
Expand Down Expand Up @@ -64,3 +77,18 @@ else
echo "create-react-app `.buildpacks` not defined. Exiting."
exit 1
fi

export_env_dir "$ENV_DIR" '^(REACT_APP_|NODE_)'

if [ 'true' == "${REACT_APP_STATEFUL_BUILD-false}" ]; then
echo '=====> Stateful build, now generating the JavaScript bundle'
echo ' NOTICE: `REACT_APP_*` config vars will be compiled into'
echo ' the slug. To update their values, push a new deployment.'
cd $BUILD_DIR
.heroku/node/bin/npm run build
else
echo '=====> Stateless build, the JavaScript bundle will be generated on start-up'
fi

mkdir -p $BUILD_DIR/.profile.d
cp $BP_DIR/.profile.d/create-react-app.sh $BUILD_DIR/.profile.d/