Développement de package Shiny : structurez votre application en package R
Transformez un dossier de fichiers source()d en véritable package R — suivi des dépendances, fonctions à espace de noms, R CMD check, tests et installation en une ligne — avec usethis et roxygen.
Structurez une application Shiny en package R distribuable avec usethis et roxygen. create_package() échafaude le projet, les balises d’export et document() gèrent l’interface publique via NAMESPACE, use_package() suit les dépendances dans DESCRIPTION, un lanceur run_app() exporté rend l’application appelable via myapp::run_app(), et check() plus install() la valident et la distribuent. La fondation construite à la main que golem automatise.
- Un package apporte à votre application quatre choses qu’un dossier de fichiers
source()d ne peut pas offrir — une liste de dépendances enregistrée (DESCRIPTION), une interface publique/privée (NAMESPACE), la validationR CMD checket une installation en une ligne. usethis::create_package()l’échafaude. Une seule commande écrit leDESCRIPTION, leNAMESPACEet le répertoireR/que tout développeur R reconnaît au premier coup d’œil.- roxygen gère ce qui est public. Placez
#' @exportau-dessus d’une fonction, exécutezdevtools::document(), et il écrit leNAMESPACEà votre place — sans édition manuelle. - L’application devient un package via un
run_app()exporté qui renvoieshiny::shinyApp(...). Installez le package etmyapp::run_app()le lance depuis n’importe où. - The runtime is the judge.
devtools::load_all()et l’appel de votre fonction exportée — oumyapp::run_app()aprèsinstall()— prouvent que le package fonctionne réellement, pas seulement que les fichiers existent. - golem automatise tout cela. Cette leçon est la fondation construite à la main ; lorsque vous êtes prêt à cesser de le faire à la main, le framework golem génère la même structure pour vous.
Introduction
Votre application Shiny a commencé comme un unique app.R. Elle a grossi, alors vous l’avez scindée en ui.R, server.R et un dossier helpers/ que vous chargez avec source() en haut du fichier. Elle fonctionne — sur votre machine. Puis un collègue la clone et rien ne marche : il lui manque trois packages, et il n’existe nulle part de liste de ce dont l’application dépend. Il n’y a pas de R CMD check pour détecter une coquille avant la livraison, aucun moyen d’installer l’application et de l’appeler par son nom, et chaque fonction est globale — deux fichiers peuvent silencieusement définir le même filter_data() et s’écraser mutuellement.
Un package résout ces quatre problèmes. Ce n’est pas une cérémonie gratuite ; c’est la réponse propre à R à la question « comment rendre ceci partageable, vérifiable et fiable sur la machine de quelqu’un d’autre ? ». Un package enregistre ses dépendances dans un fichier DESCRIPTION, contrôle quelles fonctions sont publiques via un NAMESPACE, se soumet au validateur R CMD check (le même contrôle que celui exécuté par CRAN), et s’installe en une seule commande pour que n’importe qui puisse lancer myapp::run_app(). L’outillage — usethis et roxygen2 — se charge des parties fastidieuses pour vous, si bien que « en faire un package » se résume à une poignée de commandes, pas à un week-end.
Cette leçon enseigne les mécanismes manuels : ce qu’est chaque fichier, et les commandes exactes qui construisent et livrent une application Shiny sous forme de package. C’est la fondation que le framework golem automatise — comprenez-la ici, et le code généré par golem cesse d’être magique.
Quand ne pas faire un package. Une application de 50 lignes que vous exécuterez une fois avant de la jeter n’a besoin de rien de tout cela. Un package justifie son coût lorsque l’application sera maintenue dans la durée, partagée avec d’autres ou envoyée en production — quand « ça marche sur ma machine » ne suffit plus. En deçà de ce seuil, un simple app.R est le bon outil, et empaqueter relève de la sur-ingénierie. C’est le même jugement que porte l’ouvrage de référence, R Packages de Wickham et Bryan, sur le moment où un package en vaut la peine.
Vous tirerez le meilleur de cette leçon si vous êtes à l’aise pour écrire des fonctions et avec le patron des modules Shiny — les paires NS() / moduleServer() qui deviennent les fonctions R/ de votre package. Vous devez aussi avoir installé l’outillage de développement :
install.packages(c("usethis", "devtools", "roxygen2"))usethis échafaude et édite les fichiers du package, roxygen2 transforme les commentaires #' en documentation et en NAMESPACE, et devtools enveloppe le flux de travail build/check/install. C’est la chaîne d’outils standard des packages R (usethis, roxygen2).
create_package() écrit un vrai projet sur le disque et les commandes de build pilotent une session R active, donc rien sur cette page n’est exécuté ici — une page web statique ne peut ni échafauder un package ni en installer un. Copiez n’importe quel bloc sur votre machine et exécutez-le. La preuve, c’est l’application qui tourne : lorsque devtools::load_all() charge vos fonctions et que myapp::run_app() lance l’application après l’installation du package, vous avez montré que la structure fonctionne de bout en bout. The runtime is the judge.
Créer le package
Une seule commande échafaude tout. Pointez create_package() vers un chemin — le dernier élément du chemin devient le nom du package (lettres, chiffres et points, commençant par une lettre) :
usethis::create_package("path/to/myapp")Cela écrit un package minimal mais complet et, dans RStudio, l’ouvre comme un nouveau projet. Voici la structure qu’il produit, affichée avec fs::dir_tree() (qui imprime un répertoire sous forme d’arborescence de texte) :
fs::dir_tree("path/to/myapp")myapp
├── DESCRIPTION # package metadata + dependency list
├── NAMESPACE # what the package exports and imports (generated)
├── R # your R source code lives here
└── myapp.Rproj # RStudio project file
Deux fichiers font le travail important. DESCRIPTION (un nom de fichier, pas un acronyme) contient l’identité du package et — c’est crucial — sa liste de dépendances. NAMESPACE déclare quelles fonctions le package rend publiques (exports) et lesquelles il emprunte à d’autres packages (imports). Le DESCRIPTION de départ ressemble à ceci :
Package: myapp
Title: What the Package Does (One Line, Title Case)
Version: 0.0.0.9000
Authors@R:
person("First", "Last", , "first.last@example.com", role = c("aut", "cre"))
Description: What the package does (one paragraph).
License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a license.
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3
Vous éditez Title, Description et Authors@R à la main, et vous définissez la licence avec une fonction utilitaire plutôt qu’en tapant l’espace réservé :
usethis::use_mit_license() # writes a LICENSE file + sets the License: fielduse_mit_license() (ou use_gpl3_license(), et consorts) écrit le fichier de licence et remplit correctement le champ License: — un seul appel au lieu d’un espace réservé que vous rempliriez de travers.
Fonctions, roxygen et NAMESPACE
Le code de votre application vit dans R/*.R sous forme de fonctions. Le package décide lesquelles de ces fonctions le monde extérieur peut appeler, et c’est ce que le NAMESPACE consigne. Vous n’écrivez pas le NAMESPACE à la main — vous annotez chaque fonction avec un commentaire roxygen et laissez une commande le générer.
Placez une fonction dans R/hello.R avec une balise #' @export au-dessus :
#' Say hello from the app
#'
#' @return A greeting string.
#' @export
hello_app <- function() {
"Hello from myapp"
}Les lignes #' sont des commentaires roxygen — de la documentation structurée qui vit juste à côté du code. La balise @export est la plus importante : elle marque hello_app() comme faisant partie de l’interface publique du package. Générez maintenant le NAMESPACE et les fichiers d’aide à partir de ces commentaires :
devtools::document() # roxygen2 writes NAMESPACE + man/*.Rd from the #' commentsAprès document(), le NAMESPACE contient une seule ligne — export(hello_app) — et man/hello_app.Rd contient la page d’aide générée. Les fonctions sans @export restent internes : appelables à l’intérieur du package, invisibles à l’extérieur. Cette séparation public/privé est la raison d’être des espaces de noms — un dossier de fichiers source()d déverse chaque fonction dans l’environnement global où tout peut être écrasé ; un package trace une ligne claire entre l’interface que vous promettez de garder stable et les rouages internes que vous êtes libre de modifier.
Pour charger le package pendant le développement — sans installation nécessaire — utilisez :
devtools::load_all() # simulate installing + attaching the package
hello_app() # #> "Hello from myapp"load_all() rend chaque fonction de R/ disponible exactement comme si le package était installé, de sorte que votre boucle éditer-exécuter est rapide : modifiez une fonction, load_all(), appelez-la. Cet appel qui renvoie sa valeur est la preuve à l’exécution que le package est correctement câblé.
Suivre les dépendances
Voici le gain qu’un dossier source()d ne peut pas vous offrir : une liste de dépendances enregistrée. Lorsque votre code utilise un autre package, vous le déclarez dans DESCRIPTION afin que l’installation de votre package entraîne ce dont il a besoin. use_package() fait cela :
usethis::use_package("shiny") # adds shiny to DESCRIPTION's ImportsCela écrit shiny dans le champ Imports: du DESCRIPTION. Désormais, quiconque installe myapp obtient automatiquement shiny — fini le « il manque trois packages sur une machine neuve ». Ajoutez chaque package que votre application utilise réellement (use_package("DT"), use_package("bslib"), et ainsi de suite).
Il existe une règle complémentaire dans le code d’un package : référez-vous aux fonctions d’un autre package via pkg::fn(), plutôt qu’en appelant library(pkg) à l’intérieur de vos fonctions. Écrivez shiny::fluidPage(), pas library(shiny) puis fluidPage(). Lister le package dans Imports le rend disponible ; le préfixe :: indique exactement d’où provient chaque fonction. Appeler library() dans une fonction de package est une erreur de débutant courante — ça n’a pas sa place là, et R CMD check le signalera.
L’application en tant que package
Jusqu’ici, il s’agit de construction de package générique. Voici le geste propre à Shiny : enveloppez votre application dans une fonction de lancement exportée. Placez votre UI et votre serveur dans R/, puis exportez un run_app() qui assemble et renvoie l’objet application Shiny :
#' The application UI
#' @noRd
app_ui <- function() {
shiny::fluidPage(
shiny::titlePanel("myapp"),
shiny::textOutput("greeting")
)
}
#' The application server
#' @noRd
app_server <- function(input, output, session) {
output$greeting <- shiny::renderText("Hello from myapp")
}
#' Launch the application
#' @return A Shiny app object.
#' @export
run_app <- function() {
shiny::shinyApp(ui = app_ui(), server = app_server)
}La balise @noRd sur app_ui() et app_server() les garde internes (documentées pour vous, non exportées) — seul run_app() est public. Parce que run_app() renvoie ce que renvoie shiny::shinyApp() (un objet application Shiny), installer le package et appeler myapp::run_app() démarre l’application depuis n’importe où sur la machine — aucun répertoire de travail où faire setwd(), aucun fichier à source(). Cette unique fonction exportée est le point d’entrée public de l’application.
Les ressources statiques — CSS, JavaScript, images — vont dans le répertoire inst/ du package (son contenu est installé aux côtés du package). Un fichier situé à inst/app/www/styles.css est retrouvé à l’exécution avec system.file(), qui se résout vers l’emplacement d’installation quel que soit l’endroit où réside le package :
system.file("app/www", package = "myapp") # the installed path to your assetsUtilisez system.file() pour les chemins des ressources plutôt que des chemins relatifs comme "inst/app/www" — le préfixe inst/ disparaît à l’installation, et seul system.file() retrouve les fichiers de façon fiable sur la machine de quelqu’un d’autre.
Tester, vérifier, installer, distribuer
L’application une fois empaquetée, vous disposez de toute la chaîne d’outils de qualité. Intégrez des tests avec testthat :
usethis::use_testthat() # set up the tests/testthat/ infrastructure (once)
usethis::use_test("app") # create tests/testthat/test-app.R to fill inuse_testthat() crée le répertoire de tests et use_test("app") ouvre un fichier test-app.R où écrire vos assertions ; devtools::test() exécute la suite. Validez l’ensemble du package avec le même contrôle que celui utilisé par CRAN :
devtools::check() # runs R CMD check: docs, dependencies, tests, examplescheck() exécute R CMD check — le validateur exhaustif qui détecte les dépendances manquantes, les exports non documentés, les library() à l’intérieur des fonctions, les exemples cassés et des dizaines d’autres problèmes avant qu’ils n’atteignent un utilisateur. Un check() propre (aucune erreur, aucun avertissement, aucune note) est la barre standard d’un package livrable. Installez-le localement :
devtools::install() # build + install into your R libraryAprès install(), myapp::run_app() fonctionne dans n’importe quelle session R.
La distribution emprunte trois voies courantes, à peu près par ordre d’effort :
| Voie | Comment l’utilisateur installe | Idéal pour |
|---|---|---|
| GitHub | remotes::install_github("you/myapp") |
Partage avec une équipe ou la communauté, itération rapide |
| Dépôt interne | install.packages("myapp", repos = ...) |
Le serveur de packages privé d’une organisation |
| CRAN | install.packages("myapp") |
Un package stable et largement utile (une revue formelle + des politiques à respecter) |
Pour la plupart des applications Shiny, GitHub est la réponse : poussez le package, et un collègue exécute remotes::install_github("you/myapp") pour obtenir le code exact. CRAN est un engagement plus lourd — une soumission formelle et la politique CRAN à respecter — et n’en vaut la peine que pour un package destiné à une large réutilisation, ce que la plupart des applications Shiny internes ne sont pas.
golem automatise tout cela
Tout ce qui précède est la version construite à la main, et cela vaut la peine d’être connu car c’est ce qui se passe réellement. Mais dès lors que vous construisez régulièrement des applications de production, vous ne voudrez pas exécuter create_package(), câbler run_app() et gérer les dépendances à la main à chaque fois. golem automatise exactement ce flux de travail : golem::create_golem() échafaude le package avec la structure app_ui() / app_server() / run_app() déjà en place, ses scripts dev/ pilotent la boucle build/document/check, attachment::att_amend_desc() maintient à jour les dépendances du DESCRIPTION à votre place, et golem génère vos fichiers de déploiement par-dessus. Les mécanismes sont identiques à ce que vous venez d’apprendre — golem vous évite simplement de les taper. Apprenez la version manuelle ici pour comprendre le package que golem vous remet ; recourez à golem lorsque vous voulez le même résultat sans les étapes manuelles.
Problèmes fréquents
Vous avez ajouté #' @export mais la fonction reste introuvable. Ajouter la balise ne suffit pas — le NAMESPACE n’est régénéré que lorsque vous exécutez devtools::document(). Éditez le commentaire roxygen, puis exécutez document() (et load_all() pour prendre en compte le changement). Si myapp::my_function() échoue avec « object not found », un document() non exécuté en est la cause habituelle.
Votre application fonctionne localement mais échoue sur la machine d’un collègue — un package ne figure pas dans DESCRIPTION. Si votre code appelle un package que vous n’avez jamais déclaré avec use_package(), il fonctionne chez vous (le package est installé sur votre machine) mais échoue ailleurs avec « there is no package called ‘…’ ». La solution : exécutez usethis::use_package("<pkg>") pour chaque package que votre code utilise, afin que la dépendance voyage avec votre package. devtools::check() en détecte la plupart avant la livraison.
Vous avez appelé library(shiny) à l’intérieur d’une fonction de package. Dans le code d’un package, chargez les fonctions des autres packages avec le préfixe pkg::fn() (shiny::fluidPage()) et déclarez le package dans Imports via use_package() — jamais library() à l’intérieur d’une fonction. library() a sa place dans les scripts d’analyse, pas dans les packages ; R CMD check le signale, et cela peut subtilement modifier quelles fonctions sont visibles à l’exécution.
Questions fréquentes
Un package vous apporte quatre choses qu’un dossier de fichiers source()d ne peut pas : une liste de dépendances enregistrée (pour qu’il s’installe correctement sur n’importe quelle machine), une interface publique contrôlée via le NAMESPACE, la validation R CMD check (le même contrôle que celui exécuté par CRAN) et une installation en une ligne pour que n’importe qui puisse le lancer avec myapp::run_app(). C’est la façon propre à R de rendre une application partageable, vérifiable et fiable — cela en vaut la peine dès lors que l’application est maintenue dans la durée, partagée ou envoyée en production (R Packages).
Exécutez usethis::create_package("path/to/myapp") pour échafauder le package, placez votre UI et votre serveur dans R/, et exportez un lanceur : une fonction run_app() marquée #' @export qui renvoie shiny::shinyApp(ui = app_ui(), server = app_server). Ajoutez les dépendances avec usethis::use_package("shiny"), exécutez devtools::document() pour générer le NAMESPACE, puis devtools::install(). Ensuite, myapp::run_app() démarre l’application depuis n’importe où.
DESCRIPTION est les métadonnées et la liste de dépendances du package — son nom, sa version, son auteur, sa licence, et le champ Imports: qui nomme chaque package dont il a besoin ; vous l’éditez (souvent via des fonctions utilitaires usethis comme use_package()). NAMESPACE déclare l’interface du package — quelles fonctions il exporte (rend publiques) et importe d’autres packages ; vous ne l’éditez pas à la main, devtools::document() le génère à partir de vos balises roxygen #' @export. En bref : DESCRIPTION = ce dont le package a besoin, NAMESPACE = ce que le package expose.
Trois voies. GitHub est la plus courante : poussez le package et un utilisateur exécute remotes::install_github("you/myapp"). Un dépôt de packages interne le dessert au sein d’une organisation avec install.packages("myapp", repos = ...). CRAN (install.packages("myapp")) est destiné aux packages largement réutilisables et exige de passer une revue formelle plus la politique CRAN. Après l’une ou l’autre, l’utilisateur lance l’application avec myapp::run_app().
Apprenez les mécanismes manuels usethis/roxygen — create_package(), @export, use_package(), un run_app() exporté — car c’est cela, empaqueter, et c’est suffisant pour une application simple. Recourez à golem lorsque vous construisez régulièrement des applications de production : il échafaude automatiquement la même structure de package (create_golem() écrit le squelette app_ui/app_server/run_app), vous donne un flux de travail dev/ reproductible, garde les dépendances du DESCRIPTION synchronisées et génère les fichiers de déploiement. golem automatise cette leçon ; il ne remplace pas le fait de la comprendre.
Testez vos connaissances
Vous avez une application fonctionnelle dans un dossier — app.R plus un helpers.R que vous chargez avec source() — et vous voulez la livrer sous forme de package nommé dashapp pour qu’un collègue puisse l’installer et l’exécuter. Écrivez les commandes pour (1) échafauder le package, (2) déclarer sa dépendance envers shiny, et (3) esquisser le lanceur exporté. Puis nommez la commande qui génère le NAMESPACE, et comment votre collègue lancerait l’application installée.
L’échafaudage se fait avec usethis::create_package() en donnant un chemin dont le dernier élément est le nom du package. Déclarez une dépendance avec usethis::use_package(). Le lanceur est une fonction marquée #' @export qui renvoie shiny::shinyApp(...). Le NAMESPACE est généré par devtools::document() — il ne s’écrit pas à la main. La fonction exportée d’un package installé s’appelle via pkg::fn().
# 1. Scaffold the package
usethis::create_package("path/to/dashapp")
# 2. Declare the dependency (adds shiny to DESCRIPTION Imports)
usethis::use_package("shiny")
# 3. In R/run_app.R — the exported launcher (UI + server in R/ too)
#' @export
run_app <- function() {
shiny::shinyApp(ui = app_ui(), server = app_server)
}Déplacez l’UI de votre application dans une fonction app_ui() et son serveur dans une fonction app_server dans R/ (marquées #' @noRd pour qu’elles restent internes), et placez aussi les fonctions de helpers.R dans R/. Générez ensuite le NAMESPACE et installez :
devtools::document() # writes NAMESPACE from the #' @export tags
devtools::install() # build + install locallyVotre collègue l’installe (depuis GitHub, remotes::install_github("you/dashapp")) et le lance avec dashapp::run_app() — pas de source(), aucun répertoire de travail à définir. document() est ce qui transforme votre balise @export en la ligne export(run_app) dans le NAMESPACE.
Vous ajoutez #' @export au-dessus d’une nouvelle fonction my_helper() dans R/utils.R, mais l’appel de myapp::my_helper() échoue avec « object not found ». Quelle est la cause la plus probable ?
A. Vous devez ajouter my_helper au fichier NAMESPACE à la main.
B. Vous n’avez pas exécuté devtools::document() depuis l’ajout de la balise, donc le NAMESPACE n’a pas été régénéré.
C. #' @export ne fonctionne que sur les fonctions d’un fichier nommé exports.R.
D. Les fonctions exportées doivent être listées dans DESCRIPTION, pas dans NAMESPACE.
B. La balise #' @export est un commentaire — elle ne fait rien tant que devtools::document() ne l’a pas lue et n’a pas régénéré le NAMESPACE (en écrivant la ligne export(my_helper)). Exécutez document() (et load_all() pour recharger), et la fonction devient visible. A est faux car on n’édite jamais le NAMESPACE à la main — document() en est responsable. C est inventé : @export fonctionne sur n’importe quelle fonction dans n’importe quel fichier R/*.R. D confond les deux fichiers — l’interface (les exports) réside dans NAMESPACE ; DESCRIPTION contient les dépendances.
Conclusion
Transformer une application Shiny en package, c’est passer de « ça marche sur ma machine » à « ça s’installe et s’exécute partout ». usethis::create_package() échafaude la structure ; vous placez votre UI, votre serveur et vos utilitaires dans R/, marquez le lanceur avec #' @export, et laissez devtools::document() écrire le NAMESPACE à partir de vos balises roxygen. usethis::use_package() enregistre chaque dépendance dans DESCRIPTION pour que l’application s’installe complètement ailleurs, un run_app() exporté renvoyant shiny::shinyApp(...) fait de myapp::run_app() le point d’entrée de l’application, et devtools::check() plus install() la valident et la livrent. Voilà la fondation construite à la main — la structure exacte que le framework golem génère pour vous lorsque vous préférez ne plus le faire à la main. Empaquetez l’application, et the runtime is the judge : load_all() et run_app() prouvent qu’elle fonctionne.
Leçons connexes
- Bâtir sur l’empaquetage et autour : la leçon modules Shiny enseigne les paires
NS()/moduleServer()qui deviennent les fonctionsR/de votre package — la primitive de réutilisation autour de laquelle une application empaquetée s’organise — et la leçon framework golem montre commentcreate_golem()automatise le fluxcreate_package()→run_app()→document()que vous venez d’apprendre à la main, en y ajoutant une boucle de développement reproductible et des générateurs de fichiers de déploiement. La question plus large de l’endroit où vivent les fichiers dans un projet qui grandit relève de l’organisation du code, traitée séparément dans cette série. - Aller plus loin : Applications web Shiny — le cursus Shiny complet. · Science computationnelle et IA — le pilier.
Prouvez que vous savez le faire. Maîtrisez toute la série Architecture Shiny — suivez votre parcours, construisez des projets et obtenez un certificat.
Passez à Pro — Prova illimité sur vos propres données et un certificat vérifiable qui atteste la compétence.
dès 15 $/mois facturé annuellement
✓ Vous êtes Pro — continuez. The runtime is the judge.
Réutilisation
Citation
@online{2026,
author = {},
title = {Développement de package Shiny : structurez votre application
en package R},
date = {2026-07-16},
url = {https://www.datanovia.com/learn/programming/shiny/architecture/package-development},
langid = {fr}
}