22  pkgdown

Published

2024-09-11

The pkgdown package helps us easily create websites to enhance the visibility and usability of our app-package through a professional and informative website.

  • Install pkgdown

  • Run usethis::use_pkgdown_github_pages() to set up the package and writes the configuration file (_pkgdown.yml)

  • Customize _pkgdown.yml as needed, then

  • Run pkgdown::build_site_github_pages() to build your site

  • Push the changes to deploy it online for others to access!


In this chapter, we’ll cover setting up a pkgdown website for our app-package. Building a package website isn’t required, but it’s a great way to confirm your package is documented and structured correctly, and it gives you an opportunity to share all of your hard work! pkgdown can be configured to automatically generate a beautiful website from a pre-specified Git branch GitHub Actions.

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section:

library(shinypak)
list_apps(regex = 'pkgdown')
## # A tibble: 1 × 2
##   branch     last_updated       
##   <chr>      <dttm>             
## 1 17_pkgdown 2024-08-23 07:16:31

Launch an app:

launch(app = "17_pkgdown")

A pkgdown website makes our Shiny app and its accompanying package more accessible to potential users by providing them with a central location for any information they need (app features, updates, etc.).

22.1 Setting up pkgdown

The magic of pkgdown is it’s conversion of an existing R package structure into a website with documentation for our application.

install.packages("pkgdown")

pkgdown has a usethis function similar to testthat for setup:1

usethis::use_pkgdown_github_pages()

use_pkgdown_github_pages() takes care of (most of) the setup for our app-package website, but we’ll break down the steps below.2 I’ve replaced my GitHub username with <username> and the name of the app-package/repository with <pkgName>):

22.1.1 _pkgdown.yml

The initial output after running use_pkgdown_github_pages() looks something like the following:

 Setting active project to '/Users/<username>/projects/<pkgName>'
 Adding '^_pkgdown\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore'
 Adding 'docs' to '.gitignore'
 Writing '_pkgdown.yml'
 Modify '_pkgdown.yml'
 Recording 'https://<username>.github.io/<pkgName>/' as site's url in '_pkgdown.yml'
✔ Adding 'https://<username>.github.io/<pkgName>/' to URL
✔ Setting 'https://<username>.github.io/<pkgName>/' as homepage of GitHub repo '<username>/<pkgName>'

_pkgdown.yml is initially created with only the url, template, and bootstrap version:

url: https://<username>.github.io/<pkgName>/
template:
  bootstrap: 5

These fields are all that’s required to launch your pkgdown site, but in the following sections we’ll cover how to edit _pkgdown.yml to customize the fonts, colors, contents, and layout of our site.

22.1.2 gh-pages branch

use_pkgdown_github_pages() sets up publishing our app-package site from an ‘orphan branch from GitHub pages’:

 Initializing empty, orphan 'gh-pages' branch in GitHub repo '<username>/<pkgName>'
 GitHub Pages is publishing from:
 URL: 'https://<username>.github.io/<pkgName>/'
 Branch: 'gh-pages'

An orphan branch is a new Git branch with no commit history, effectively starting a new ‘root’ in our project’s development history. For our app-package, the gh-pages branch serves as a new line of development, completely separated from all other branches.

We’re also told GitHub pages will be publishing our app-package website at the following URL: https://<username>.github.io/<pkgName>/

22.1.3 .github/workflows/

use_pkgdown_github_pages() creates a GitHub Action workflow folder (.github/workflows/) with a YAML file (pkgdown.yaml):

 Path: '/'
 Creating '.github/'
 Adding '^\\.github$' to '.Rbuildignore'
 Adding '*.html' to '.github/.gitignore'
 Creating '.github/workflows/'
 Saving 'r-lib/actions/examples/pkgdown.yaml@v2' to '.github/workflows/pkgdown.yaml'
 Learn more at <https://github.com/r-lib/actions/blob/v2/examples/README.md>.

