# install.packages('pak')
::pak('mjfrigaard/shinypak') pak
22 pkgdown
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.
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
::use_pkgdown_github_pages() usethis
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'
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:
build_site()
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.
::build_site_github_pages() pkgdown
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).
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:
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
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 toarticles/specs.html
:
In _pkgdown.yml
text: Docs
menu:
- text: "Specs"
- text: App Specifications
href: articles/specs.html
Output
Use -------
with a text
field to create horizontal separators between sections (without a corresponding href
).
- I’ve added a
Features
section and aApplication Features
vignette (stored invignettes/features.Rmd
and published toarticles/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
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
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
push
es or pull_request
s 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. group
11 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:
The workflow
file triggers the following steps:
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… 🔧
- setting up a safe directory (
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
worktree
13 is created for thegh-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 thegh-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 worktree is prepared in a
The website content generated by
pkgdown
in thedocs
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… 📦
- The content is updated to reflect the latest version of the website associated the
After successfully pushing the changes to the
gh-pages
branch, the workflow performscleanup 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
- reset the branch pointer, and
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:
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
usethis
also has a generic function for usingpkgdown
(use_pkgdown()
), but we’re going to cover building and deploying our app-package site using GitHub pages. Read more aboutuse_pkgdown()
in theusethis
documentation.↩︎Internally, this function calls
usethis::use_pkgdown()
,usethis::use_github_pages()
, andusethis::use_github_action("pkgdown")
. Read more in theusethis
documentation.↩︎I created this vignette with
usethis::use_vignette("sap")
and included instructions for launching the various apps insap
.↩︎Read more about
bslib
inpkgdown
sites in the documentation.↩︎Functions with
@keywords internal
aren’t listed in the package index, but can be accessed withpkg:::fun()
(like thetest_logger()
function insap
).↩︎Using the
mod_
as a prefix for module functions is a habit I’ve adopted from thegolem
package (specifically, theadd_module()
function).↩︎“You can create releases to bundle and deliver iterations of a project to users.”↩︎
“To enable a workflow to be triggered manually, you need to configure the
workflow_dispatch
event.”↩︎“Use concurrency to ensure that only a single job or workflow using the same concurrency group will run at a time.”↩︎
“Concurrency groups provide a way to manage and limit the execution of workflow runs or jobs that share the same concurrency key.”↩︎
The
needs: website
fields might be a placeholder? I’m unaware of theneeds
keyword applied within awith
clause for setting up dependencies. This also could be intended as a comment or note for future adjustments…↩︎“A git repository can support multiple working trees, allowing you to check out more than one branch at a time.”↩︎
“
chmod
changes the permissions of each given file according to mode, where mode describes the permissions to modify.”↩︎