This repository is a working demo that shows our approach to Javascript components working in server side rendered pages. It completely replaces Webpack and works with lighter Rollup.
As an Javascript framework we use Svelte that works pretty well with Elixir. It's also very lightweight, syntax is supersimple and it is most useful for small components and forms, altough we are running some SPA administration in Svelte so it can be used for that as well.
As a HTML/CSS framework Tailwind is used but this approach can be used with whatever you have.
There are four apps as in this demo that represents of what can be done and how.
Simple components dummy components that just display some data and simulate a delayed action by waiting for some time.
This component shows how to communicate with API controller. This is not a traditional stateless API controller that requires access token but rather its authentication process is based on CSRF token and Session. Controller is behind a specific pipeline that confirms validity of CSRF token.
CSRF token is stored into <head> section where the component looks before it sends any request to server and put it into HTTP headers.
This component shows how can you create a dynamic form instead of using server side input helpers. Input fields are still rendered inside a server rendered form tag but we render inputs and validate everything on the client in a Svelte component and control when it's submitted from it.
Page title helper is used in app.html.eex that looks for :page_title key in the connection assigns. Page title can be set from controller or view depending on what you prefer. Sometimes it makes sense to set all data in controller and sometimes you would use view, it makes no difference to the template helper.
<title><%= title(@conn, assigns) %></title>
From technical point of view not that much has changed. This demo project is still a Phoenix project that you are already familiar with, we just replace Webpack content of assets
folder with Vite, added some new eex files and few lines of code here and there.
apps
folder withou having to register them somewherestatic/apps
directory (can be configured) and generated functions to get path to script and style for all appsconn |> Apps.include(["connect", "numbers"])
<span data-app="like" data-id="1" data-liked="true"/>
For easier usage there is a _template folder that contains all necessary files. _template/lib/replace_with_project_name_web should be renamed to your web project or the files should be copied out of it to your web project.
Create new application, for example withmix phx.new --no-assets project_name
Copy files from _template
folder
Add these aliases to mix.exs
defp aliases do
[
setup: ["assets.setup", "assets.build", "deps.get"],
"assets.setup": ["cmd --cd assets npm i"],
"assets.build": ["cmd --cd assets npm run prod"],
...
]
end
assets
folder you should have following structureapps
for Svelte applications/componentscss
for global scripts with global.scss for global cssimages
for static images copied to priv/static/images
js
for main.js
that is the main entry point for all common Javascript code shared among all (or some) apps, like notification manager, event bus, basic rest providers and so onlib\example_app\templates\layout\root.html.eex
- Add this code at the end of <head>
section to allow automatic script registrations
<script defer type="text/javascript" type="module" src={Routes.static_path(@conn, "/js/main.mjs")}></script>
<%= render "_component_scripts.html", additional_scripts: Map.get(assigns, :additional_scripts, []) %>
<%= render_existing ProjectNameWeb.LayoutView, "app.styles.html", assigns %>
<%= render "_component_styles.html", additional_styles: Map.get(assigns, :additional_styles, []) %>
<%= render_existing view_module(@conn), String.replace_suffix(view_template(@conn), ".html", "") <> ".styles.html", assigns %>
and this just above end of body
<%= render_existing ProjectNameWeb.LayoutView, "app.scripts.html", assigns %>
<%= render_existing view_module(@conn), String.replace_suffix(view_template(@conn), ".html", "") <> ".scripts.html", assigns %>
Check Plug.Static in checkpoint that you dont have js and apps allowed.
Add titles
{:simplificator_3000_phoenix, "~> 0.1.0"}
as dependencyuse Simplificator3000Phoenix, :controller
to controller makro definition in project_name_web.ex
use Simplificator3000Phoenix, :view
to view macro definion in project_name_web.ex
use Simplificator3000Phoenix, :layout
to your layout view (layout_view.ex
)<title><%= title(@conn, assigns) %></title>
to head in your root layoutset_title("title")
in controllerspage_title
= default title
if title_separator
is set final title is composed of page_title
+title_separator
+title you set config :project_name,
page_title: "Project name",
title_separator: "・"
To add new app just create folder with its name and add js/main.js
. There you have to create contructor for your app and register it to app manager.
import App from "./App.svelte";
// instantiate the component
function constructor(element, props) {
new App({
// mount it to `document.body`
target: element,
// pass some props (optional)
props: props,
});
}
window.AppsManager.register("counter", constructor);
Now to use it just add it to Apps.Include(["app-name",...])
in controller you want to use it and add elemenet with data-app="app-name"
to template and ...
Your apps (their css and js) are automatically loaded as you need them and created with proper parameters.
Start your Phoenix server with run.bat
that automatically opens iex console.
You run vite builds by running npm run dev
for development build without minimalization and with watch and npm run prod
for production build. If you only want to build some apps put their names after command (npm run dev app1 app2
)
Now you can visit localhost:4000
from your browser.
Ready to run in production? Please check our deployment guides.