Build static HTML spaces

Community Article Published July 30, 2024

image/png

Frontend libraries like Svelte, React, or Observable Framework generate static webapps, but require a build step.

You generally have the following options to create a static HTML space.

  • You can host the code on GitHub and deploy it through a GitHub action, or you can build and deploy it from your local machine.
  • Alternatively, you can host the website in a Docker space, as I did for severo/followgraph. The Dockerfile builds the website on every push to the repository; then, the container serves the updated site at runtime. But it's not optimal, and the space will be stopped automatically after a time, which would not be the case with a static HTML space.

Here, we propose a hack with two spaces:

  • the code space: a Docker space that hosts the application source code, builds it and deploys it, and
  • the site space: a static HTML space that hosts the built website.

In this tutorial, we deploy the default Svelte demo page.

Thanks to @XciD for the idea and optimizations to the Dockerfile. Thanks to @Wauplin for the review and corrections 🙏.

Site space

Let's start by creating the site space that will host the built website. Go to https://huggingface.co/new-space. We call the space "svelte-demo" and select the "Static" SDK and the "Blank" Static template.

image/png

The space contains a README.md and default HTML and CSS files. See the initial state at: https://huggingface.co/spaces/severo/svelte-demo/tree/777cd50e4088a5deed54117d1ae6b561948db994

image/png

We don't need to work on this space anymore. It will be fully controlled from code space.

Code space

Let's now create another space that will contain the website's source code and the Dockerfile for building and deploying it.

Create the code space

We go to https://huggingface.co/new-space, call the space "build-svelte-demo," select the "Docker" SDK and then the "Blank" Docker template.

image/png

Clone the repository locally

We work locally by cloning this space to our machine:

$ git clone git@hf.co:spaces/severo/build-svelte-demo
$ cd build-svelte-demo

Create the Svelte website

As an example, let's create the Svelte demo page. We call vite and select Svelte as the framework with the Typescript variant.

$ npm init vite
Need to install the following packages:
create-vite@5.4.0
Ok to proceed? (y) y


> npx
> create-vite

✔ Project name: … svelte-demo
✔ Select a framework: › Svelte
✔ Select a variant: › TypeScript

Scaffolding project in /home/slesage/hf/build-svelte-demo/svelte-demo...

Done. Now run:

  cd svelte-demo
  npm install
  npm run dev

You can also use Vite to scaffold projects for vanilla javascript, other libraries such as Vue or React, or use a community template.

Ensure the website works locally

Install the packages and start the development web server:

$ cd svelte-demo
$ npm install
$ npm run dev

Open the browser at http://localhost:5173. You should see the default Svelte page.

Create the Dockerfile

Copy the following code to a new file called Dockerfile at the repository's root.

FROM ubuntu:22.04

# Install Node.js 22 - https://github.com/nodesource/distributions?tab=readme-ov-file#installation-instructions-deb
# And python3
RUN apt update \
    && apt install -y curl python3 python3-pip \
    && rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh \
    && bash nodesource_setup.sh \
    && apt update \
    && apt install -y nodejs \
    && rm -rf /var/lib/apt/lists/* \
    && rm nodesource_setup.sh

RUN pip install --upgrade "huggingface_hub[cli]"

# Build the app
WORKDIR /tmp/app
COPY svelte-demo/ ./
RUN npm ci && npm rebuild && npm run build

# The site space name must be passed as an environment variable
# https://huggingface.co/docs/hub/spaces-sdks-docker#buildtime
ARG STATIC_SPACE
# The Hugging Face token must be passed as a secret (https://huggingface.co/docs/hub/spaces-sdks-docker#buildtime)
# 1. get README.md from the site space
RUN --mount=type=secret,id=HF_TOKEN,mode=0444,required=true \
    huggingface-cli download --token=$(cat /run/secrets/HF_TOKEN) --repo-type=space --local-dir=/tmp/app/dist $STATIC_SPACE README.md && rm -rf /tmp/app/dist/.cache
# 2. upload the new build to the site space, including README.md
RUN --mount=type=secret,id=HF_TOKEN,mode=0444,required=true \
    huggingface-cli upload --token=$(cat /run/secrets/HF_TOKEN) --repo-type=space $STATIC_SPACE /tmp/app/dist . --delete "*"

# Halt execution because the code space is not meant to run.
RUN exit 1

Some explanations:

  • We use a generic image (Ubuntu) to install any tool we need. In this case, we use node and python, but you can easily adapt the docker file if you need to build using other tools.
  • The first block installs Node.js, Python 3, and the huggingface_hub CLI.
  • The next block installs the npm packages and builds the website to /tmp/app/dist.
  • We then declare that the STATIC_SPACE environment variable is required. It must be set to the repository name of the site space. More on that later.
  • The last block downloads the README.md file to the locally built app and then uploads everything to replace the current contents. Note that we pass a space secret called HF_TOKEN (see the following sections).
  • The last line (RUN exit 1) is a hack: we return an error code to prevent the code space from running.

Prepare the STATIC_SPACE environment variable

Enter the code space settings, and set a variable called STATIC_SPACE with the site space repository name: severo/svelte-demo:

image/png

Prepare the HF_TOKEN secret

Create a new token in your settings: https://huggingface.co/settings/tokens:

  • Fine-grained
  • Token name: space-build-svelte-demo
  • Find the site space svelte-demo under Repositories permissions, and check Write access to contents/settings of selected repos
  • Create token

image/png

Then copy the value and paste it in a new secret called HF_TOKEN in the code space repository settings.

image/png

We now have one environment variable and one secret:

image/png

Commit and push

The last step is to commit your changes and push them to the code space on Hugging Face:

$ git add .
$ git commit -m "add Dockerfile and the app"
$ git push

Look at the building logs

Once pushed, the code space will be built automatically. You can see the logs at https://huggingface.co/spaces/severo/build-svelte-demo?logs=build

image/png

The last lines should show that the files have been uploaded to the site space, and then that the build fails, on purpose, as explained above:

--> RUN --mount=type=secret,id=HF_TOKEN,mode=0444,required=true huggingface-cli upload --token=$(cat /run/secrets/HF_TOKEN) --repo-type=space severo/svelte-demo /tmp/space . --delete "*"
Consider using `hf_********` for faster uploads. This solution comes with some limitations. See https://huggingface.co/docs/huggingface_hub/hf_******** for more details.
Removing 5 file(s) from commit that have not changed.
No files have been modified since last commit. Skipping to prevent empty commit.
https://huggingface.co/spaces/severo/svelte-demo/tree/main/.
DONE 1.1s

--> RUN exit 1

--> ERROR: process "/bin/sh -c exit 1" did not complete successfully: exit code: 1

Conclusion

The website is now deployed on the site space: https://huggingface.co/spaces/severo/svelte-demo

image/png

Let's recap the logic:

  • create an empty static HTML space: the site space
  • host your Svelte/React/Vue app in another Docker space: the code space
  • add a Dockerfile to the code space with the commands to build and deploy to the site space
  • any change pushed to the code space will upload the site space.

Side-effect: the code space can remain private while the site space is public.

Now it's your turn. Share your creations!