How I host Elm web applications with GitHub Pages

dwayne

Dwayne Crooks

Posted on October 17, 2024

How I host Elm web applications with GitHub Pages

Any Elm web application that uses Browser.sandbox, Browser.element, or Browser.document can be hosted with GitHub Pages. And, to host any website or web application with GitHub Pages all you need to do is tell GitHub which branch contains the files you want to host. In this article, we'll start a project from scratch and gradually add to it until we've created a reasonable process for building the project and hosting it with GitHub Pages.

Hello, world!

Let's use Git to create a new directory, called hello, and initialize a repository in it.

git init hello
Enter fullscreen mode Exit fullscreen mode

Then, change into the directory and create an HTML file, called index.html, containing "Hello, world!".

cd hello
echo "Hello, world!" > index.html
Enter fullscreen mode Exit fullscreen mode

Now, let's stage the file and commit our changes.

git add index.html
git commit -m "Initial commit"
Enter fullscreen mode Exit fullscreen mode

To host the website we'd use GitHub Pages. GitHub Pages is a static site hosting service provided by GitHub that allows you to host static websites directly from your GitHub repositories.

Log into your GitHub account and create a new public repository called hello. Then, push your existing repository from your local machine to the new remote repository you created on GitHub.

git remote add origin git@github.com:username/hello.git
git push -u origin master
Enter fullscreen mode Exit fullscreen mode

Finally, tell GitHub to use your master branch to publish a GitHub Pages site. After a few seconds your website would be available at https://username.github.io/hello/.

Add CSS

Let's alter the presentation of the website with CSS. Update index.html in order to add an external stylesheet.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=1" />

    <title>Hello</title>
    <link rel="stylesheet" href="index.css">
  </head>
  <body>
    <p>Hello, <span class="name">world</span>!</p>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Then, write your styles in index.css.

html, body {
  margin: 0;
  height: 100%;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
}

p {
  margin: 0;
  text-align: center;
  font-size: 3rem;
  color: #999;
}

.name {
  color: #333;
}
Enter fullscreen mode Exit fullscreen mode

And, update the public facing website by committing your changes and pushing them to your remote repository.

git add index.html index.css
git commit -m "Alter the presentation with CSS using an external stylesheet"
git push
Enter fullscreen mode Exit fullscreen mode

After a few seconds you should see the changes appear at https://username.github.io/hello/.

Add JavaScript

For the sole purpose of adding JavaScript to the project, we'd change the text from "world" to our name when the page is loaded. Let's write the JavaScript in index.js.

document.addEventListener("DOMContentLoaded", () => {
  document.querySelector(".name").textContent = "Dwayne";
});
Enter fullscreen mode Exit fullscreen mode

And, let's not forget to reference the JavaScript file in index.html.

<body>
  <p>Hello, <span class="name">world</span>!</p>
  <script src="index.js"></script>
</body>
Enter fullscreen mode Exit fullscreen mode

Then, commit and deploy.

git add index.html index.js
git commit -m "Add JavaScript to change the text from \"world\" to my name when the page loads"
git push
Enter fullscreen mode Exit fullscreen mode

Wait a few seconds to view the changes at https://username.github.io/hello/.

Some thoughts

In a few lines of HTML, CSS, and JavaScript, we've managed to create and host a simple website using Bash, Git, and GitHub Pages. If you want a more interesting website then it's a matter of adding more lines of HTML, CSS, and JavaScript. And, with sufficient skill in HTML, CSS, and JavaScript you'd be able to create and host amazing websites.

A web application makes use of these same ingredients, i.e. HTML, CSS, and JavaScript, but it uses significantly more JavaScript. As the JavaScript powering your web application grows in size it can bring with it a variety of problems that a few languages, like TypeScript, ReScript, PureScript, and Elm, have attempted to solve. Each of the aforementioned compile to JavaScript languages have their pros and cons but it is beyond the scope of this article to get into those details. Suffice it to say, my preference is Elm. It is also not the goal of this article to convince you to use Elm but only to show you how Elm fits into the flow of creating a web application and hosting it on GitHub Pages. So let's continue by adding Elm to our project.

Add Elm

In case you haven't already done so, please follow these instructions to install Elm.

N.B. If you're interested in using isolated development environments then you might also be interested in reading about how I use Devbox in my Elm projects.

