Skip to content

Commit

Permalink
Merge pull request #225 from Appsilon/develop
Browse files Browse the repository at this point in the history
Release v0.8.0
  • Loading branch information
kamilzyla authored Apr 11, 2022
2 parents 2f18118 + 442251d commit 51229dd
Show file tree
Hide file tree
Showing 33 changed files with 1,677 additions and 96 deletions.
5 changes: 4 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: rhino
Title: A Framework for Enterprise Shiny Applications
Version: 0.7.0
Version: 0.8.0
Authors@R:
c(
person("Kamil", "Zyla", role = c("aut", "cre"), email = "[email protected]"),
Expand All @@ -17,6 +17,8 @@ Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.2
VignetteBuilder: knitr
Depends:
R (>= 2.10)
Imports:
box,
cli,
Expand All @@ -37,5 +39,6 @@ Imports:
Suggests:
knitr,
rmarkdown
LazyData: true
Config/testthat/edition: 3
Config/testthat/parallel: true
12 changes: 12 additions & 0 deletions R/data.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#' Population of rhinos
#'
#' A dataset containing population of 5 species of rhinos
#'
#' @format A data frame with 58 rows and 3 variables:
#' \describe{
#' \item{Year}{year}
#' \item{Population}{rhinos population}
#' \item{Species}{rhinos species}
#' }
#' @source \url{https://ourworldindata.org/}
"rhinos"
Binary file added data/rhinos.rda
Binary file not shown.
2 changes: 1 addition & 1 deletion inst/templates/app_structure/app.R
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# File generated by Rhino. Do not edit.
# Rhino / shinyApp entrypoint. Do not edit.
rhino::app()
2 changes: 1 addition & 1 deletion inst/templates/app_structure/app/logic/__init__.R
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Logic: application code independent from Shiny.
# https://appsilon.github.io/rhino/articles/rhino-project-structure.html
# https://appsilon.github.io/rhino/articles/explanation-project-structure.html
2 changes: 1 addition & 1 deletion inst/templates/app_structure/app/view/__init__.R
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# View: Shiny modules and related code.
# https://appsilon.github.io/rhino/articles/rhino-project-structure.html
# https://appsilon.github.io/rhino/articles/explanation-project-structure.html
5 changes: 5 additions & 0 deletions inst/templates/github_ci/dot.github/workflows/rhino-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ jobs:
with:
r-version: ${{ env.R_VERSION }}

- name: Setup system dependencies
run: >
sudo apt-get install --yes
libcurl4-openssl-dev
- name: Restore renv from cache
uses: actions/cache@v2
env:
Expand Down
24 changes: 24 additions & 0 deletions man/rhinos.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 38 additions & 1 deletion pkgdown/_pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,40 @@ template:

navbar:
type: inverse

structure:
left: [tutorial, explanation, how-to-guides, reference]
components:
tutorial:
text: Tutorial
href: articles/tutorial-create-your-first-rhino-app.html
explanation:
text: Explanation
menu:
- text: Application structure
href: articles/explanation-application-structure.html
- text: Box modules
href: articles/explanation-box-modules.html
- text: Renv configuration
href: articles/explanation-renv-configuration.html
- text: Node.js - JavaScript and Sass tools
href: articles/explanation-node-js-javascript-and-sass-tools.html
how-to-guides:
text: How-to Guides
menu:
- text: Migrate app to Rhino
href: articles/how-to-migrate-app-to-rhino.html
- text: Write R code
href: articles/how-to-write-r-code.html
- text: Write JavaScript code
href: articles/how-to-write-javascript-code.html
- text: Use static files
href: articles/how-to-use-static-files.html
- text: Manage R dependencies
href: articles/how-to-manage-r-dependencies.html
- text: Keep multiple apps in a single repository
href: articles/how-to-keep-multiple-apps-in-a-single-repository.html


reference:
- title: Project initialization
contents:
Expand All @@ -29,3 +62,7 @@ reference:
contents:
- diagnostics
- test_e2e

- title: Data
contents:
- rhinos
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
---
title: "Rhino Project Structure"
title: "Explanation: Application structure"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Rhino Project Structure}
%\VignetteIndexEntry{Explanation: Application structure}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```

# Philosophy
Shiny comes with a powerful
[reactive programming](https://shiny.rstudio.com/articles/reactivity-overview.html) model
Expand Down Expand Up @@ -70,70 +63,35 @@ server <- function(id) {
}
```

