-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathgolem.qmd
340 lines (202 loc) · 24 KB
/
golem.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
```{r, echo = FALSE}
knitr::opts_chunk$set(
fig.align = "center"
)
```
# Golem {#sec-golem}
Neste capítulo, vamos apresentar o [framework Golem](https://golemverse.org/) para a construção de aplicativos Shiny prontos para serem colocados em produção.
```{r, fig.align="center", echo = FALSE, out.width="25%"}
knitr::include_graphics("img/golem.png")
```
## Motivação
O destino final de aplicativos Shiny costuma ser um ambiente de produção diferente do ambiente de desenvolvimento. Seja um servidor próprio, uma máquina na nuvem ou o [shinyapps.io](https://shinyapps.io/), o nosso app precisa funcionar nesses ambientes, não apenas na nossa máquina.
Uma vez no ambiente de produção, aplicativos Shiny costumam ficar lá por um bom tempo, gerando a necessidade de manutenção períodica e/ou atualizações. A depender de como o app foi desenvolvido, essas tarefas podem ficar muito mais trabalhosas. Seria interessante, nesse sentido, ter um framework de desenvolvimento que facilitasse a organização e documentação do código e o controle das dependências. É para isso que o Golem foi criado.
O Golem é um *framework* para desenvolvimento de aplicativos Shiny prontos para serem colocados em produção. As vantagens são:
- padroniza a organização dos scripts e demais arquivos do seu app;
- integra com pacotes que aceleram o desenvolvimento do código;
- motiva a documentação do código;
- e facilita o compartilhamento e a reutilização de códigos em outros projetos e com outras pessoas.
Na próxima seção, abordaremos como usar o pacote `golem` para obter essas vantagens.
## Como usar?
Antes de mais nada, precisamos instalar o pacote.
```{r, eval = FALSE}
install.packages("golem")
```
Para criar um app dentro do framework Golem, basta rodar o seguinte código:
```{r, eval=FALSE}
golem::create_golem("~/Documents/meuapp")
```
Esse código vai criar uma pasta chamada `meuapp/` dentro de `~/Documents/` (você pode especificar qualquer outra pasta no seu computador). Essa pasta vai conter diversos arquivos que lhe permitirão iniciar o desenvolvimento do seu app dentro do Golem.
Antes de falarmos dos arquivos dessa pasta, precisamos ter em mente que usar o pacote `golem` diz muito mais respeito a seguir uma filosofia do que a aprender uma ferramenta. Como os próprios autores descrevem
> *Golem is an __opinionated__ framework for building production-grade shiny applications*.
Isto é, para usar o Golem precisamos construir nosso app de um jeito específico, que os autores consideram ser o melhor. Com relação ao pacote em si, criada a estrutura inicial com a função `golem::create_golem()`, você poderia continuar o desenvolvimento do app dentro desse framework sem utilizar nenhuma outra função do `golem`^[Na prática, poderíamos construir essa estrutura nós mesmos e usar o framework Golem sem usar o pacote `golem`.]. O que realmente importa é seguir as seguintes premissas:
- um aplicativo Golem é construído como um **pacote R**;
- sempre que conveniente, devemos dividir o nosso app em módulos;
- e devemos documentar as funções que compõem o aplicativo.
Assim, como já falamos de módulos no capítulo anterior, para entender melhor o Golem, precisamos falar um pouco de pacotes.
## Pacotes
Se você nunca construiu um pacote de R, recomendamos fortemente a leitura do livro [R Packages](https://r-pkgs.org/), em especial os 12 primeiros capítulos. Também recomendamos que treine construindo alguns pacotes simples fora do Shiny/Golem antes de continuar (veja os exercícios deste capítulo). Esse passo atrás é importante para que você saiba diferenciar o que é um pacote de R, o que é o Shiny e o que é o Golem.
Dito isso, para aqueles que por imprudência ou falta de tempo continuarão este texto sem uma base sobre o tema "pacotes", vamos apresentar os pontos necessários para o entendimento do Golem.
### O que é um pacote?
Um pacote de R é uma forma específica de organizar código, seguindo o protocolo descrito pela R Foundation.
> _Pacotes são a unidade fundamental de código R reprodutível._
>
> — Wickham & Bryan
Um pacote inclue funções em R, documentação sobre como usá-las, testes e dados de exemplo.
De maneira geral, as funções de um pacote tentam resolver bem um problema em específico. O pacote `dplyr`, por exemplo, possui funções especializadas em manipular bases de dados, já o pacote `ggplot2` possui funções para a construção de gráficos.
### Estrutura básica do pacote
A seguir, apresentaremos a estrutura básica (arquivos e pastas) de qualquer pacote R.
- `DESCRIPTION`: define o nome, descrição, versão, licença, dependências e outras caracaterísticas do pacote. É um arquivo de metadados.
- `LICENSE`: especifica os termos de uso e distribuição do seu pacote.
- `.Rbuildignore`: lista arquivos que não devem ser incluídos ao compilar o pacote R a partir do código-fonte, isto é, arquivos que são úteis apenas no desenvolvimento e não serão enviados para quem instalar o pacote.
- `NAMESPACE`: este arquivo declara as funções que o pacote exporta (que ficam disponível quando alguém usa `library()`) e as funções que seu pacote importa de outros pacotes. Ele é criado automaticamente a partir da documentação das funções do pacote. Não devemos editar este arquivo manualmente.
- `R/`: pasta onde fica o código R das funções do pacote. Essa pasta não deve conter subdiretórios.
### Criando pacotes
Uma maneira fácil de criarmos a estrutura básica de um pacote é usamos a função `usethis::create_package()`. Você deve passar um caminho como `~/Documents/meupacote` e uma nova pasta chamada `meupacote` será criada dentro da pasta `Documents`. Essa pasta será tanto um projeto do RStudio quanto um pacote, ambos chamados `meupacote`.
> Não adicione acentos, caracteres especiais e espaços no nome do pacote, assim como nos arquivos que você criar dentro dele.
```{r eval=FALSE}
usethis::create_package("~/Documents/meupacote")
```
### A pasta `R/`
Dentro de um pacote, a pasta `R/` só pode ter scripts R com funções. Guardaremos nela todas as funções que farão parte do nosso pacote, mesmo que elas sejam apenas funções usadas internamente.
As funções colocadas dentro dessa pasta nunca devem ser rodadas diretamente. Se você quiser testá-las, deve fazer isso "carregando as funções", isto é, usando a função `devtools::load_all()`. Isso fará com que todas as funções dentro da pasta `R/` fiquem disponíveis na sua sessão, algo equivalente a fazer `library(meupacote)`, mas com a diferença de também carregar as funções não exportadas.
Podemos usar `usethis::use_r("nome-do-arquivo")` para criar um arquivo script R dentro da pasta `R/`.
### Dependências
Sem os inúmeros pacotes criados pela comunidade, o R provavelmente já estaria no porão da Ciência de Dados. Por isso, a primeira coisa que escrevemos nos nossos *scripts* quase sempre é `library(algumPacoteLegal)`. Quando estamos construindo um pacote, é comum querermos utilizar dentro dele outros pacotes que não apenas o R base. Esses pacotes são chamados de **dependências**.
Ao desenvolver um pacote, a função `library()` **nunca deve ser utilizada**^[Imagine se, ao usarmos uma função de um pacote, ele carregasse um novo pacote na nossa sessão, possivelmente mascarando funções que estamos usando. Isso seria uma péssima prática.], e todas as funções externas devem ter seus pacotes de origem explicitamente referenciados pelo operador `::`. Embora seja chato especificar todos os pacotes, isso traz uma vantagem: as dependências do código estarão sempre atualizadas, pois elas estarão sempre atreladas às próprias funções sendo utilizadas.
Sempre que você utilizar um pacote dentro do pacote que está desenvolvendo, você deve especificá-lo como dependência no arquivo `DESCRIPTION`. Isso dirá ao R que, ao instalar o seu pacote, ele também precisa instalar todos os pacotes listados como dependência nesse arquivo. Você pode fazer isso facilmente utilizando `usethis::use_package()`. O código abaixo registra o pacote `dplyr` como dependência de um pacote sendo construído.
```{r, eval=FALSE}
usethis::use_package("dplyr")
```
Se você está usando um pacote em desenvolvimento a partir de um repositório do GitHub, por exemplo, você pode usar a função `usethis::use_dev_package()` para adicioná-lo como dependência.
```{r, eval=FALSE}
usethis::use_dev_package("dplyr", remote = "tidyverse/dplyr")
```
Leia a documentação dessas funções para mais informações sobre como adicionar dependências ao arquivo `DESCRIPTION`.
### Dados
Se o seu pacote possuir bases de dados, como a `dplyr::starwars`, ou qualquer outro tipo de objeto do R, como `pi` ou `letters`, você deve colocá-los dentro de uma pasta chamada `data/`, na raiz do projeto, com a extensão `.rda`^[Arquivos `.rda` são extremamente estáveis, compactos e podem ser carregados rapidamente pelo R, tornando este formato o principal meio de guardar dados de um pacote.]. Isso pode ser feito facilmente a partir da função `usethis::use_data()`.
Ao rodar o código abaixo, por exemplo, vamos criar uma pasta `data/` na raiz do pacote, caso ela não exista ainda, e salvar nela o vetor base `nomes` no arquivo `nomes.rda`.
```{r, eval=FALSE}
nomes <- c("Athos", "Bruna", "Caio")
usethis::use_data(nomes)
```
Fazendo isso, quando alguém carregar esse pacote, o objeto `nomes` ficará disponível para ser utilizado (igual a base `starwars` fica disponível quando carregamos o `dplyr`).
### Documentação de funções
Para documentar as funções do seu pacote (i.e., gerar aquele documento mostrado quando rodamos `?mean`, por exemplo), escrevemos comentários antes da definição da função nos scripts da pasta `R/`. Fazemos isso usando um tipo de comentários especial, o `#'`, e marcadores que indicam qual parte da documentação estamos escrevendo. A estrutura dos comentários deve ser a seguinte:
```{r eval=FALSE}
#' Título da função
#'
#' Descrição da função
#'
#' @param a Descrição do primeiro parâmetro.
#' @param b Descrição do segundo parâmetro.
#'
#' @return Descrição do resultado (valor que sai da função).
#'
#' @export
fun <- function(a, b) {
a + b
}
```
O marcador `@export` indica que a função ficará disponível quando rodarmos `library(meupacote)`. Se você não quer que a função fique disponível, basta não colocar esse marcador.
Após escrever a documentação das suas funções dessa maneira, você deve rodar `devtools::document()` para que ela seja compilada e fique disponível no seu pacote (acessível pelo *Help* do R). Isso é feito por trás das cortinas pelo pacote `roxygen2`.
**Dica**: o RStudio disponibiliza um atalho para criar a estrutura da documentação de uma função. No menu superior, clique em `Code` -> `Insert Roxygen Skeleton`.
Para saber mais sobre documentação de pacotes, leia [este capítulo do R Packages](https://r-pkgs.org/man.html).
### Instalando e compartilhando o seu pacote
Para verificar se você não feriu alguma regra de desenvolvimento de pacotes R, você pode usar a função `devtools::check()`. Essa função devolverá um relatório com possíveis problemas que o seu pacote pode ter, como erros de sintaxe, arquivos com extensões não permitidos, dependências não declaradas ou erros de documentação.
Para instalar o seu pacote localmente durante o desenvolvimento, rode a função `devtools::install()`. Isso é equivalente a ter o pacote instalado via `install.packages()`.
O jeito mais fácil de disponibilizar o seu pacote na internet é subi-lo para um repositório público no Github. Dessa maneira, qualquer pessoa pode instalá-lo com a função `remotes::install_github()`.
Para subir um pacote para o CRAN, o processo é (bem) mais burocrático. Se você quiser saber mais, leia [este capítulo do R Packages](https://r-pkgs.org/release.html).
## Estrutura de um Golem app
Agora que já sabemos o básico sobre pacotes R, podemos voltar a falar do Golem.
Uma pasta criada pela função `golem::create_golem()` terá a seguinte estrutura:
```{r}
#> ├── DESCRIPTION
#> ├── NAMESPACE
#> ├── R
#> │ ├── app_config.R
#> │ ├── app_server.R
#> │ ├── app_ui.R
#> │ └── run_app.R
#> ├── dev
#> │ ├── 01_start.R
#> │ ├── 02_dev.R
#> │ ├── 03_deploy.R
#> │ └── run_dev.R
#> ├── inst
#> │ ├── app
#> │ │ └── www
#> │ │ └── favicon.ico
#> │ └── golem-config.yml
#> └── man
#> └── run_app.Rd
```
Veja que ela possui, entre outras coisas, a estrutura básica de um pacote. Vamos descrever cada arquivo mais detalhadamente e discutir a importância dele no contexto do desenvolvimento de um Shiny app:
- O arquivo `DESCRIPTION`: guarda os metadados do pacote. No desenvolvimento de um aplicativo Shiny, ele vai guardar o nome do aplicativo, o que ele faz, as dependências dele, a versão (importante em projetos em produção que recebem atualizações periódicas) e quem contatar quando alguma coisa der errada. Com relação às dependências, isso quer dizer que, para rodar o seu app, o R precisará instalar todos os pacotes listados nesse arquivo.
- O arquivo `NAMESPACE`: guarda metadados do pacote. Com esse arquivo, podemos carregar apenas funções específicas de um pacote dentro do nosso app^[Isso é feito automaticamente pelo `roxygen2` a partir da documentação das funções. Não edite esse arquivo na mão.]. O Golem faz isso com o pacote `shiny` nas funções `app_ui()` e `app_server()` para não precisarmos colocar `shiny::` no início de cada função.
- A pasta `R/`: guarda as funções do pacote. Como o app será feito dentro de um pacote R, todo o seu código será escrito em funções nessa pasta. O Golem já cria os arquivos para construirmos a UI e o servidor. Os scripts contendo os módulos do aplicativo também devem ser colocados nessa pasta, assim como scripts com funções úteis utilizadas em vários lugares do app.
- O arquivo `R/app_config.R`: usado para especificar alguns mecanismos do Golem, como ler o arquivo de configuração localizado em `inst/golem-config.yml`.
- O arquivo `R/app_server.R`: script com a função `app_server()`, onde você vai desenvolver o servidor do seu aplicativo.
- O arquivo `R/app_ui.R`: script com a função `app_ui()`, onde você vai desenvolver a UI do seu aplicativo, e a função `golem_add_external_resources()`, utilizada para dizer ao Shiny que a pasta `inst/app/www` será utilizada como uma fonte de recursos externos, acessada pelo caminho `www/nome_do_arquivo`^[Isto é, você não precisa incluir `inst/app/` no caminho do arquivo. Por exemplo, se você quiser colocar a imagem `inst/app/www/imagem.png` no seu app, basta usar o caminho `www/imagem.png`.]. Além disso, o Golem inclui no HTML do seu app a conexão com todo arquivo CSS e JS que você coloca nessa pasta, então não precisamos fazer isso manualmente.
- O arquivo `R/run_app.R`: script que contém a função `run_app()`, utilizada para rodar o app. Ela chama a função `shiny::shinyApp()`, que inicia o app localmente. A funcão `shiny::shinyApp()` está dentro da função `golem::with_golem_options()`, que recebe parâmetros passados para a `run_app()`. Esses parâmetros podem ser recuperados dentro do app com a função `golem::get_golem_options()`, deixando a parametrização de um aplicativo Shiny muito mais simples^[Podemos criar versões diferentes do app que serão executadas a depender dos parâmetros passados na função `run_app()`.].
- `dev/`: pasta com scripts do `golem` que podem ser utilizados ao longo do desenvolvimento do app. Eles contêm uma lista de funções úteis que ajudam a configurar diversos aspectos do aplicativo. O uso desses scripts é opcional.
- A pasta `inst/app/www`: local onde adicionaremos os recursos externos do aplicativo (imagens, arquivos CSS, fontes etc) que serão compartilhados com o navegador de quem estiver usando o app. A pasta `inst` é uma pasta especial no desenvolvimento de pacotes. Ela serve para adicionarmos arquivos que gostaríamos que fossem instalados com o pacote, como arquivos de teste, imagens etc. No contexto do Shiny, ela será utilizada para guardarmos arquivos auxiliares, como a própria pasta `app/www`, templates `.Rmd` de relatórios que o app gera, arquivos `.md` com textos que serão colocados no app, entre outros.
- A pasta `man/`: contém a documentação do pacote, a ser gerada pelo `roxygen2`. É muito importante documentarmos todas as funções do nosso app, pois é muito comum que o código precise de ajustes ou atualizações no futuro. Uma breve descrição do que a função espera e o que ela devolve pode ser suficiente para ajudar a pessoa que for mexer no app no futuro (que pode ser você mesma) a esconomizar horas de debug.
## Principais funções
Além da função `golem::create_golem()` que utilizamos para criar o nosso projeto com a estrutura do framework Golem, o pacote `golem` possui diversas funções úteis para usarmos durante o desenvolvimento do pacote. Vamos listar a seguir algumas delas:
- `golem::set_golem_name()`: usada para mudar o nome do seu aplicativo. A mudança precisa ser feita tanto no arquivo `DESCRIPTION` quanto dentro da função `app_sys()` contida no arquivo `R/app_config.R`, e essa função realiza essas tarefas.
- `golem::add_module()`: cria um arquivo na pasta `R/` com o template de um módulo do Shiny. O nome do módulo (utilizado também como nome do arquivo) é passado pelo argumento `name`.
- `golem::add_css_file()` e `golem::add_js_file()`: cria um arquivo vazio com extensão `.css` ou `.js` dentro da pasta `inst/app/www` do app. O nome desse arquivo pode ser passado pelo argumento `name`.
- `golem::use_utils_ui()`: cria um arquivo chamado `golem_utils_ui.R` na pasta `R/` com diversas funções úteis para serem utilizadas na UI de um Shiny app.
- `golem::use_utils_server()`: cria um arquivo chamado `golem_utils_server.R` na pasta `R/` com diversas funções úteis para serem utilizadas no servidor de um Shiny app.
- `golem::add_shinyappsio_file()`: cria um arquivo `app.R` que pode ser utilizado para fazer o deploy do app para o [shinyapps.io](https://shinyapps.io).
## Deploy
Neste capítulo, vamos falar sobre como fazer o deploy do seu app feito em Golem em dois ambientes diferentes: no [shinyapps.io](https://shinyapps.io) e em qualquer máquina ou serviço que rode containers Docker.
### Deploy para o shinyapps.io
Para subir um Shiny app para o shinyapps.io (veja a [Seção -@sec-deploy-shinyapps] para mais detalhes), precisamos enviar o script primário do nosso app^[O arquivo com a chamada da função `shiny::shinyApp()` no final.], normalmente chamado de `app.R`, junto de todos os arquivos dos quais ele depende (outros scripts, bases de dados, imagens etc). A partir do RStudio, podemos fazer isso com alguns cliques a partir do botão Publish, disponível quando abrimos o arquivo `app.R`, ou a partir da função `rsconnect::deployApp()`, que consegue identificar esse script primário caso você não especifique para ela no argumento `appPrimaryDoc`.
No entanto, na estrutura do Golem que vimos até o momento não temos esse arquivo, já que todas as partes do app são construídas dentro de funções na pasta `R/`. O script `run_app.R` não pode ser considerado esse script primário pois a chamada da função `shiny::shinyApp()` está dentro da definição da função `run_app()`. O que fazer nesse caso?
A solução é criar um arquivo `app.R` dentro dessa estrutura. Podemos fazer isso usando a função `golem::add_shinyappsio_file()`. O arquivo será criado na pasta raiz do seu projeto e poderá ser utilizado como script primário do seu app, isto é, a partir dele você poderá usar o botão `Publish`. Além do `app.R`, você deverá subir para o shinyapps.io todos arquivos que constituem o seu app/pacote.
O arquivo `app.R` criado vai conter a chamada das seguintes funções:
```{r, eval=FALSE}
pkgload::load_all()
options("golem.app.prod" = TRUE)
run_app()
```
- `pkgload::load_all()` vai carregar o as funções definidas na pasta `R/`.
- `options("golem.app.prod" = TRUE)` vai definir a variável de sistema `golem.app.prod` como `TRUE`. Isso é opcional e pode ser utilizado para criar versões diferentes de homologação ou produção para o seu app.
- `run_app()`: roda o seu app, isto é, executa a função `shiny::shinyApp()`.
Com esse arquivo criado, você também poderá utilizar normalmente a função `rsconnect::deployApp()` para fazer o deploy.
### Deploy com Docker
O [Docker](https://www.docker.com/) é um software código aberto para o desenvolvimento e a implantação de aplicações embrulhadas em containers^[Containers são uma unidade padrão de software que empacota um código e suas dependências de tal forma que a aplicação possa rodar de maneira confiável independentemente do ambiente computacional. Leia mais [aqui](https://www.docker.com/resources/what-container/).]. Ao colocar o seu app em um container, você garante que o ambiente onde ele ficará hospedado, seja ele qual for, terá todas as dependências que ele precisa para funcionar e estará devidamente configurado para que as pessoas possam acessá-lo por meio de uma URL.
Para *dockerizar* um app, precisamos criar um `Dockerfile`. Esse arquivo conterá todas as instruções necessárias para criar um container possuindo todas as dependências e configurações para hospedar o seu app (incluindo o Shiny Server). O pacote `golem` possui algumas funções para a criação desse arquivo:
- `golem::add_dockerfile()`: adiciona um `Dockerfile` genérico, sem configurações para ambientes específicos.
- `golem::add_dockerfile_shinyproxy()`: adiciona um `Dockerfile` com configurações específicas para o [ShinyProxy](https://www.shinyproxy.io/), uma solução código aberto para o deploy de aplicativos Shiny.
- `golem::add_dockerfile_heroku()`: adiciona um `Dockerfile` com configurações específicas para o [Heroku](https://www.heroku.com/), um serviço para hospedagem de qualquer tipo de aplicação dockerizada.
O `Dockerfile` será criado na pasta raiz do seu projeto. Para testá-lo localmente, caso você tenha o Docker instalado na sua máquina, basta rodar no **Terminal**:
```
docker build -t meuApp .
docker run -p 8080:80 meuApp
```
Se você precisar trocar a configuração padrão do Shiny Server, crie um novo `shiny-server.conf` na pasta `inst/app` e insira a seguinte linha no `Dockerfile` antes da linha `EXPOSE 80`:
```
COPY ./inst/app/shiny-server.conf /etc/shiny-server/shiny-server.conf
```
Além do ShinyProxy e do Heroku, você também pode subir o seu app dockerizado para plataformas como a AWS e o Google Cloud Engine.
## Exercícios
1. Quais são as vantagens de se usar o Golem?
2. Qual a diferença entre usar Golem e usar apenas módulos?
3. Podemos seguir o framework Golem sem usar o pacote `golem`. Verdadeiro ou falso?
4. Em qual arquivo na estrutura de um pacote R definimos as suas dependências?
5. Construa um pacote R que tenha as seguintes funções:
- `soma(x, y)`: realiza a soma `x + y`;
- `subtração(x, y)`: realiza a subtração `x - y`;
- `mult(x, y)`: realiza a multiplicação `x * y`;
- `divisao(x, y)`: realiza a divisão `x / y`.
6. Construa um pacote R que tenha as seguintes funções:
- `read_csvs(arquivos, empilhar = FALSE)`: ela recebe um vetor com caminhos de arquivos `.csv` contendo bases de dados e devolve uma lista de tibbles com cada uma das bases se `empilhar = FALSE` e uma tibble com todas as bases empilhadas se `empilhar = TRUE`.
- `read_excels(arquivos, empilhar = FALSE)`: a mesma ideia, mas para arquivos `.xlsx`.
7. Aplique o framework Golem no app disponibilizado [neste link](https://curso-r.github.io/main-dashboards-2/exercicios/03-golem/app.R). Ele utiliza a base de dados Pokemon, que pode ser baixada [clicando aqui](https://curso-r.github.io/main-dashboards-2/dados/pkmn.rds). Além disso,
a) Transforme o shinydashboard em Bs4Dash: construa ao menos a UI do zero.
b) Modularize o app (cada página do dashboard deve ser um módulo diferente).
c) Refaça os gráficos utilizando alguma biblioteca javascript (plotly, echarts, highcharts etc).
d) Faça o deploy do app para o shinyapps.io.