Let's start an Elm project at the root of our hello directory.

elm init
Enter fullscreen mode Exit fullscreen mode

Press Enter at the prompt to answer yes and create the elm.json and the src directory. Then, create the file src/Main.elm.

touch src/Main.elm
Enter fullscreen mode Exit fullscreen mode

Now, open it in your preferred editor and add the following:

module Main exposing (main)

import Html as H
import Html.Attributes as HA

main : H.Html msg
main =
    H.p []
        [ H.text "Hello, "
        , H.span [ HA.class "name" ] [ H.text "Dwayne" ]
        , H.text "!"
        ]
Enter fullscreen mode Exit fullscreen mode

Compile the Elm code to JavaScript and place it in a file called app.js.

elm make src/Main.elm --output=app.js
Enter fullscreen mode Exit fullscreen mode

You'd see that it generated an app.js file as well as an elm-stuff directory. elm-stuff contains information about your code that the compiler uses, however it doesn't need to be version controlled. Add a .gitignore file to tell Git not to track elm-stuff.

echo "elm-stuff/" > .gitignore
Enter fullscreen mode Exit fullscreen mode

Edit index.html to reference app.js instead of index.js. Also, initialize the Main module and tell it where to render your application.

<body>
  <div id="app"></div>
  <script src="app.js"></script>
  <script>
    Elm.Main.init({
      node: document.getElementById("app")
    });
  </script>
</body>
Enter fullscreen mode Exit fullscreen mode

Finally, stage your changes, commit them and deploy your project as before.

git rm index.js
git add elm.json src/Main.elm app.js .gitignore index.html
git commit -m "Create an Elm web application"
git push
Enter fullscreen mode Exit fullscreen mode

Check https://username.github.io/hello/ after a few seconds to see what you've made.

Congratulations! You've created and deployed an Elm web application.

Some more thoughts

A larger Elm web application would have more lines of Elm code and/or more Elm files. If it uses ports then it would also have more lines of JavaScript and/or more JavaScript files. The process that I showed you above to build and deploy your Elm web application would still apply. That said, there are things that could be improved.

Of immediate concern to us is the fact that there are files being deployed that we don't want to deploy but that we still want Git to track. For e.g. elm.json, .gitignore, and src/Main.elm.

Let's fix that.

Deploy from a separate branch

Currently, we're implementing our Elm web application on the master branch and deploying from that very same branch. This is getting us into trouble because we have implementation files that we don't want to put on the web. Instead, we'd tell GitHub to use a different branch to deploy our application. And, on that branch we'd only supply the files that we definitely want to put on the web.

Let's create a new orphan branch to track only the files we want to host. An orphan branch is a branch that has no parent commits when it is first created. You'd be able to add anything you want to that branch and its history would be distinct from the history of the master branch. Most importantly it would allow us to manage the two aspects, i.e. implementation and deployment, of our project in one Git repository.

git checkout --orphan gh-pages
Enter fullscreen mode Exit fullscreen mode

Now, remove the unnecessary files and directories leaving behind app.js, index.css, and index.html.

git rm -rf .gitignore elm.json src/
rm -rf elm-stuff/
Enter fullscreen mode Exit fullscreen mode

Then, commit the changes and let GitHub know about the new branch.

git commit -m "Initial commit"
git push -u origin gh-pages
Enter fullscreen mode Exit fullscreen mode

Finally, tell GitHub to use your gh-pages branch to publish the GitHub Pages site. After a few seconds your web application would be available at the same URL, i.e. https://username.github.io/hello/, as before.

Stop tracking app.js on the master branch

Checkout the master branch.

git checkout master
Enter fullscreen mode Exit fullscreen mode

Tell Git to stop tracking app.js since it can easily be generated by compiling src/Main.elm.

git rm app.js
echo "app.js" >> .gitignore
git commit -m "Stop tracking app.js"
Enter fullscreen mode Exit fullscreen mode

The start of a build and deployment process

As we add features we'd make changes to index.css, index.html, and src/Main.elm. At convenient points along the way we'd commit those changes. To view our work we'd compile src/Main.elm into app.js as before and open the index.html file in a browser.

Now, let's say we're happy with our changes and we want to deploy them. We'd need to take the changed files and copy them over to the gh-pages branch, commit the changes and push them up to the GitHub repository.

