How to deploy i18n-js to Heroku when using jsbundling
We recently released a big frontend upgrade to Front Lobby. This included an upgrade to i18n-js v4 which ended up causing problems when deploying to Heroku. Here’s how we fixed it.
The problem
When we tried to deploy we got the following error during Herokus build phase:
ERROR in ./app/javascript/i18n.js 3:0-47
Module not found: Error: Can't resolve './translations.json' in '/tmp/build_36012f77/app/javascript'
resolve './translations.json' in '/tmp/build_36012f77/app/javascript'
This is the error you’d get if i18n-js hasn’t exported its translations to translations.json
yet, as it is supposed to.
The setup
Now, some relevant details of our setup:
- i18n-js 4
- jsbundling 1.2
- Using webpack 5.9
- Deployed to Heroku
The common approach
The commonly recommended way to ensure your translations are available in your frontend assets is to have them exported during the assets:precompile
task is run. In our case we need to hook into the javascript:build
task, which is what jsbundling uses to compile assets.
To do so we’ve added the following rake task, which works just fine - at least in development 🙄:
namespace :i18n do
namespace :js do
desc "Exports translations to be used by i18n-js"
task :export => :environment do
`bundle exec i18n export`
end
end
end
# Run `i18n export` prior to building Javascript assets, so translations are
# available for use. javascript:build is the rake task run by jsbundling
# during assets:precompile
Rake::Task["javascript:build"].enhance(["i18n:js:export"])
(Depending on your build system the above may vary; there are more examples at the i18n-js discussions.
Alas, for some reason the above rake task didn’t do the trick for us when deploying.
Buildpacks; the solution to - and source of - all build problems
As it turns out, we were running the nodejs buildpack first (since that’s the recommended order in order to control Node and Yarn versions). The nodejs buildpack by default runs your build
script if you have one defined in package.json
. However, jsbundling adds a build
script to package.json
, which it uses internally, I guess:
"scripts": {
"build": "webpack --config webpack.config.js"
}
Unfortunately, this meant that the nodejs buildpack would try to compile our assets before the above rake task was even invoked - and even before our Ruby dependencies had been installed at all! There’s no chance for our translations to have been exported at that point.
Luckily Heroku provides a way to customize the build process, which we can use to tell Heroku to not build anything during the build step of the nodejs buildpack:
"scripts": {
"build": "webpack --config webpack.config.js",
"heroku-postbuild": ""
}
With this in place in package.json
Heroku would do nothing during the nodejs build step and everything would work just fine when we reached the ruby build step:
-----> Build
Detected both "build" and "heroku-postbuild" scripts
Running heroku-postbuild (yarn)
-----> Pruning devDependencies
...
-----> Preparing app for Rails asset pipeline
Running: rake assets:precompile
Here’s hoping this saves someone else the headaches I had figuring it out.