We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Separate layouts for different parts of the app
almirsarajcic
Created
Your Phoenix app might have distinct parts of the frontend that don’t share too much Javascript or CSS code. You don’t want your users to download huge CSS and JS bundles for parts of the app they may never reach.
Referencing smaller files directly and not bundling them together is one way to do it and that’s what the Rails community is experimenting with.
I’m conservative in my programming efforts so until I see that approach becomes adopted by more people (i.e. everyone), I’ll keep using the one I’m proposing here.
What we did for StoryDeck was separate all the frontend code into three layouts:
- onboarding (what you see when you just visit app.storydeck.ai)
- app (what logged-in users see)
- guest (what guests see when they are invited to collaborate on stories)
Each of the layout has a CSS and JS entry point:
assets/css/app.css
assets/css/guest.css
assets/css/onboarding.css
assets/js/app.js
assets/js/guest.js
assets/js/onboarding.js
These files look pretty much the same as default app.css
and app.js
files, but in them, we include only the hooks and other JS code that’s necessary for that part of the app to function resulting in smaller minified files.
We even have one CSS entry point for emails, but that’s a story for another time.
To make that work, we made ESBuild and Tailwind support these files:
# aliases in mix.exs
[
"assets.build": [
"tailwind app",
"tailwind guest",
"tailwind onboarding",
"esbuild story_deck"
],
"assets.deploy": [
"tailwind app --minify",
"tailwind guest --minify",
"tailwind onboarding --minify",
"esbuild story_deck --minify",
"phx.digest"
],
]
# watchers in config/dev.exs
[
esbuild: {Esbuild, :install_and_run, [:story_deck, ~w(--sourcemap=inline --watch)]},
tailwind_app: {Tailwind, :install_and_run, [:app, ~w(--watch)]},
tailwind_guest: {Tailwind, :install_and_run, [:guest, ~w(--watch)]},
tailwind_onboarding: {Tailwind, :install_and_run, [:onboarding, ~w(--watch)]}
]
Ok, that might be misleading because it’s just a part of it. Additional ESBuild and Tailwind setup is required in config/config.exs
:
# Configure esbuild (the version is required)
config :esbuild,
version: "0.17.11",
story_deck: [
args:
~w(js/app.js js/guest.js js/onboarding.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
# Configure tailwind (the version is required)
config :tailwind,
version: "3.3.2",
app: [
args: ~w(
--config=tailwind.config.js
--input=css/app.css
--output=../priv/static/assets/app.css
),
cd: Path.expand("../assets", __DIR__)
],
guest: [
args: ~w(
--config=tailwind.config.js
--input=css/guest.css
--output=../priv/static/assets/guest.css
),
cd: Path.expand("../assets", __DIR__)
],
onboarding: [
args: ~w(
--config=tailwind.config.js
--input=css/onboarding.css
--output=../priv/static/assets/onboarding.css
),
cd: Path.expand("../assets", __DIR__)
]
Our router looks like this:
pipeline :app do
plug :put_root_layout, html: {StoryDeckWeb.Layouts, :root}
end
pipeline :guest do
plug :put_root_layout, html: {StoryDeckWeb.Layouts, :guest}
end
pipeline :onboarding do
plug :put_root_layout, html: {StoryDeckWeb.Layouts, :onboarding}
end
scope "/", StoryDeckWeb do
pipe_through [:browser]
# Auth
scope "/" do
pipe_through [:redirect_if_user_is_authenticated, :onboarding]
# ...
end
end
# App
scope "/" do
pipe_through [:app]
# ...
end
and our layouts (e.g. lib/story_deck_web/components/layouts/onboarding.html.heex
) include assets this way:
<link phx-track-static rel="stylesheet" href={static_url(@conn, ~p"/assets/onboarding.css")} />
<script
defer
phx-track-static
type="text/javascript"
src={static_url(@conn, ~p"/assets/onboarding.js")}
>
</script>
Feel free to approach us at projects@optimum.ba if you need any help with this.
Copy link
copied to clipboard