Javascript EEX templates

Are Javascript EEX templates supposed to work?

E.g. if I create config.js.eex in /assets/js/
and then in app.js import Config from “./config”
will Webpack/Phoenix evaluate EEX expressions in config.js.eex?

I can’t make it work, when I change the extension from js to js.eex Webpack can’t resolve the file.

This is not really how things work. Why do you need eex for your javascript?

1 Like

@LostKobrakai I need to pass some config constants to JavaScript and the script will be used outside of the main app (it’s a kind of a lib). I want to keep the constants in one place - Phoenix config.

Traditionally you embed these on data- html attributes somewhere in the HTML, and then you can document.getElementById("constants").dataset['someConstant']. This lets you use proper javascript tooling instead of basically doing javascript code generation via EEX.

@benwilson512 As I mentioned the JS script will be used outside of the main app as a library, so unfortunately passing data from backend to frontend through data attribs is not possible.

If it’s a library it’s even more important to treat it like a proper javascript module / dependency. Can you expand on the use case?

You’d have to compile them to JS files before webpack can work it’s magic on it. Webpack’s compilation step should be seen as completely separate from phoenix.

Additionally, the html.eex templates are turned into functions on phoenix’s compilation and not individual files, so if you really want to let webpack access the JS files, you would need to make your own compilation script to output individual JS files for webpack’s consumption.

@Brainiac Thanks for the insights.

@benwilson512 Sure, we’re building something similar to Google Analytics, which tracks all events on a website automatically, without defining manually events to be tracked with something like track(“some event”). Our customers will use the admin dashboard (Phoenix app) and install JS snippets on their websites (the script that I want to pass through EEX evaluation). The JS snippet may have some config values which are shared with the Phoenix app, e.g. API URLs, cookie names, dictionaries, etc.

The important part of such static assets is that they’re fast and cacheable. So you’d want to have a static javascript file and just like google analytics some additional code, which supplies the config you need.

Gotcha. So the point isn’t to generate javascript the phoenix app will execute, the point is to show people on the page the handful of lines they need to copy and paste into their app, with various values injected.

If that’s the case, I’d approach this more like a general string interpolation problem than a javascript file interpolation thing. This is more like having a _copyable_javascript.html.eex partial where inside of that you have basically a <pre> tag with the text you want inside.

1 Like

@LostKobrakai, @benwilson512 I’m sorry, I should be more clear, I explained it incorrectly… The JS snippet is only to load the JS lib asynchronously, similarly as Google Analytics is loaded on websites. Notice there are 2 things: 1) JS snippet that can be copied by the client from the Phoenix dashboard and 2) JS lib which is included by the clients on their websites. The config values I want to pass through EEX are going to the JS lib not the JS snippet, which is not important for the merits of the case, I mentioned it only to explain what we’re working on.

To explain it differently, suppose you develop Google Analytics. You’ve got a backend app and JS lib that your clients include on their websites. How to share some config values in the backend app and the JS lib that is loaded on websites?

The same way that Google analytics and every other javascript library does it. You give them a snippet to put in their website that sets config values on some global object, or on some DOM element that your library will look for. I assure you, google analytics does not generate a unique copy of itself just for you. You get linked the same globally cached google analytics code that everyone else does, and you parameterize it by passing in parameters on your website.

@benwilson512 The config values that I’m talking about are not for the lib configuration by the customer, but are internal config data of the lib. An example of such config data is the URL of a 3rd party API endpoint that is used by the frontend lib and the backend app, one that changes from time to time.

Well you could add those in the snippet and not in the .js file as they will be visible anyway.

But if you want to render dynamic javascript, you just have to create an eex template in phoenix, and the url used to access the js file must point to a phoenix controller+action.

In the action you just set the appropriate header for content type and render the template. Nothing complicated here.

Sorry I’m having such a difficult time understanding the idea here. Can you provide a more concrete example?

@benwilson512 Ok, let’s simplify it. Suppose you’re working on a Google Analytics clone…

Google Analytics is installed on a website by including a JS snippet on each page.

An example of such snippet is here:

<!-- Google Analytics -->

<script>

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){

(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),

m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)

})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-XXXXX-Y', 'auto');

ga('send', 'pageview');

</script>

<!-- End Google Analytics -->

Notice that the snippet loads asynchronously the actual Google Analytics JS lib: “https://www.google-analytics.com/analytics.js”

Now, once the lib is loaded it can track events on the website, for example pageview events, which are sent to the backend (our Phoenix app) for processing.

So we’ve got 3 parts here:

  • JS snippet

  • JS lib

  • backend (Phoenix app)

Now, how to share some Phoenix config with the JS lib (which is generated through Webpack pipeline)? Suppose both the analytics.js (JS lib) and the backend (Phoenix app) need to connect to some external API endpoint, which changes from time to time. A good idea is to put the API URL to a config file and share it between different modules of the system that use it, so that the configuration data is in one place only. This could be API endpoint URLs, cookie names, query string keys, dictionaries, etc.

I tried to put such config data to Phoenix config.exs and then interpolate it in lib.js.eex (which is an equivalent of the Google Analytics JS lib - analytics.js).

Let me make sure I’ve got this correct. You have a client website, which may or may not be phoenix, doesn’t matter, and that website adds the snippet.

That snippet links a javascript file you want to serve from your phoenix app, and you’d like to interpolate some of the config from the phoenix app into the javascript file you are serving.

It sounds like the main thing that changes over time is the external API endpoint. Setting aside for a second that this seems like something DNS should solve, I still think you want to handle this in two parts.

Part 1 is a completely static javascript file that takes the endpoint as a parameter or javascript based setting, it would not be something you’d interpolate in.
Part 2 is an API a client can hit to get the current endpoint to use.

Then your snippet does 3 things:

  1. loads the completely static javascript library
  2. hits the API to figure out what endpoint to use
  3. sets the endpoint on the loaded javascript lib, and starts everything.

This way the javascript lib, which is way more bytes than the endpoint data, can be 100% static proper javascript file that you can aggressively cache, put on a CDN, etc. The API stays dynamic and returns the endpoint.

You can see how #2 is basically just DNS though. IMHO the best option would be to stand up a reliably constant domain, hard code the javascript lib to point to that domain, and then just have that domain point to different other domains via DNS as it changes.

2 Likes

@benwilson512 Yes, you’ve got it right.

As for the API endpoint case that I gave - unfortunately it is just an example, I’m looking for a generic solution that will allow to manage such config values from Phoenix.

Also, referencing Part 1, if you pass the config values as parameters of the JS file, then everytime the config changes the client website will have to update their code.

PS: Rails asset pipeline works in such a way - you can use .js.erb files.

Config values you want to control from Phoenix the JavaScript code should derive by hitting your Phoenix API. Then you can change them all you want and clients don’t need to change anything.