Currently I am working in a project where I have to integrate many real-time data sources and show the results to the users as soon as possible. As you can imagine
Elixir
+
PhoenixFramework
are my chosen tools. This kind of projects always imply a lot of challenges that we have to solved (check my fork of
Floki
). But the tedious one has not been directly related with the particular case of this project, it has been dealing with
Brunch
. Brunch is a front-end build tool that is brought by Phoenix by default. It seems to be simple (especially if you compare with
Webpack
), but my problems began when I need to execute different Javascript scripts depending on the rendered Phoenix template.
The HTML from Phoenix are rendered in server side and Brunch compiles and joins everything (by default) to one Javascript file(or maybe two if you want to have the 3rd party in other place). That provokes that you will have an unique entry point, but if you will have several "views" already rendered from the server, maybe you will have several needs that will be covered by different Javascript codes and you don't want to execute always all.
¿How did I finally solve it?
Brunch can compile and join the Javascript files in the way that you decide, therefore I decided to create two main folders inside web/static/js path:
We will say to brunch that it has to produce a common.js file for all the javascript files that are not in "specific" folder (common + 3rd party) and one file per template that will be under "specific" folder (I created a folder per template because it was better for my sanity where the js file and the folder have the same name. Maybe in the future you have to have more specific logic in each template and you need to have more js files in each folder). I know that I will finally produce more files that we would have desired, but the heaviest part should be in common.js, and each entry point Javascript file will contain a few lines of code (in my case, less than 10 lines each) because we only need to use these files for initializations.
Brunch can compile and join the Javascript files in the way that you decide, therefore I decided to create two main folders inside web/static/js path:
- common: It will contain all the logic
- specific: It will contain the entry points
We will say to brunch that it has to produce a common.js file for all the javascript files that are not in "specific" folder (common + 3rd party) and one file per template that will be under "specific" folder (I created a folder per template because it was better for my sanity where the js file and the folder have the same name. Maybe in the future you have to have more specific logic in each template and you need to have more js files in each folder). I know that I will finally produce more files that we would have desired, but the heaviest part should be in common.js, and each entry point Javascript file will contain a few lines of code (in my case, less than 10 lines each) because we only need to use these files for initializations.
exports.config = { files: { javascripts: { joinTo: { "js/common.js": /(?!web\/static\/js\/specific)/, "js/myscript.js": /^(web\/static\/js\/specific\/one)/, "js/myscript2.js": /^(web\/static\/js\/specific\/two)/, "js/myscript3.js": /^(web\/static\/js\/specific\/three)/ } ... }
All the Phoenix templates are rendered using a layout (web/static/templates/layout/app.html.eex). This file assumes that Brunch is going to produce a Javascript file called app.js which will contain all the logic that we need and also the entry point. In our case, we have to substitute this file by common.js because this new file will contain all the logic. And then, we have to add two new lines:
<script type="text/javascript" src="<%= static_path(@conn, "/your_app/js/common.js") %>"></script> <script type="text/javascript" src="<%= static_path(@conn, "/your_app/js/" <> assigns[:js_script]) <> ".js" %>"></script> <script>require("<%= "web/static/js/specific/" <> assigns[:js_script] <> "/" <> assigns[:js_script] %>")</script>
The first one will indicate the necessary Javascript file in each case and the second one will indicate that it is the entry point, so it will be executed.
As you can see, we have used the possibility of reading custom values that could be stored in the user connection, hence; we only have to indicate in the variable ":js_script" which one should be used in each case.
As you can see, we have used the possibility of reading custom values that could be stored in the user connection, hence; we only have to indicate in the variable ":js_script" which one should be used in each case.
defmodule YourApp.YourController do use YourApp.Web, :controller def index(conn, _params) do conn |> assign(:js_script, "my_script") |> render("index.html") end end
The main logic and the 3rd party code which are the heaviest part will be in common.js, so it will be downloaded once and cached by the browser and the specific parts, just your few lines of initialization, will be downloaded for each template, although it will finally be cached as well.
This method was the simplest one that I found in order to solve my problems, I hope that it is useful for someone else.
This method was the simplest one that I found in order to solve my problems, I hope that it is useful for someone else.