We’re also told the contents in this file are copied from the r-lib/actions repository (which we’ve covered previously in Section 21.1 and Section 21.2).

22.2 Building site

usethis has two functions for building your pkgdown site:

  1. build_site()
  2. build_site_github_pages()

We used use_pkgdown_github_pages() to configure our app-package, so we’ll use build_site_github_pages() to build our site.

pkgdown::build_site_github_pages()

In the following sections, we’ll take a look at how the files and folders in our app-package are used to create the site’s contents. As mentioned above, the great thing about pkgdown sites is that they use our existing package structure to build a beautiful site that’s easy to navigate (with minimal changes).

22.2.1 docs/

The docs/ folder contains the .html files for our website (that’s why ^docs$ was added to the .Rbuildignore and docs was added to the .gitignore). After creating a home for our site contents, the site initialization files are copied from the local pkgdown installation into docs/

== Building pkgdown site ======================================================
Reading from: '/Users/<username>/<pkgName>'
Writing to:   '/Users/<username>/<pkgName>/docs'

22.2.2 README.md -> index.html

The landing page (index.html) for our app-package website is built from the README.md file. An example of the site URL is below:

https://<username>.github.io/<pkgName>/index.html

The <username> is our GitHub username, and the <pkgName> is the name of our package. The authors.html is built from the Author and Maintainer fields in the DESCRIPTION file. Long-form documentation can be stored in vignettes (Section 1.14.3) which will be converted into articles (covered below).

If you’d like a boilerplate README.md for an R package, you can use usethis::use_readme_rmd():

usethis::use_readme_rmd()
 Setting active project to '/Users/<username>/projects/<pkgName>'
 Writing 'README.Rmd'
 Adding '^README\.Rmd$' to '.Rbuildignore'
 Modify 'README.Rmd'
 Writing '.git/hooks/pre-commit'

At minimum, the README.Rmd should include:

  1. The purpose/goal of your app-package
  2. Instructions for installation
  3. Links to a deployed version (if applicable)
  4. Supporting packages

If I chose to use this README.md file, I usually remove the .git/hooks/pre-commit (so they don’t interfere with my personal add/commit/push process).

unlink('.git/hooks/pre-commit')

22.2.3 man/ -> Reference

The functions documented in the man/ folder are converted into individual items in the Reference menu item (see Chapter 5). I’ve included two examples below:

man/
  ├── display_type.Rd
  └── ggp2_launch_app.Rd
-- Building function reference ---------
Writing 'reference/index.html'
Reading 'man/display_type.Rd'
Writing 'reference/display_type.html'
Reading 'man/ggp2_launch_app.Rd'
Writing 'reference/ggp2_launch_app.html'
  • Functions will only be included in the Reference if they’ve been exported (see Section 6.1)

  • If we’ve been thorough in documenting, the @seealso and @family tags will create hyperlinks between our utility functions, modules, UI/server/standalone app functions (see Section 5.2)

  • The @examples will be run and displayed (Section 5.1.4)

22.2.4 vignettes -> Articles

Any of the .Rmd in the vignettes folder will be rendered as HTML articles under the Articles menu item. The exception to this is any vignettes with the same name as our app-package (which will automatically be listed under a menu dropdown titled “Get Started”):3

vignettes/
  ├── sap.Rmd
  └── specs.Rmd
-- Building articles -------------
Writing 'articles/index.html'
Reading 'vignettes/sap.Rmd'
Writing 'articles/sap.html'
Reading 'vignettes/specs.Rmd'
Writing 'articles/specs.html'
== DONE ==========================

The final step in the build process is to add a .nojekyll file in the repository (this hidden file is necessary for pkgdown sites configured to deploy from GitHub pages).

-- Extra files for GitHub pages ----------------------------------------------
Writing '.nojekyll'

22.3 Customize site layout

We can customize the look of our pkgdown site by editing the contents of _pkgdown.yml.

22.3.1 Themes, colors and fonts

Below are some examples of the fields that control the bootswatch theme (<THEME>), code syntax highlighting(<HIGHLIGHTING>):

