Separate JS & CSS for admin panel in Phoenix
It's a common need to have separate precompiled assets for admin panel or other heavy pages. Here's how to do it.
Note: In this guide I'm using Brunch as it's a default asset manager in Phoenix. Of course, as explained in Phoenix guide, it's easy to use different asset build tool like Webpack, but that's out of the scope of this article.
Why bother
Well, one of the main objectives for web developer should be to keep client-consumed website as lightweight as possible. Even if we include appropriate clauses in JavaScripts and stylesheets to prohibit them from applying to non-admin pages, browsers will still have to:
- download them, slowing down the initial entry and each one after you've released new assets, which by the way may include zero changes relevant to non-admin pages
- process them, which is even worse as it'll slow down rendering of every page (and make no mistake - even with modern JS engines, it will noticeably affect your page loads)
- handle errors that happen in admin JS, possibly canceling execution of other key scripts
Admin panels usually come with some pretty heavy assets like WYSIWYG editors, image uploaders or UI plugins for drag-n-drop and alike. I'd definitely recommend keeping those away from the main website unless it shares those functionalities with the admin panel.
Directory structure
There's no single convention for namespacing assets for multiple scopes, but I've invented one that plays nice with existing conventions and makes things easy for Brunch. You'll end up with the same directories as the default Phoenix asset layout, that is:
js
for ES6-enabled, module-wrapped JavaScriptscss
for stylesheetsvendor
for JavaScripts that don't get processed at all
So, go ahead and create the following directory structure:
web/static/app/js web/static/app/css web/static/app/vendor web/static/admin/js web/static/admin/css web/static/admin/vendor
You should move existing app-related files into app
and admin-related ones to admin
. If some of new directories end up empty, you can create empty .keep
files in them in order to force them to stay in version control and be available when needed.
You may have noticed that I didn't include the assets
directory in the party. That's because the contents from hypothetical admin/assets
and app/assets
would be copied into the same target directory by Brunch, possibly colliding with each other if you'd ever put the same file in both. Therefore, web/static/assets
should stay as is and it's up to you to decide on organization within it (perhaps just by adding web/static/assets/admin
).
Configuring Brunch
Now, replace the files
key in brunch-config.js
with the following:
lang:js files: { javascripts: { joinTo: { 'js/app.js': /^(web\/static\/app)/, 'js/admin.js': /^(web\/static\/admin)/ } }, stylesheets: { joinTo: { 'css/app.css': /^(web\/static\/app)/, 'css/admin.css': /^(web\/static\/admin)/ } }, templates: { joinTo: 'js/app.js' } }
We want to have the same behavior of the vendor directory as before, so no module-wrapping and no Babel for ES6. While module-wrapping will still be disabled to every vendor directory including our new ones, we need to disable Babel manually with the following:
lang:js plugins: { babel: { /* ... */ ignore: [ /web\/static\/app\/vendor/, /web\/static\/admin\/vendor/ ] } }
That's it. You can run brunch build
to see if it works. Now you can use your new assets in the layout with paths like those:
lang:elixir static_path @conn, "/css/app.js" static_path @conn, "/css/app.css" static_path @conn, "/css/admin.js" static_path @conn, "/css/admin.css"
Summary
You can apply the same technique to create virtually unlimited number of precompiled assets. Don't hesitate to comment. If anything goes wrong I'll gladly help.
That said, I'm leaving you in peace with your per-scope Brunch.