We’ll be making a Hanami 2.1.0.rc3 next month, and it will contain a new approach to asset compilation. I want to explain this here in detail so you can understand what’s coming, so you can know what to test with your apps, and to gather any feedback you may have!
Background
Last year we released Hanami 2.1.0.rc1 and .rc2 and were preparing for the 2.1.0 final release when we encountered an issue with our assets handling: assets from different slices, but with the same base names would conflict and override each other in the final compiled public/assets/
directory.
We explored a fix for this inside our hanami-assets JS package, and while we could have made that work, several downsides remained:
- We still required a duplicate (and incomplete) implementation of detecting Hanami slices
- We still required users to keep a redundant slice prefix in front of each slice’s assets (e.g.
javascript_tag("admin/app")
inside theAdmin
slice - We still merged all slice assets into a single shared manifest, thereby breaking the isolation that slices otherwise provide
- It required us to keep and maintain a more complex esbuild wrapper, which would increase the chances of bugs or issues in the future.
- And because our usage of esbuild would have remained off its “golden path”, it increased our risk of hitting an evolutionary dead end for this overall approach.
Given all of this, we decided to step back and try a fresh approach to assets handling. I’ve now proven this approach will work, and I’d like to share it with you in advance of the upcoming rc3 release.
The new approach: the hanami
CLI invokes an esbuild process per slice
Previously, we invoked a single Node process that process that found all assets, compiled them with esbuild and then created a merged manifest. In the bug fix attempt I linked above, we looked at invoking an esbuild compilation per slice within the same single Node.js process (using async/await to run them in parallel).
With our new approach, our hanami assets compile
and assets watch
commands will invoke a single esbuild process per slice, and will pass the relevant slice paths as arguments to each esbuild process. We’ll handle this by forking to a Ruby process per slice.
In this approach, slices will be detected faithfully, since it’s our Ruby code that already knows about all registered slices and their locations.
esbuild compiles a single slice’s assets only, and outputs to a dedicated directory with a dedicated manifest
When our esbuild wrapper is invoked by the CLI, it will compile the assets within a single slice only, and output the compiled files to a dedicated directory for the slice:
- For the app, this is
public/assets/
- For each slice, this is
public/assets/[slice_name]/
A single assets.json
manifest will be written into each directory, and will contain entries for that slice’s assets only.
In this way, our usage of esbuild becomes much more typical: one input directory, one output directory. Nice and simple.
Every slice will have its own independent "assets"
component
With every slice having its own assets directory and its own assets manifest, this also means that every slice will have its own Hanami::Assets
instance registered as a component with the "assets"
key, providing access to the assets in that slice’s manifest.
A slice can only access its own assets by default (with those assets no longer redundantly prefixed by the slice name)
Following from the above, this means a slice will have access only to its own assets by default.
This means e.g. that javascript_tag("app.js")
in an Admin
slice will reference the file at /public/assets/admin/app-[hash].js
.
Because each slice has its own assets component and its own underlying assets manifest, that means that you no longer need to provide the redundant slice name prefix that you had to do for the previous 2.1.0 release candidates. This is why you can write javascript_tag("app.js")
instead of javascript_tag("admin/app.js")
.
Assets may be shared across slices via component imports and view context deps
Slices are meant to be serve as a means to isolate code, and the approach with assets will now take this same approach.
However, if you need to access assets from another slice, you can do so by:
- Importing the
"assets"
component from the other slice - Including the imported assets component as a dependency of your slice’s view context
- Referencing that assets object directly in your views/helpers:
javascript_tag(other_slice_assets["app.js"])
This is not intended to be the mainstream approach, but the fact that it is possible should allow some flexibility where it is needed. See this PR for a clear demonstration.
A slice may have its own config/assets.js
Lastly, if you need to provide custom esbuild options for a particular slice’s asset compilation, you will now be able to do this via a custom config/assets.js
that will be picked up and used just for that slice. All you need to do is put the file inside the slice’s own config/
directory: slices/[slice_name]/config/assets.js
.
The app’s top-level config/assets.js
will be used for all slices that do not have their own custom config.
Where can I see this work?
Check out the following work-in-progress PRs:
- Run multiple assets processes by timriley · Pull Request #131 · hanami/cli · GitHub
- Compile within single slice only by timriley · Pull Request #20 · hanami/assets-js · GitHub
- Require a root when initializing assets by timriley · Pull Request #136 · hanami/assets · GitHub
- Set up an assets provider per slice by timriley · Pull Request #1371 · hanami/hanami · GitHub
- Support cross slice assets by timriley · Pull Request #1372 · hanami/hanami · GitHub
When will the rc3 release be?
I’m hoping for first half of February.
I’d love some solid testing for a week or two. If all goes well, 2.1.0 stable will come next.
Thanks for checking this out, and I hope this new approach can provide the most solid foundation for assets in Hanami 2. If you have any feedback, please share it here.