YAML Fields
template:
  bootstrap: 5
  bootswatch: <THEME>
  theme: <HIGHLIGHTING>
In _pkgdown.yml
template:
  bootstrap: 5
  bootswatch: united
  theme: atom-one-light

We can use the bslib package for additional control over the fonts and colors on our site. The <COLOR> should be replaced with a color hex, and <FONT> can include any freely available Google fonts.4

YAML Fields
  bslib:
    primary: "<COLOR>"
    code-color: "<COLOR>"
    code-bg: "<COLOR>"
    base_font:
      google: <FONT>
    heading_font:
      google: <FONT>
    code_font:
      google: <FONT>
In _pkgdown.yml
  bslib:
    primary: "#007987"
    secondary: "#f5feff"
    base_font:
      google: Ubuntu
    heading_font:
      google: Fira Sans Condensed
    code_font:
      google: Inconsolata

You can see the theme, color, and font choices below:

pkgdown template in yml

pkgdown template in yml

22.3.2 Articles

The articles should include a link to the landing page for our app, and provide detailed examples of how it works, including links to any additional resources or documentation.

The navbar components can also be customized with titles, sections, and article names. In _pkgdown.yml, the articles are listed under components, and we will add a text title (Docs) and sub-heading (Specs):

In _pkgdown.yml
navbar:
 components:
   articles:
    text: Docs
    menu:
    - text: "Specs"
Output

Navbar components

Navbar components

Any vignette with a filename that matches the package name is automatically named ‘Getting Started’ in the navbar. We can also add sections with article titles by placing them in text fields. These are listed under the menu (note the indentation), with a path to the html file.

  • Below I’ve listed the App Specifications vignette under a "Specs" section (see Chapter 15), and linked to articles/specs.html:
In _pkgdown.yml
    text: Docs
    menu:
    - text: "Specs"
    - text: App Specifications
      href: articles/specs.html
Output

Article sections

Article sections

Use ------- with a text field to create horizontal separators between sections (without a corresponding href).

  • I’ve added a Features section and a Application Features vignette (stored in vignettes/features.Rmd and published to articles/features.html):
In _pkgdown.yml
    text: Docs
    menu:
    - text: "Specs"
    - text: App Specifications
      href: articles/specs.html
    - text: -------
    - text: "Features"
    - text: App Features
      href: articles/features.html
Output

Articles separator

Articles separator

22.3.3 Function reference

pkgdown will automatically generate a Package index section for any object with an .Rd file in the man/ folder. This includes functions we’ve explicitly exported with (i.e., with @export) and functions we’ve documented with @keywords internal.5

By default, the function are sorted alphabetically, but we can customize them into sections with titles and descriptions using the fields below in _pkgdown.yml:

reference:
- title: "<TITLE>"
  desc: >
    <DESCRIPTION>
  contents:
  - <FUNCTION>

For example, we can include a section for the modules we’re exporting from our app-package:

In _pkgdown.yml
reference:
- title: "Modules"
  desc: >
    App modules
Output

The _pkgdown.yml file must include all exported functions if you customize the reference field. If not, you’ll see an error when you try to build your site:

-- Building function reference ------------------------------------
Error in `check_missing_topics()`:
! All topics must be included in reference index
 Missing topics: <FUN>
 Either add to _pkgdown.yml or use @keywords internal

To help organize and display the functions in your app-package, we can use tidyselect helpers6 in the bullets below contents.

For example, we can list modules with starts_with("mod"):7

In _pkgdown.yml
reference:
- title: "Modules"
  desc: >
    Application modules
  contents:
  - starts_with("mod")
Output

We can also use _pkgdown.yml to list any datasets we’ve documented (see Section 7.2) in our app-package.

In _pkgdown.yml
- title: "Data"
  desc: "App data"
  contents:
  - movies
Output

22.4 Deploying your site