Let's assume app.js and index.css sustained a few changes. Here's one way to proceed to deploy those changes.

mkdir ../temp
cp app.js index.css ../temp

git checkout gh-pages

cp ../temp/app.js ../temp/index.css .
git add app.js index.css
git commit -m "Update app.js and index.css"
git push

git checkout master
Enter fullscreen mode Exit fullscreen mode

Here's another way to proceed. This time using git-worktree so that we'd have both the master and the gh-pages branches checked out simultaneously.

git worktree add ../temp gh-pages

cp app.js index.css ../temp
git -C ../temp add app.js index.css
git -C ../temp commit -m "Update app.js and index.css using git-worktree"
git -C ../temp push

git worktree remove ../temp
Enter fullscreen mode Exit fullscreen mode

With either approach our changes would be deployed. I use variations on these ideas to deploy all of my Elm web applications, from side projects to projects at work.

Case Study: Markdown Previewer

Markdown Previewer is an Elm web application based on freeCodeCamp's Build a Markdown Previewer front-end project. It's implementation can be found on the master branch and the gh-pages branch contains the 3 files (app.js, index.css, and index.html) hosted by GitHub Pages at https://dwayne.github.io/elm-markdown-previewer/.

The bin/build script

The bin/build script prepares an empty build directory, copies the HTML files, compiles the Sass files into CSS, and compiles the Elm code into JavaScript.

The bin/deploy script

The bin/deploy script runs a production build (the CSS is compressed with no source maps and the Elm code is optimized with mdgriffith/elm-optimize-level-2 and minified with Terser), uses git-worktree to check out the gh-pages branch into a temporary directory, copies the files HTML, CSS, and JavaScript files from the build directory, commits the changes and pushes them up to the remote repository so that GitHub Pages could host the new files.

Other examples

Here are more projects that are hosted with GitHub Pages. They all have build and deployment scripts similar to Markdown Previewer.

What about Browser.application?

Conduit is an Elm web application that uses Browser.application. Suppose I hosted it using GitHub Pages. Then, a fresh page load for https://dwayne.github.io/elm-conduit/login would cause the GitHub Pages server to look for a file called "login" which it won't find. The GitHub Pages server would return a 404 page indicating that the file was not found. This is expected behaviour for an HTTP server. However, what we want to happen instead is for the request to be redirected to the index.html page so that the Elm web application would handle the routing for us as described here. Unfortunately, GitHub Pages doesn't support adding custom redirects. But, other hosting providers do and we'd talk about that in a separate article.

N.B. This is a problem you have to overcome for any web application that uses the History API to perform client-side routing. See Notes on client-side routing from Create React App or Routing with history.pushState from Vue CLI.

Recap

We started from nothing and then created and deployed a simple HTML, CSS, and JavaScript based website using Bash, Git and GitHub Pages. Afterwards, we added Elm to the mix and I showed you how to build and deploy an Elm web application using the same technologies.

We were using one branch to do everything, i.e. to implement our web application and to deploy it, so some of our implementation files ended up in the deploy. To avoid this we decided to use separate branches. One dedicated to the implementation and one dedicated to the deployment. This slightly increased the complexity but I showed you two ways you'd go about deploying your project from its dedicated branch.

The good news is that the deploy process doesn't get more complicated than what I've shown you above. The core concept remains the same. You have branches (say a master branch and several feature branches) in which you use to implement your web application and you have branches (say a staging and production branch) you use for deployment. Then, you have to tell your hosting provider, be it GitHub Pages, Netlify, Render, Cloudflare Pages, or something else, which branches you're using for your deployments.

Conclusion

To host an Elm web application with GitHub Pages you have to put the files you want to host on a Git branch and point GitHub to that branch.

No matter how complicated your build process becomes you can always have it spit out the files you need to host in a build directory of your choosing. Then, it's a matter of copying those files over to the hosting branch and pushing the changes up to your GitHub repository to allow the GitHub Pages server to host the new files.

Further reading

Subscribe to my newsletter

If you're interested in improving your skills with Elm then I invite you to subscribe to my newsletter, Elm with Dwayne. To learn more about it, check out this announcement.

💖 💪 🙅 🚩
dwayne
Dwayne Crooks

Posted on October 17, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related