Dwayne Crooks
Posted on October 17, 2024
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
Then, change into the directory and create an HTML file, called index.html
, containing "Hello, world!"
.
cd hello
echo "Hello, world!" > index.html
Now, let's stage the file and commit our changes.
git add index.html
git commit -m "Initial commit"
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
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>
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;
}
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
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";
});
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>
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
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
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
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 "!"
]
Compile the Elm code to JavaScript and place it in a file called app.js
.
elm make src/Main.elm --output=app.js
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
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>
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
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
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/
Then, commit the changes and let GitHub know about the new branch.
git commit -m "Initial commit"
git push -u origin gh-pages
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
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"
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
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
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.
- dwayne/elm-hello
- Other freeCodeCamp projects
- 2048
- 7GUIs
- dwayne/elm-integer calculator live demo
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
- How I use Devbox in my Elm projects
- What is
git checkout --orphan
used for? - Git for Static Sites - Interestingly Kris Jenkins blogged about a similar approach back in 2016. I actually came to these ideas, independently of Kris' post, through a Ruby project called Octopress.
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.
Posted on October 17, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.