The .github/workflows/pkgdown.yaml file automates building and deploying our app-package’s pkgdown site. This workflow file is configured to be triggered by specific GitHub events, build the website using the standard package files, then deploys it to GitHub Pages. Below we’ll breakdown the fields and values of the workflow (and their functions):

22.4.1 Triggers

1. Set to trigger on pushes or pull_requests made to the main or master branches (we’ll change these to only trigger on the 17_pkgdown branch).

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

 

a. Also triggers when a release is published, allowing the website to showcase the latest version of the package.8

  release:
    types: [published]

 

b. workflow_dispatch allows the workflow to be manually triggered from the GitHub Actions web interface (for ad-hoc updates).9

  workflow_dispatch:

22.4.2 Jobs

2. name defines a single job with the ID pkgdown.

name: pkgdown

3. jobs specifies the job ID (pkgdown) and runs the job on the latest Ubuntu runner provided by GitHub Actions.

jobs:
  pkgdown:
    runs-on: ubuntu-latest

4. The comment # Only restrict concurrency for non-PR jobs refers to the concurrency field,10 which prevents concurrent runs of the job for non-pull request events (avoiding conflicts or redundant deployments). a. group11 uses a dynamic expression to differentiate between pull_request events and github.event_name, using the run ID (github.run_id) for pull requests to allow concurrency.

    concurrency:
      group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }}

5. env sets the GITHUB_PAT environment variable using the GitHub token, secrets.GITHUB_TOKEN (which allows the workflow to authenticate and perform operations within the repository).

    env:
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

6. permissions explicitly grants the workflow write permissions to the repository, enabling it to push changes (like an updated website).

    permissions:
      contents: write

22.4.3 Steps

7. Checks out the repository’s code, making it available to subsequent steps.

  steps:
    - uses: actions/checkout@v4

8. Installs pandoc, which is necessary for rendering markdown documents and vignettes.

    - uses: r-lib/actions/setup-pandoc@v2

9. Sets up the R environment and configures it to use the public RStudio package manager (use-public-rspm) for faster package installations.

    - uses: r-lib/actions/setup-r@v2
      with:
        use-public-rspm: true

10. Installs the dependencies required by our app-package and pkgdown.

    - uses: r-lib/actions/setup-r-dependencies@v2

 

a. Specifies installing any::thing from pkgdown and the local:: package (our app-package).12

      with:
        extra-packages: any::pkgdown, local::.
        needs: website

11. Executes pkgdown::build_site_github_pages() within an R script shell to build the pkgdown website. It’s configured not to start a new R process for the build and not to install our app-package (assuming dependencies are already handled in step 10).

    - name: Build site
      run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
      shell: Rscript {0}

12. Uses JamesIves/github-pages-deploy-action@v4.5.0 to deploy the site to GitHub Pages (provided the event is not a pull request).

    - name: Deploy to GitHub pages 🚀
      if: github.event_name != 'pull_request'
      uses: JamesIves/github-pages-deploy-action@v4.5.0

 

a. Specifies not to clean the deployment branch, deploys it to the gh-pages branch, and sets the site content source folder to docs

      with:
        clean: false
        branch: gh-pages
        folder: docs

.github/workflows/pkgdown.yaml performs more operations than the previous workflows. However, these extra steps allow us to use the gh-pages branch to maintain and showcase up-to-date package documentation for users, contributors, and collaborators. When we’re happy with the layout of our website in _pkgdown.yml, we can add, commit, and push the changes back to the repo:

git add .
git commit -m "updates to _pkgdown.yml"
git push

22.5 GitHub

Back in GitHub, we can see our previous Style (Section 21.1), shiny (Section 21.2), and docker-shiny, moviesapp (Section 21.3) workflows:

Launch app with the shinypak package:

launch('17_pkgdown')

After pushing the changes to the 17_pkgdown branch, a new workflow is created and run in the Actions tab:

Workflow created on push to 17_pkgdown branch

Workflow created on push to 17_pkgdown branch

The workflow file triggers the following steps:

