Rendering Elm to static HTML with Selenium


Suppose you have a simple page on a website with part of its content generated by an Elm program. To also make (the initial view of) the content available to users or simple web crawlers who do not run JavaScript, it can be useful to generate a static HTML copy. This can also help to prevent flickering when the website loads.

Previously I used elm-static-html static-html-from-elm for this, but in combination with Html.Styled from elm-css the output was not exactly the same as when running the Elm code in a browser and there were encoding problems. Hence, why not use a browser to generate the static HTML?

We can do this using Selenium.

First, we make an HTML file which works in your local browser, say render.html: See also the Elm guide about Embedding in HTML.

<!doctype html>
<html lang="en">
    <meta charset="UTF-8">
    <script src="MyElmProject.min.js"></script>
    <div id="MyApp"></div>
      node: document.getElementById("MyApp")

Second, we use Selenium to control a “headless”, i.e. invisible, Chrome to open the HTML file, run the Elm script and print out the innerHTML. For example, save the following as

from os import getcwd
from selenium import webdriver

options = webdriver.ChromeOptions()
options.headless = True
driver = webdriver.Chrome(options=options)
url = "file://" + getcwd() + "/render.html"
divContent = driver.find_element_by_id('MyApp').get_attribute('innerHTML')

Finally, the following Makefile will build the .js, .min.js and .static.html files. For .min.js we follow the Elm guide about minification.


MyApp.js: src/*.elm
    elm make --optimize --output=$@

MyApp.min.js: MyApp.js
    uglifyjs $< --compress \
        'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \
        | uglifyjs --mangle --output=$@

MyApp.static.html: render.html MyApp.min.js
        python3 > $@

You can now use the content of MyApp.static.html as a static replacement by putting it inside the <div id="MyApp"></div> in index.html. We leave it as an exercise for the reader to also automate this last step.