Publishing a Standalone Angular App on GitLab Pages

(Full Disclosure: I used GitLab because I work for GitLab. That said, this wasn’t an official work project, simply something I did on the side)

I recently finished writing a web-based tool called Fossilizer; a simulator for one of the many systems in Path of Exile. The tool is somewhat unique in that, while it is a dynamic page, it acts like a static page: There’s no backend database, no persistence of any kind for that matter. Everything the app needs is available via static .html or .js files. In short, my usual web deployment destinations (e.g. AWS, Firebase, Heroku) were overkill. All I really needed was some place to host a static website.

GitLab Pages

GitLab isn’t just a place to store and collaborate on git repositories. It also features built-in CI/CD tooling. While that tooling can be used to run tests, in my case I was looking at it for a different reason: GitLab Pages.

The features page mentions that it can “host your static websites” and “use any static site generator”. While there are plenty of examples of doing this using a number of SSGs, there were none for Angular (presumably because people don’t generally use it as a static site). Thus, this article.

The .gitlab-ci.yml file

All of GitLab CI is controlled by a file in your repository named .gitlab-ci.yml. A bare-bones CI file for pages looks like this:

image: alpine:latest
pages:
  stage: deploy
  script:
  - echo 'Nothing to do...'
  artifacts:
    paths:
    - public
  only:
  - master

What this does is publish the contents of the public directory to GitLab pages. If you put an index.html file in the public directory, you can go to https://yourusername.gitlab.io/projectname/index.html and see it in all its glory.

Tweaking for Angular

At this point, you might think you’re done. The above CI directive is all you need, so all you have to do is ng build and copy the contents of the dist directory into the public directory. But you’d have to do that every single time for every single deploy, and what are computers for if not doing the easily automatable?

The script: portion of the CI file above can be instructed to do builds; in static site generators this is the portion that would invoke e.g. Jekyll. We just need it to invoke angular builds instead:

script:
- npm install -g @angular/cli@6.2.1
- npm install
- ng build
- mv dist/fossilizer/* public/

These steps install the Angular CLI tools that we need in order to do the build and, while we’re at it, install everything else we need. ng build then does the same thing it’d do on our machines, and finally we move everything that Angular built into the public directory, where GitLab pages can deploy it.

The default docker image that the CI tasks run on doesn’t have NPM, though, so you’ll need to include one that does:

image: node:8.12.0

The Final .gitlab-ci File

image: node:8.12.0

pages:
cache:
paths:
- node_modules/

stage: deploy
script:
- npm install -g @angular/cli@6.2.1
- npm install
- ng build
- mv dist/fossilizer/* public/
artifacts:
paths:
- public
only:
- master
- pages

This includes some caching for our node_modules directory so we don’t have to build it every time we do a deploy. I’ve also made it so that you can deploy a pages branch rather than only having master able to affect the resulting page.

Improvements from here include:

  • Currently, the ng build step doesn’t specify production mode, or any of the additional optimizations it’s capable of. For me, that’s because my tool is essentially in public beta and if there are errors I want people to be able to report them.
  • GitLab pages has two options for where it hosts your content. You can host it at a URL like https://yourusername.gitlab.io/yourproject or you can host it at https://yourproject.gitlab.io if you jump through the appropriate hoops. I’ve done the latter, but if you do the former you’ll need to tell Angular that it has a different base using the base-href option.