GitHub Pages Deploy Action

GitHub Pages Deploy Action
  • The process begins with checking the necessary configurations for deployment and using the Deploy Token… 🔑 for authentication.

    Checking configuration and starting deployment… 🚦
    Deploying using Deploy Token… 🔑
  • Git is set up and configured (similar to a new user) with user credentials (<username> and <username>@pm.me), but also includes:

    • setting up a safe directory (--add safe.directory),
    • ignoring case (config core.ignorecase false),
    • removing any extra headers (http.https://github.com/.extraheader), and
    • adding remote origin to point to the GitHub repository where the sap package resides (remote add origin).
    Configuring git…
    /usr/bin/git config --global --add safe.directory /home/runner/work/<pkgName>/<pkgName>
    /usr/bin/git config user.name <username>
    /usr/bin/git config user.email <username>@pm.me
    /usr/bin/git config core.ignorecase false
    /usr/bin/git config --local --unset-all http.https://github.com/.extraheader
    /usr/bin/git remote rm origin
    /usr/bin/git remote add origin ***github.com/<username>/<pkgName>.git
    Git configured… 🔧
  • The workflow checks for an existing gh-pages branch in the repository (which is the branch we’ve configured to serve the website content).

    Starting to commit changes…
    /usr/bin/git ls-remote --heads ***github.com/<username>/<pkgName>.git refs/heads/gh-pages
    7cac1013e4a324d943d2b33ed6d52f0cf6b243a6    refs/heads/gh-pages
  • A Git worktree13 is created for the gh-pages branch, which allows multiple branches within the same repository (without having to clone the repository again).

    • The worktree is prepared in a detached HEAD state, and then the gh-pages branch is checked out:
    Creating worktree…
    /usr/bin/git fetch --no-recurse-submodules --depth=1 origin gh-pages
    From https://github.com/<username>/<pkgName>
     * branch            gh-pages   -> FETCH_HEAD
     * [new branch]      gh-pages   -> origin/gh-pages
    /usr/bin/git worktree add --no-checkout --detach github-pages-deploy-action-temp-deployment-folder
    Preparing worktree (detached HEAD 742bb18)
    /usr/bin/git checkout -B gh-pages origin/gh-pages
  • The website content generated by pkgdown in the docs directory is copied into the worktree directory, ensuring that all necessary files are updated with the latest changes:

    • chmod is used to ‘change access permissions’14
    • -R applies the permission changes to the directory specified and to all of the subdirectories and files
    • +rw adds both read and write permissions for the user, group, and others to the files/directories targeted by this command
    /usr/bin/chmod -R +rw /home/runner/work/<pkgName>/<pkgName>/docs
    /usr/bin/rsync -q -av --checksum --progress /home/runner/work/<pkgName>/<pkgName>/docs/. github-pages-deploy-action-temp-deployment-folder --exclude .ssh --exclude .git --exclude .github
  • The changes are added, committed, and then force-pushed to the gh-pages branch:

    • The content is updated to reflect the latest version of the website associated the sap R package
    • The commit message includes a deployment tag and the hash of the commit from the deployment
    Checking if there are files to commit…
    /usr/bin/git add --all .
    /usr/bin/git checkout -b github-pages-deploy-action/kpvxdgnej
    Switched to a new branch 'github-pages-deploy-action/kpvxdgnej'
    Force-pushing changes...
    /usr/bin/git push --force ***github.com/<username>/<pkgName>.git github-pages-deploy-action/kpvxdgnej:gh-pages
    To https://github.com/<username>/<pkgName>.git
       7cac101..aab5105  github-pages-deploy-action/kpvxdgnej -> gh-pages
    Changes committed to the gh-pages branch… 📦
  • After successfully pushing the changes to the gh-pages branch, the workflow performs cleanup jobs:

    • reset the branch pointer, and
    • remove the temporary worktree directory
    Running post deployment cleanup jobs… 🗑️
    /usr/bin/git checkout -B github-pages-deploy-action/kpvxdgnej
    Reset branch 'github-pages-deploy-action/kpvxdgnej'
    /usr/bin/chmod -R +rw github-pages-deploy-action-temp-deployment-folder
    /usr/bin/git worktree remove github-pages-deploy-action-temp-deployment-folder --force
  • The workflow finishes and signals the deployment has been successfully completed.

    Completed deployment successfully! ✅

Back in the Actions tab, we see a new pages build and deployment workflow has been created with a ‘bot’ tag:

The updates to _pkgdown.yml workflow represents the changes we committed and pushed to the 17_pkgdown branch (#10), but we configured our website to be served from the gh-pages branch (#11). Each time we push changes to 17_pkgdown and trigger the pkgdown workflow, a corresponding pages build and deployment workflow will be triggered to build and deploy the site:

Build and deploy pkgdown site

Build and deploy pkgdown site

This automated deployment process is essential for maintaining up-to-date documentation or website content for R packages (like sap) without manual intervention, making it easier for developers to focus on development while ensuring that users always have access to the latest information.

View the package website here.

Recap

Recap:


pkgdown is handy for creating beautiful, functional websites for your app-package. Package sites help share your app-package with others in a more engaging and informative way.

  • Installation

    install.packages('pkgdown)`
  • Setup: create a configuration file that pkgdown will use to build your site with one of the usethis functions below:

    usethis::use_pkgdown_github_pages()
    # or
    # usethis::use_pkgdown() 
    • These create a _pkgdown.yml configuration file that lets us customize how our site looks and which parts of our app-package are diplayed/highlighted.
  • Customize: we can change our pkgdown site theme, set colors and fonts, organize the navigation bar, and add custom sections and pages. For an app-package, this means we can create a landing page for our Shiny app, provide detailed articles on how it works, and link to any additional resources or documentation.

  • Building Your Site

    pkgdown::build_site_github_pages()
    # or 
    # pkgdown::build_site()
  • build_site_github_pages() goes through our app-package (function documentation, and examples, RMarkdown vignettes, README, NEWS, etc.) and assembles a coherent, navigable site.

  • Deploying Your Site: after building a pkgdown site, we can use GitHub Actions to upload the generated website contents to GitHub Pages, which hosts our app-package site directly from a GitHub repository.


  1. usethis also has a generic function for using pkgdown (use_pkgdown()), but we’re going to cover building and deploying our app-package site using GitHub pages. Read more about use_pkgdown() in the usethis documentation.↩︎

  2. Internally, this function calls usethis::use_pkgdown(), usethis::use_github_pages(), and usethis::use_github_action("pkgdown"). Read more in the usethis documentation.↩︎

  3. I created this vignette with usethis::use_vignette("sap") and included instructions for launching the various apps in sap.↩︎

  4. Read more about bslib in pkgdown sites in the documentation.↩︎

  5. Functions with @keywords internal aren’t listed in the package index, but can be accessed with pkg:::fun() (like the test_logger() function in sap).↩︎

  6. Read more about how to build the function reference here↩︎

  7. Using the mod_ as a prefix for module functions is a habit I’ve adopted from the golem package (specifically, the add_module() function).↩︎

  8. “You can create releases to bundle and deliver iterations of a project to users.”↩︎

  9. “To enable a workflow to be triggered manually, you need to configure the workflow_dispatch event.”↩︎

  10. “Use concurrency to ensure that only a single job or workflow using the same concurrency group will run at a time.”↩︎

  11. “Concurrency groups provide a way to manage and limit the execution of workflow runs or jobs that share the same concurrency key.”↩︎

  12. The needs: website fields might be a placeholder? I’m unaware of the needs keyword applied within a with clause for setting up dependencies. This also could be intended as a comment or note for future adjustments…↩︎

  13. “A git repository can support multiple working trees, allowing you to check out more than one branch at a time.”↩︎

  14. chmod changes the permissions of each given file according to mode, where mode describes the permissions to modify.”↩︎