# Box modules
With large applications it is critical for maintainability
to properly structure your code using files and directories.
R comes with the `library()` and `source()` functions,
but its functionality is limited when it comes
to dividing your code into modules and expressing their dependencies.

To address this, Rhino uses the [box](https://klmr.me/box/) R package,
which allows you to modularize your code in a similar way to languages like Python and Java:
# Minimal `app.R`
A Rhino application comes with a minimal `app.R`:
```r
box::use(
dplyr, # Import dplyr. Its functions can be used via `$`, e.g. `dplyr$filter`.
shiny[reactive], # Import the `reactive()` function from shiny package.
)
box::use(
logic/data_validation # Import the `logic/data_validation.R` module.
)
# Rhino / shinyApp entrypoint. Do not edit.
rhino::app()
```

The best place to learn about box is its official [documentation](https://klmr.me/box/).
Some useful box features are also explained in the sections below.

## `__init__.R` files
Objects exported by an `__init__.R` file can be imported from its parent directory.
You should not edit this file and instead write your top-level code in `app/main.R`.
The comment is also important:
thanks to the `shinyApp` string, RStudio recognizes this file as a Shiny application
and shows the "Run" and "Publish" buttons.

### Example
Assume we have an `app/foo/__init__.R` file with the following content:
```r
#' @export
bar <- "Hello!"
```

We can now import `bar` as if it was defined in `app/foo.R`:
```r
box::use(
app/foo[bar]
)
```
Such a solution gives Rhino full control over app startup.
Steps performed by `rhino::app()` include:

This mechanism can be used in combination with reexports
to make it easier to import multiple modules from a single directory.
1. Purge box cache, so the app can be reloaded without restarting R session.
2. Configure logger (log level, log file).
3. Configure static files.
4. Load the main module / legacy entrypoint.
5. Add head tags (favicon, CSS & JS).

## Reexports
A module can reexport objects imported from a different module
by applying `#' @export` to a `box::use()` statement.
One can wonder if we really need a separate `main.R` file.
Couldn't we just define the top-level `ui` and `server` in `app.R`
and pass it to `rhino::app()` as arguments, much like with `shiny::shinyApp()`?

### Example
Assume we have modules `analysis_tab.R` and `download_tab.R` in the `app/view` directory.
We can reexport them from `app/view/__init__.R` like this:
```r
#' @export
box::use(
app/view/analysis_tab,
app/view/download_tab
)
```
We employ this solution to enforce consistent use of box in the entire application.
A file loaded with `box::use()` can only load other modules/packages with `box::use()`.
On the other hand, Shiny simply sources `app.R`,
so `library()` and `source()` could be used in this single file.

The following `box::use()` statements are now equivalent:
```r
box::use(
app/view/analysis_tab,
app/view/download_tab,
)
box::use(
app/view[analysis_tab, download_tab],
)
```
As the entire Rhino application is loaded with `box::use(app/main)`,
all the sources must be properly structured as box modules.
102 changes: 102 additions & 0 deletions vignettes/explanation-box-modules.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: "Explanation: Box modules"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Explanation: Box modules}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---

# Rationale
With large applications it is critical for maintainability
to properly structure your code using files and directories.
R comes with the `library()` and `source()` functions,
but its functionality is limited when it comes
to dividing your code into modules and expressing their dependencies.

To address this, Rhino uses the [box](https://klmr.me/box/) R package,
which allows you to modularize your code in a similar way to languages like Python and Java:
```r
box::use(
dplyr, # Import dplyr. Its functions can be used via `$`, e.g. `dplyr$filter`.
shiny[reactive], # Import the `reactive()` function from shiny package.
)
box::use(
logic/data_validation, # Import the `logic/data_validation.R` module.
)
```

Box modules force you to be explicit about the dependencies between your files and packages.
The graph of dependencies is visible at a glance in an app developed with box,
while the traditional approach (`global.R`, `library()`, `source()`)
makes it easy to build an app which only the author understands.
Introduction of box to existing apps written without it
has helped to improve the code structure and find bugs.

# Features
The best place to learn about box is its official [documentation](https://klmr.me/box/).
Some useful box features are also explained in the sections below.

## Init files
Objects exported by an `__init__.R` file can be imported from its parent directory.

### Example
Assume we have an `app/foo/__init__.R` file with the following content:
```r
#' @export
bar <- "Hello!"
```

We can now import `bar` as if it was defined in `app/foo.R`:
```r
box::use(
app/foo[bar]
)
```

This mechanism can be used in combination with reexports
to make it easier to import multiple modules from a single directory.

## Reexports
A module can reexport objects imported from a different module
by applying `#' @export` to a `box::use()` statement.

### Example
Assume we have modules `analysis_tab.R` and `download_tab.R` in the `app/view` directory.
We can reexport them from `app/view/__init__.R` like this:
```r
#' @export
box::use(
app/view/analysis_tab,
app/view/download_tab
)
```

The following `box::use()` statements are now equivalent:
```r
box::use(
app/view/analysis_tab,
app/view/download_tab,
)
box::use(
app/view[analysis_tab, download_tab],
)
```

# Known issues

### Lazy-loaded data
Box 1.1.0 doesn't support lazy-loaded [data](https://r-pkgs.org/data.html#data-data),
so e.g. `box::use(datasets[mtcars])` won't work.
This feature should be available in the next release
(see this [issue](https://github.com/klmr/box/issues/219)).
For now please use `datasets::mtcars` in your code.

### Trailing commas
Box 1.1.0 allows trailing commas in `box::use()` statements and code,
but they can cause problems in some circumstances:

1. Reexports ([issue](https://github.com/klmr/box/issues/263)).
2. Functions accessed via `$` ([issue](https://github.com/klmr/box/issues/266)).

Both issues should be fixed in the nearest release.
43 changes: 43 additions & 0 deletions vignettes/explanation-node-js-javascript-and-sass-tools.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: "Explanation: Node.js - JavaScript and Sass tools"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Explanation: Node.js - JavaScript and Sass tools}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---

[Node.js](https://nodejs.org/en/about/) is a runtime environment
which can execute JavaScript code outside a web browser.
It is used widely for web development.
Its package manager, [npm](https://docs.npmjs.com/about-npm),
makes it easy to install virtually any JavaScript library.

Rhino uses Node.js to provide state of the art tools
for working with JavaScript and Sass.
In the future it might also become an easy way to add any JS library to your Shiny app.

The following functions require Node.js to work:

1. `build_js()`
2. `build_sass()` (with `sass: node` configuration in `rhino.yml`)
3. `lint_js()`
4. `lint_sass()`
5. `test_e2e()`

You don't need to worry about Node.js most of the time.
You need to install it on your system along with [yarn](https://classic.yarnpkg.com/lang/en/)
(an alternative package manager for Node.js).
The rest will be handled automatically.

Under the hood Rhino will create a `.rhino/node` directory in your project
to store the specific libraries needed by these tools.
This directory is git-ignored by default and safe to remove.

The `build_sass()` function is worth an additional comment.
Depending on the configuration in `rhino.yml`
it can use either the [sass](https://www.npmjs.com/package/sass) Node.js package
or the [sass](https://rstudio.github.io/sass/) R package.
We recommend the Node.js version, as it is the primary, actively developed implementation of Sass.
In contrast, the R package uses the deprecated
[LibSass](https://sass-lang.com/blog/libsass-is-deprecated) implementation.
Loading

0 comments on commit 51229dd

Please sign in to comment.