diff --git a/man.Rmd b/man.Rmd index 612a8ba48..921accdcd 100644 --- a/man.Rmd +++ b/man.Rmd @@ -247,11 +247,13 @@ Example: #' or even our own [olderfunction()]. ``` -**Vignettes**: If you refer to a vignette with an inline call to `vignette()`, it serves a dual purpose. +**Vignettes**: If you refer to a vignette with an inline call to `vignette("some-topic")`, it serves a dual purpose. First, this is literally the R code you would execute to view a vignette locally. But wait there's more! In many rendered contexts, this automatically becomes a hyperlink to that same vignette in roxygen2's pkgdown website. -Here we use that to link to some very relevant vignettes: +Here we use that to link to some very relevant vignettes[^man-7]: + +[^man-7]: These calls include an explicit specification of `package = "somepackage"`, since it can't be inferred from context, i.e. the context is a Quarto book, not package documentation. - `vignette("rd-formatting", package = "roxygen2")` @@ -304,9 +306,9 @@ But they provide a useful negative case study: There's a lot of repetition ("pattern", "from a string") and the verb used for the function name is repeated in the title, so if you don't understand the function already, the title seems unlikely to help much. Hopefully we'll have improved those titles by the time you read this! -In contrast, these titles from dplyr are much better[^man-7]: +In contrast, these titles from dplyr are much better[^man-8]: -[^man-7]: Like all the examples, these might have changed a bit since we wrote this book, because we're constantly striving to do better. +[^man-8]: Like all the examples, these might have changed a bit since we wrote this book, because we're constantly striving to do better. You might compare what's in the book to what we now use, and consider if you think if it's an improvement. - `mutate()`: Create, modify, and delete columns @@ -375,10 +377,10 @@ Once you've re-derived what the function does, you'll be able to write a better The `@details` are just any additional details or explanation that you think your function needs. Most functions don't need details, but some functions need a lot. -If you have a lot of information to convey, it's a good idea to use informative markdown headings to break the details up into manageable sections[^man-8]. +If you have a lot of information to convey, it's a good idea to use informative markdown headings to break the details up into manageable sections[^man-9]. Here's an example from `dplyr::mutate()`. We've elided some of the details to keep this example short, but you should still get a sense of how we used headings to break up the content in to skimmable chunks: -[^man-8]: In older code, you might see the use of `@section title:` which was used to create sections before roxygen2 had full markdown support. +[^man-9]: In older code, you might see the use of `@section title:` which was used to create sections before roxygen2 had full markdown support. If you've used these in the past, you can now turn them into markdown headings. ```{r} @@ -533,11 +535,11 @@ Beware of spurious diffs introduced by contributors who run `devtools::document( ## Return value A function's output is as important as its inputs. -Documenting the output is the job of the `@returns`[^man-9] tag. +Documenting the output is the job of the `@returns`[^man-10] tag. Here the priority is to describe the overall "shape" of the output, i.e. what sort of object it is, and its dimensions (if that makes sense). For example, if your function returns a vector you might describe its type and length, or if your function returns a data frame you might describe the names and types of the columns and the expected number of rows. -[^man-9]: For historical reasons, you can also use `@return`, but we now favor `@returns` because it reads more naturally. +[^man-10]: For historical reasons, you can also use `@return`, but we now favor `@returns` because it reads more naturally. The `@returns` documentation for functions in stringr is straightforward because almost all functions return some type of vector with the same length as one of the inputs. For example, here's how `str_like()` describes its output: @@ -546,10 +548,10 @@ For example, here's how `str_like()` describes its output: #' @returns A logical vector the same length as `string`. ``` -A more complicated case is the joint documentation for `str_locate()` and `str_locate_all()`[^man-10]. +A more complicated case is the joint documentation for `str_locate()` and `str_locate_all()`[^man-11]. `str_locate()` returns an integer matrix, and `str_locate_all()` returns a list of matrices, so the text needs to describe what determines the rows and columns. -[^man-10]: We'll come back how to document multiple functions in one topic in @sec-man-multiple-functions. +[^man-11]: We'll come back how to document multiple functions in one topic in @sec-man-multiple-functions. ```{r} #' @returns @@ -733,7 +735,7 @@ There are two basic options: #' try(bind_cols(tibble(x = 1:3), tibble(y = 1:2))) ``` -- You can wrap the code in `\dontrun{}`[^man-11], so it is never run by `example()`. The example above would look like this if you used `\dontrun{}` instead of `try()`. +- You can wrap the code in `\dontrun{}`[^man-12], so it is never run by `example()`. The example above would look like this if you used `\dontrun{}` instead of `try()`. ```{r} #' # Row sizes must be compatible when column-binding @@ -742,7 +744,7 @@ There are two basic options: #' } ``` -[^man-11]: You used to be able to use `\donttest{}` for a similar purpose, but we no longer recommend it because CRAN sets a special flag that causes the code to be executed anyway. +[^man-12]: You used to be able to use `\donttest{}` for a similar purpose, but we no longer recommend it because CRAN sets a special flag that causes the code to be executed anyway. We generally recommend using `try()` so that the reader can see an example of the error in action. @@ -753,7 +755,7 @@ For the initial CRAN submission of your package, all functions must have at leas If the code can only be run under specific conditions, use the techniques below to express those pre-conditions. ::: -### Dependencies and conditional execution +### Dependencies and conditional execution {#sec-man-examples-dependencies-conditional-execution} An additional source of errors in examples is the use of external dependencies: you can only use packages in your examples that your package formally depends on (i.e. that appear in `Imports` or `Suggests`). Furthermore, example code is run in the user's environment, not the package environment, so you'll have to either explicitly attach the dependency with `library()` or refer to each function with `::`. @@ -779,10 +781,10 @@ In the past, we recommended only using code from suggested packages inside a blo We no longer believe that approach is a good idea, because: -- Our policy is to expect that suggested packages are installed when running `R CMD check`[^man-12] and this informs what we do in examples, tests, and vignettes. +- Our policy is to expect that suggested packages are installed when running `R CMD check`[^man-13] and this informs what we do in examples, tests, and vignettes. - The cost of putting example code inside `{ ... }` is high: you can no longer see intermediate results, such as when the examples are rendered in the package's website. The cost of a package not being installed is low: users can usually recognize the associated error and resolve it themselves, i.e. by installing the missing package. -[^man-12]: This is certainly true for CRAN and is true in most other automated checking scenarios, such as our GitHub Actions workflows. +[^man-13]: This is certainly true for CRAN and is true in most other automated checking scenarios, such as our GitHub Actions workflows. In other cases, your example code may depend on something other than a package. For example, if your examples talk to a web API, you probably only want to run them for an authenticated user, and never want such code to run on CRAN. diff --git a/structure.Rmd b/structure.Rmd index 78bdbe2fa..62a23ab56 100644 --- a/structure.Rmd +++ b/structure.Rmd @@ -59,7 +59,7 @@ Examples: Note that exploring a package's source and history within the `cran` GitHub organisation is not the same as exploring the package's true development venue, because this source and its evolution is just reverse-engineered from the package's CRAN releases. This presents a redacted view of the package and its history, but, by definition, it includes everything that is essential. -## Bundled package +## Bundled package {#sec-bundled-package} A **bundled** package is a package that's been compressed into a single file. By convention (from Linux), package bundles in R use the extension `.tar.gz` and are sometimes referred to as "source tarballs". diff --git a/testing-design.Rmd b/testing-design.Rmd index 228c1bfac..760b36475 100644 --- a/testing-design.Rmd +++ b/testing-design.Rmd @@ -57,7 +57,7 @@ We use covr regularly, in two different ways: - Local, interactive use. We mostly use `devtools::test_coverage_active_file()` and `devtools::test_coverage()`, for exploring the coverage of an individual file or the whole package, respectively. - Automatic, remote use via GitHub Actions (GHA). We cover continuous integration and GHA more thoroughly elsewhere, but we will at least mention here that `usethis::use_github_action("test-coverage")` configures a GHA workflow that constantly monitors your test coverage. Test coverage can be an especially helpful metric when evaluating a pull request (either your own or from an external contributor). A proposed change that is well-covered by tests is less risky to merge. -## High-level principles for testing +## High-level principles for testing {#sec-testing-design-principles} In later sections, we offer concrete strategies for how to handle common testing dilemmas in R. Here we lay out the high-level principles that underpin these recommendations: diff --git a/vignettes.Rmd b/vignettes.Rmd index e6bdfc85d..677bcf0a3 100644 --- a/vignettes.Rmd +++ b/vignettes.Rmd @@ -1,37 +1,47 @@ # Vignettes {#sec-vignettes} -```{=html} - -``` ```{r, echo = FALSE} source("common.R") -status("restructuring") +status("polishing") ``` ## Introduction A vignette is a long-form guide to your package. Function documentation is great if you know the name of the function you need, but it's useless otherwise. -A vignette is like a book chapter or an academic paper: it can describe the problem that your package is designed to solve, and then show the reader how to solve it. +In contrast, a vignette can be framed around a target problem that your package is designed to solve. +The vignette format is perfect for showing a workflow that solves that particular problem, start to finish. +Vignettes afford you different opportunities than help topics: you have much more control over the integration of code and prose and it's a better setting for showing how multiple functions work together. + +Many existing packages have vignettes and you can see all the vignettes associated with your installed packages with `browseVignettes()`. +To limit that to a particular package, you can specify the package's name like so: `browseVignettes("tidyr")`. +You can read a specific vignette with the `vignette()` function, e.g. `vignette("rectangle", package = "tidyr")`. +To see vignettes for a package that you haven't installed, look at the "Vignettes" listing on its CRAN page, e.g. . + +However, we much prefer to discover and read vignettes from a package's website, which is the topic of @sec-website[^vignettes-1]. +Compare the above to what it feels like to access tidyr's vignettes from its website: . +Note that pkgdown uses the term "article", which feels like the right vocabulary for package users. +The technical distinction between a vignette (which ships with a package) and an article (which is only available on the website; see @sec-vignettes-article) is something the package developer needs to think about. +A pkgdown website presents all of the documentation of a package in a cohesive, interlinked way that makes it more navigable and useful. +This chapter is ostensibly about vignettes, but the way we do things is heavily influenced by how those vignettes fit into in a pkgdown website. -Many existing packages have vignettes. -You can see all the installed vignettes with `browseVignettes()`. -To see the vignette for a specific package, use the argument, `browseVignettes("packagename")`. -Each vignette provides three things: the original source file, a readable HTML page or PDF, and a file of R code. -You can read a specific vignette with `vignette(x)`, and see its code with `edit(vignette(x))`. -To see vignettes for a package you haven't installed, look at its CRAN page, e.g., . +[^vignettes-1]: This obviously depends on the quality of one's internet connection, so we make an effort to recommend behaviours that are compatible with base R's tooling around installed vignettes. -In this chapter, we're going to use RMarkdown to write our vignettes. -If you're not already familiar with RMarkdown you'll need to learn the basics elsewhere; at good place to start is . +In this book, we're going to use RMarkdown to write our vignettes[^vignettes-2], just as we did for function documentation in @sec-man-key-md-features. If you're not already familiar with RMarkdown you'll need to learn the basics elsewhere; a good place to start is . -Older packages can include vignettes written with Sweave, a precursor to RMarkdown. -If this describes, your package, I highly recommend switching to RMarkdown. +[^vignettes-2]: Sweave is the original system used for authoring vignettes (Sweave files usually have extension `.Rnw`). + Similar to our advice about how to author function documentation (@sec-man), we think it makes more sense to use a markdown-based syntax for vignettes than a one-off, LaTeX-associated format. + This choice also affects the form of rendered vignettes: Sweave vignettes render to PDF, whereas RMarkdown vignettes render to HTML. + We recommend converting Sweave vignettes to RMarkdown. -## Vignette workflow {#vignette-workflow} +In general, we embrace a somewhat circumscribed vignette workflow, i.e. there are many things that base R allows for, that we simply don't engage in. +For example, we treat `inst/doc/`[^vignettes-3] in the same way as `man/` and `NAMESPACE`, i.e. as something semi-opaque that is managed by automated tooling and that we don't modify by hand. +Base R's vignette system allows for various complicated maneuvers that we just try to avoid. +In vignettes, more than anywhere else, the answer to "But how do I do X?" is often "Don't do X". + +[^vignettes-3]: The `inst/doc/` folder is where vignettes go once they're built, when `R CMD build` makes the package bundle. + +## Workflow for writing a vignette {#sec-vignettes-workflow-writing} To create your first vignette, run: @@ -39,29 +49,45 @@ To create your first vignette, run: usethis::use_vignette("my-vignette") ``` -This will: +This does the following: + +1. Creates a `vignettes/` directory. + +2. Adds the necessary dependencies to `DESCRIPTION`, i.e. adds knitr to the `VignetteBuilder` field and adds both knitr and rmarkdown to `Suggests`. + +3. Drafts a vignette, `vignettes/my-vignette.Rmd`. + +4. Adds some patterns to `.gitignore` to ensure that files created as a side effect of previewing your vignettes are kept out of source control (we'll say more about this later). + +This draft document has the the key elements of an R Markdown vignette and leaves you in a position to add your content. +You also call `use_vignette()` to create your second and all subsequent vignettes; it will just skip any setup that's already been done. -1. Create a `vignettes/` directory. +Once you have the draft vignette, the workflow is straightforward: -2. Add the necessary dependencies to `DESCRIPTION` (i.e. it adds knitr to the `Suggests` and `VignetteBuilder` fields). +1. Start adding prose and code chunks to the vignette. + Use `devtools::load_all()` as needed and use your usual interactive workflow for developing the code chunks. -3. Draft a vignette, `vignettes/my-vignette.Rmd`. +2. Render the entire vignette periodically. -The draft vignette has been designed to remind you of the important parts of an R Markdown file. -It serves as a useful reference when you're creating a new vignette. -Once you have this file, the workflow is straightforward: + This requires some intention, because unlike tests, by default, a vignette is rendered using the currently installed version of your package, not with the current source package, thanks to the initial call to `library(yourpackage)`. -1. Modify the vignette. + One option is to properly install your current source package with `devtools::install()` or, in RStudio, Ctrl/Cmd + Shift + B. + Then use your usual workflow for rendering an `.Rmd` file. + For example, press Ctrl/Cmd + Shift + K or click ![](images/knit.png){width="45"}. -2. Press Ctrl/Cmd + Shift + K (or click ![](images/knit.png){width="45"}) to knit the vignette and preview the output. + Another option is to use `devtools::build_rmd("vignettes/my-vignette.Rmd")` to render the vignette. + This builds your vignette against a (temporarily installed) development version of your package. -3. This builds with the installed package --- but you probably want the dev package. - Use `devtools::build_rmd()`. + It's very easy to over look this issue and be puzzled when your vignette preview doesn't seem to reflect recent developments in the package. + Double check that you're building against the current version! -The check workflow, `Cmd + Shift + E`, will run the code in all vignettes. -This is a good way to verify that you've captured all the needed dependencies. +3. Rinse and repeat until the vignette looks the way you want. -## Metadata {#vignette-metadata} +If you're regularly checking your entire package (@sec-r-cmd-check), which we strongly recommend, this will help to keep your vignettes in good working order. +In particular, this will alert you if a vignette makes use of a package that's not a formal dependency. +We will come back to these package-level workflow issues below in @sec-vignettes-how-built-checked. + +## Metadata The first few lines of the vignette contain important metadata. The default template contains the following information: @@ -75,96 +101,91 @@ The default template contains the following information: %\VignetteEncoding{UTF-8} --- -This metadata is written in [yaml](https://yaml.org/), a format designed to be both human and computer readable. -The basics of the syntax is much like the `DESCRIPTION` file, where each line consists of a field name, a colon, then the value of the field. +This metadata is written in [YAML](https://yaml.org/), a format designed to be both human and computer readable. +YAML frontmatter is a common feature of RMarkdown files. +The syntax is much like that of the `DESCRIPTION` file, where each line consists of a field name, a colon, then the value of the field. The one special YAML feature we're using here is `>`. -It indicates the following lines of text are plain text and shouldn't use any special YAML features. - -The fields are: +It indicates that the following lines of text are plain text and shouldn't use any special YAML features. -- `title` and `description`. - If you change the title, you must also change the `VignetteIndexEntry{}` described below. +The default vignette template uses these fields: -- `author`: we don't use this unless the vignette author is different to the package author. +- `title`: this is the title that appears in the vignette. + If you change it, make sure to make the same change to `VignetteIndexEntry{}`. + They should be the same, but unfortunately that's not automatic. -- `date`: don't recommend this either as it's very easy to forget to update. - You could use `Sys.date()`, but this shows when the vignette was built, which might be very different to when it was last updated. - -- Output: this tells rmarkdown which output formatter to use. - There are many options that are useful for regular reports (including html, pdf, slideshows, ...) but `rmarkdown::html_vignette` has been specifically designed to work well inside packages. +- `output`: this specifies the output format. + There are many options that are useful for regular reports (including html, pdf, slideshows, etc.), but `rmarkdown::html_vignette` has been specifically designed for this exact purpose. See `?rmarkdown::html_vignette` for more details. -- Vignette: this contains a special block of metadata needed by R. - Here, you can see the legacy of LaTeX vignettes: the metadata looks like LaTeX commands. - You'll need to modify the `\VignetteIndexEntry` to provide the title of your vignette as you'd like it to appear in the vignette index. - Leave the other two lines as is. - They tell R to use `knitr` to process the file, and that the file is encoded in UTF-8 (the only encoding you should ever use to write vignettes). - -Also includes block to set up some standard options: - - ``` {r, echo = FALSE} - knitr::opts_chunk$set(collapse = TRUE, comment = "#>") - ``` - -`collapse = TRUE` and `comment = "#>"` are my preferred way of displaying code output. -I usually set these globally by putting the following knitr block at the start of my document. +- `vignette`: this is a block of special metadata needed by R. + Here, you can see the legacy of LaTeX vignettes: the metadata looks like LaTeX comments. + The only entry you might need to modify is the `\VignetteIndexEntry{}`. + This is how the vignette appears in the vignette index and it should match the `title`. + Leave the other two lines alone. + They tell R to use `knitr` to process the file and that the file is encoded in UTF-8 (the only encoding you should ever use for a vignette). -## Controlling evaluation +We generally don't use these fields, but you will see them in other packages: -Your vignettes will be evaluated in many different places, not just your computer --- CI/CD, CRAN, and users can run on their computers (although not typical). -Thus, you need to make sure they work everywhere. +- `author`: we don't use this unless the vignette is written by someone not already credited as a package author. -Any packages used by your vignette must be listed in `Imports` or `Suggests` fields. -Generally safe to assume that suggest packages will be installed when you vignette is executed. -But if a package is particularly hard to install you might want to safeguard using one of the tools below. +- `date`: we think this usually does more harm than good, since it's not clear what the `date` is meant to convey. + Is it the last time the vignette source was updated? + In that case you'll have to manage it manually and it's easy to forget to update it. + If you manage `date` programmatically with `Sys.date()`, the date reflects when the vignette was built, i.e. when the package bundle was created, which has nothing to do with when the vignette or package was last modified. + We've decided it's best to omit the `date`. -You're probably already familiar with the chunk option `eval = FALSE`. -But can also set for all later chunks with `knitr::opts_chunk$set(eval = FALSE)`. -This is particularly useful for: +The draft vignette also includes two R chunks. +The first one configures our preferred way of displaying code output and looks like this: -- `eval = requireNamespace("package")` -- `eval = !identical(Sys.getenv("foo"), "")` -- `eval = file.exists("special-key")` +````{verbatim} +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` +```` -A final option if you want to don't want to execute at all on CRAN. -Another option is to create an "article"; an Rmd that appears only on the website that's not embedded in the package. -This makes it slightly less accessible, but it's fine if you have a pkgdown website. +The second chunk just attaches the package the vignette belongs to. -Many other options are described at . +````{verbatim} +```{r setup} +library(yourpackage) +``` +```` -`error = TRUE` captures any errors in the block and shows them inline. -This is useful if you want to demonstrate what happens if code throws an error. +You might be tempted to (temporarily) replace this `library()` call with `load_all()`, but we advise that you don't. +Instead, use the techniques given in @sec-vignettes-workflow-writing to exercise your vignette code with the current source package. -## Advice {#vignette-advice} +## Advice on writing vignettes > If you're thinking without writing, you only think you're thinking. > --- Leslie Lamport When writing a vignette, you're teaching someone how to use your package. -You need to put yourself in the readers' shoes, and adopt a "beginner's mind". -This can be difficult because it's hard to forget all of the knowledge that you've already internalised. +You need to put yourself in the reader's shoes, and adopt a "beginner's mind". +This can be difficult because it's hard to forget all of the knowledge that you've already internalized. For this reason, we find in-person teaching to be a really useful way to get feedback. You're immediately confronted with what you've forgotten that only you know. A useful side effect of this approach is that it helps you improve your code. -It forces you to re-see the initial onboarding process and to appreciate the parts that are hard. +It forces you to re-see the initial on-boarding process and to appreciate the parts that are hard. Our experience is that explaining how code works often reveals some problems that need fixing. -(In fact, a key part of the tidyverse package release process is writing a blog post: we now do that before submitting to CRAN because of the number of times it's revealed some subtle problem that requires a fix). -In the tidyverse, I think we're generally always a little behind on vignettes and we need more than we currently have. +In fact, a key part of the tidyverse package release process is writing a blog post: we now do that before submitting to CRAN, because of the number of times it's revealed some subtle problem that requires a fix. +It's also fair to say that the tidyverse and its supporting packages would benefit from more "how-to" guides, so that's an area where we are constantly trying to improve. Writing a vignette also makes a nice break from coding. Writing seems to use a different part of the brain from programming, so if you're sick of programming, try writing for a bit. -### Writing +Here are some resources we've found helpful: -- I strongly recommend literally anything written by Kathy Sierra. - Her old blog, [Creating passionate users](https://headrush.typepad.com/) is full of advice about programming, teaching, and how to create valuable tools. - I thoroughly recommend reading through all the older content. - Her new blog, [Serious Pony](https://seriouspony.com/blog/), doesn't have as much content, but it has some great articles. +- Literally anything written by Kathy Sierra. + She is not actively writing at the moment, but her content is mostly timeless and is full of advice about programming, teaching, and how to create valuable tools. + See her original blog, [Creating passionate users](https://headrush.typepad.com/), or the site that came after, [Serious Pony](https://seriouspony.com/blog/). -- If you'd like to learn how to write better, I highly recommend [Style: Lessons in Clarity and Grace](https://amzn.com/0321898680) by Joseph M. Williams and Joseph Bizup. - It helps you understand the structure of writing so that you'll be better able to recognise and fix bad writing. +- "Style: Lessons in Clarity and Grace" by Joseph M. Williams and Joseph Bizup. + This book helps you understand the structure of writing so that you'll be better able to recognise and fix bad writing. ### Diagrams @@ -174,16 +195,79 @@ Writing seems to use a different part of the brain from programming, so if you'r You'll need to watch the file size. If you include a lot of graphics, it's easy to create a very large file. Be on the look out for a `NOTE` that complains about an overly large directory. +You might need to take explicit measures, such as lowering the resolution, reducing the number of figures, or switching from a vignette to an article (@sec-vignettes-article). ::: -### Organisation +### Links + +There is no official way to link to help topics from vignettes or *vice versa* or from one vignette to another. + +This is a concrete example of why we think pkgdown sites are a great way to present package documentation, because pkgdown makes it easy (literally zero effort, in many cases) to get these hyperlinked cross-references. +This is documented in `vignette("linking", package = "pkgdown")`. +If you're reading this book online, the inline call to `vignette()` in the previous sentence should be hyperlinked to the corresponding vignette in pkgdown[^vignettes-4], using the same toolchain that will create automatic links in your pkgdown websites! +We discussed this syntax previously in @sec-man-key-md-features, in the context of function documentation. + +[^vignettes-4]: And, for anyone else, executing this code in the R console will open the vignette, if the host package is installed. + +Automatic links are generated for functions in the host package, namespace-qualified functions in another package, vignettes, and more. +Here are the two most important examples of automatically linked text: + +- `` `some_function()` ``: Autolinked to the documentation of `some_function()`, within the pkgdown site of its host package. + Note the use of backticks and the trailing parentheses. + +- `` `vignette("fascinating-topic")` ``: Autolinked to the "fascinating-topic" article within the pkgdown site of its host package. + Note the use of backticks. + +### Filepaths + +Sometimes it is necessary to refer to another file from a vignette. +The best way to do this depends on the application: + +- A figure created by code evaluated in the vignette: By default, in the `.Rmd` workflow that we recommend, this takes care of itself. + Such figures are automatically embedded into the `.html` using data URIs. + You don't need to do anything. + Example: `vignette("extending-ggplot2", package = "ggplot2")` generates a few figures in evaluated code chunks. + +- An external file that could be useful to users or elsewhere in the package (not just in vignettes): Put such a file in `inst/` (@sec-misc-inst), perhaps in `inst/extdata/` (@sec-data-extdata), and refer to it with `system.file()` or `fs::path_package()`. + Example from `vignettes("sf2", package = "sf")`: + + `````{verbatim} + ````{r} + library(sf) + fname <- system.file("shape/nc.shp", package="sf") + fname + nc <- st_read(fname) + ``` + ````` + +- An external file whose utility is limited to your vignettes: put it alongside the vignette source files in `vignettes/` and refer to it with a filepath that is relative to `vignettes/`. + + Example: The source of `vignette("tidy-data", package = "tidyr")` is found at `vignettes/tidy-data.Rmd` and it includes a chunk that reads a file located at `vignettes/weather.csv` like so: + + ````{verbatim} + ```{r} + weather <- as_tibble(read.csv("weather.csv", stringsAsFactors = FALSE)) + weather + ``` + ```` + +- An external graphics file: put it in `vignettes/`, refer to it with a filepath that is relative to `vignettes/` and use `knitr::include_graphics()` inside a code chunk. + Example from `vignette("sheet-geometry", package = "readxl")`: + + ````{verbatim} + ```{r out.width = '70%', echo = FALSE} + knitr::include_graphics("img/geometry.png") + ``` + ```` + +### How many vignettes? For simpler packages, one vignette is often sufficient. -Call it `pkgname.Rmd`; that takes advantage of a pkgdown convention which will automatically link "Getting Started" to your vignette. +If your package is named "somepackage", call this vignette `somepackage.Rmd`. +This takes advantage of a pkgdown convention, where the vignette that's named after the package gets an automatic "Getting Started" link in the top navigation bar. -But for more complicated packages you may actually need more than one. -In fact, you can have as many vignettes as you like. -I tend to think of them like chapters of a book -- they should be self-contained, but still link together into a cohesive whole. +More complicated packages probably need more than one vignette. +It can be helpful to think of vignettes like chapters of a book -- they should be self-contained, but still link together into a cohesive whole. ### Scientific publication @@ -193,5 +277,197 @@ In this case, you might also consider submitting your vignette to the [Journal o Both journals are electronic only and peer-reviewed. Comments from reviewers can be very helpful for improving your package and vignette. -If you just want to provide something very lightweight so folks have an easy time citing your package you might also consider the [Journal of Open Source Software](https://joss.theoj.org). +If you just want to provide something very lightweight so folks can easily cite your package, consider the [Journal of Open Source Software](https://joss.theoj.org). This journal has a particularly speedy submission and review process, and is where we published "[*Welcome to the Tidyverse*](https://joss.theoj.org/papers/10.21105/joss.01686)", a paper we wrote so that folks could have a single paper to cite and all the tidyverse authors would get some academic credit. + +## Special considerations for vignette code + +A recurring theme is that the R code inside a package needs to be written differently from the code in your analysis scripts and reports. +This is true for your functions (@sec-code-when-executed), tests (@sec-testing-design-principles), and examples (@sec-man-examples), and it's also true for vignettes. +In terms of what you can and cannot do, vignettes are fairly similar to examples, although some of the mechanics differ. + +Any package used in a vignette must be a formal dependency, i.e. it must be listed in `Imports` or `Suggests` in `DESCRIPTION`. +Similar to our stance in tests (@sec-suggested-packages-and-tests), our policy is to write vignettes under the assumption that suggested packages will be installed in any context where the vignette is being built. +We generally use suggested packages unconditionally in vignettes. +But, as with tests, if a package is particularly hard to install, we might make an exception and take extra measures to guard its use. + +There are many other reasons why it might not be possible to evaluate all of the code in a vignette in certain contexts, such as on CRAN's machines or in CI/CD. +These include all the usual suspects: lack of authentication credentials, long-running code, or code that is vulnerable to intermittent failure. + +The main method for controlling evaluation in an `.Rmd` document is the `eval` code chunk option, which can be `TRUE` (the default) or `FALSE`. +Importantly, the value of `eval` can be the result of evaluating an expression. +Here are some relevant examples: + +- `eval = requireNamespace("somedependency")` +- `eval = !identical(Sys.getenv("SOME_THING_YOU_NEED"), "")` +- `eval = file.exists("credentials-you-need")` + +The `eval` option can be set for an individual chunk, but in a vignette it's likely that you'll want to evaluate most or all of the chunks or practically none of them. +In the latter case, you'll want to use `knitr::opts_chunk$set(eval = FALSE)` in an early, hidden chunk to make `eval = FALSE` the default for the remainder of the vignette. +You can still override with `eval = TRUE` in individual chunks. + +In vignettes, we use the `eval` option in a similar way as `@examplesIf` in examples (@sec-man-examples-dependencies-conditional-execution). +If the code can only be run under specific conditions, you must find a way to to check for those pre-conditions programmatically at runtime and use the result to set the `eval` option. + +Here are the first few chunks in a vignette from googlesheets4, which wraps the Google Sheets API. The vignette code can only be run if we are able to decrypt a token that allows us to authenticate with the API. +That fact is recorded in `can_decrypt`, which is then set as the vignette-wide default for `eval`. + +````{verbatim} +```{r setup, include = FALSE} +can_decrypt <- gargle:::secret_can_decrypt("googlesheets4") +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + error = TRUE, + eval = can_decrypt +) +``` + +```{r eval = !can_decrypt, echo = FALSE, comment = NA} +message("No token available. Code chunks will not be evaluated.") +``` + +```{r index-auth, include = FALSE} +googlesheets4:::gs4_auth_docs() +``` + +```{r} +library(googlesheets4) +``` +```` + +Notice the second chunk uses `eval = !can_decrypt`, which prints an explanatory message for anyone who builds the vignette without the necessary credentials. + +The example above shows a few more handy chunk options. +Use `include = FALSE` for chunks that should be evaluated but not seen in the rendered vignette. +The `echo` option controls whether code is printed, in addition to output. +Finally, `error = TRUE` is what allows you to purposefully execute code that could throw an error. +The error will appear in the vignette, just as it would for your user, but it won't prevent the execution of the rest of your vignette's code, nor will it cause `R CMD check` to fail. +This is something that works much better in a vignette than in an example. + +Many other options are described at . + +### Article instead of vignette {#sec-vignettes-article} + +There is one last technique, if you don't want any of your code to execute on CRAN. +Instead of a vignette, you can create an article, which is a term used by pkgdown for a vignette-like `.Rmd` document that is not shipped with the package, but that appears only in the website. +An article will be less accessible than a vignette, for certain users, such as those with limited internet access, because it is not present in the local installation. +But that might be an acceptable compromise, for example, for a package that wraps a web API. + +You can draft a new article with `usethis::use_article()`, which ensures the article will be `.Rbuildignore`d. +A great reason to use an article instead of a vignette is to show your package working in concert with other packages that you don't want to depend on formally. +Another compelling use case is when an article really demands lots of graphics. +This is problematic for a vignette, because the large size of the package causes problems with `R CMD check` (and, therefore, CRAN) and is also burdensome for everyone who installs it, especially those with limited internet. + +## How vignettes are built and checked {#sec-vignettes-how-built-checked} + +We close this chapter by returning to a few workflow issues we didn't cover in @sec-vignettes-workflow-writing: How do the `.Rmd` files get turned into the vignettes consumed by users of an installed package? +What does `R CMD check` do with vignettes? +What are the implications for maintaining your vignettes? + +It can be helpful to appreciate the big difference between the workflow for function documentation and vignettes. +The source of function documentation is stored in roxygen comments in `.R` files below `R/`. +We use `devtools::document()` to generate `.Rd` files below `man/`. +These `man/*.Rd` files are part of the source package. +The official R machinery cares *only* about the `.Rd` files. + +Vignettes are very different because the `.Rmd` source is considered part of the source package and the official machinery (`R CMD build` and `check`) interacts with vignette source and built vignettes in many ways. +The result is that the vignette workflow feels more constrained, since the official tooling basically treats vignettes somewhat like tests, instead of documentation. + +### `R CMD build` and vignettes {#sec-vignettes-how-built} + +First, it's important to realize that the `vignettes/*.Rmd` source files exist only when a package is in source (@sec-source-package) or bundled form (@sec-bundled-package). +Vignettes are rendered when a source package is converted to a bundle via `R CMD build` or a convenience wrapper such as `devtools::build()`. +The rendered products (`.html`) are placed in `inst/doc/`, along with their source (`.Rmd`) and extracted R code (`.R`; discussed in @sec-vignettes-how-checked). +Finally, when a package binary is made (@sec-structure-binary), the `inst/doc/` directory is promoted to a top-level `doc/` directory, as happens with everything below `inst/`. + +```{=html} + +``` +The key takeaway from the above is that it is awkward to keep rendered vignettes in a source package and this has implications for the vignette development workflow. +It is tempting to fight this (and many have tried), but based on years of experience and discussion, the devtools philosophy is to accept this reality. + +Assuming that you don't try to keep built vignettes around persistently in your source package, here are our recommendations for various scenarios: + +- Active, iterative work on your vignettes: Use your usual interactive `.Rmd` workflow (such as the ![](images/knit.png){width="45"} button) or `devtools::build_rmd("vignettes/my-vignette.Rmd")` to render a vignette to `.html` in the `vignettes/` directory. + Regard the `.html` as a disposable preview. + (If you initiate vignettes with `use_vignette()`, this `.html` will already be gitignored.) + +- Make the current state of vignettes in a development version available to the world: + + - Offer a pkgdown website, preferably with automated "build and deploy", such as using GitHub Actions to deploy to GitHub Pages. + Here are tidyr's vignettes in the development version (note the "dev" in the URL): . + + - Be aware that anyone who installs directly from GitHub will need to explicitly request vignettes, e.g. with `devtools::install_github(dependencies = TRUE, build_vignettes = TRUE)`. + +- Make the current state of vignettes in a development version available locally: + + - Install your package locally and request that vignettes be built and installed, e.g. with `devtools::install(dependencies = TRUE, build_vignettes = TRUE)`. + +- Prepare built vignettes for a CRAN submission: Don't try to do this by hand or in advance. + Allow vignette (re-)building to happen as part of `devtools::submit_cran()` or `devtools::release()`, both of which build the package. + +```{=html} + +``` +If you really do want to build vignettes in the official manner on an *ad hoc* basis, `devtools::build_vignettes()` will do this. +But we've seen this lead to developer frustration, because it leaves the package in a peculiar form that is a mishmash of a source package and an unpacked package bundle. +This nonstandard situation can then lead to even more confusion. +For example, it's not clear how these not-actually-installed vignettes are meant to be accessed. +Most developers should avoid using `build_vignettes()` and, instead, pick one of the approaches outlined above. + +::: callout-tip +## Pre-built vignettes (or other documentation) + +We highly recommend treating `inst/doc/` as a strictly machine-writable directory for vignettes. +We recommend that you do not take advantage of the fact that you can place arbitrary pre-built documentation in `inst/doc/`. +This opinion permeates the devtools ecosystem which, by default, cleans out `inst/doc/` during various development tasks, to combat the problem of stale documentation. + +However, we acknowledge that there are exceptions to every rule. +In some domains, it might be impractical to rebuild vignettes as often our recommended workflow implies. +Here are a few tips: + +- You can prevent the cleaning of `inst/doc/` with `pkgbuild::build(clean_doc =)`. + You can put `Config/build/clean-inst-doc: FALSE` in `DESCRIPTION` to prevent rcmdcheck from cleaning `inst/doc/` and something similar may be implemented for pkgbuild (). + +- The rOpenSci tech note [How to precompute package vignettes or pkgdown articles](https://ropensci.org/blog/2019/12/08/precompute-vignettes/) describes a clever, lightweight technique for keeping a manually-updated vignette in `vignettes/`. + +- The [R.rsp](https://henrikbengtsson.github.io/R.rsp/index.html) package offers explicit support for static vignettes. +::: + +### `R CMD check` and vignettes {#sec-vignettes-how-checked} + +We conclude with a discussion of how vignettes are treated by `R CMD check`. +This official checker expects a package bundle created by `R CMD build`, as described above. +In the devtools workflow, we usually rely on `devtools::check()`, which automatically does this build step for us, before checking the package. +`R CMD check` has various command line options and also consults many environment variables. +We're taking a maximalist approach here, i.e. we describe all the checks that *could* happen. + +`R CMD check` does some static analysis of vignette code and scrutinizes the existence, size, and modification times of various vignette-related files. +If your vignettes use packages that don't appear in `DESCRIPTION`, that is caught here. +If files that should exist don't exist or *vice versa*, that is caught here. +This should not happen if you use the standard vignette workflow outlined in this chapter and is usually the result of some experiment that you've done, intentionally or not. + +The vignette code is then extracted into a `.R` file, using the "tangle" feature of the relevant vignette engine (knitr, in our case), and run. +The code originating from chunks marked as `eval = FALSE` will be commented out in this file and, therefore, is not executed. +Then the vignettes are rebuilt from source, using the "weave" feature of the vignette engine (knitr, for us). +This executes all the vignette code yet again, except for chunks marked `eval = FALSE`. + +::: callout-warning +## Submitting to CRAN + +CRAN's incoming and ongoing checks use `R CMD check` which, as described above, exercises vignette code up to two times. +Therefore, it is important to conditionally suppress the execution of code that is doomed to fail on CRAN. + +However, it's important to note that the package bundle and binaries distributed by CRAN actually use the built vignettes included in your submission. +Yes, CRAN will attempt to rebuild your vignettes regularly, but this is for quality control purposes. +CRAN distributes the vignettes you built. +:::