Hi Paul,
Small world! I'm currently building a stand alone webpage that uses cytoscape.js to display shiny reactivity dependencies. Cytoscape.js is a very smooth network library!
Repo: https://github.com/rstudio/shinyreactlog
shinyreactlog is not a shiny app, but it does provide as an example on how to assemble the javascript. I've tried to make the package have file structures that work both with R packages and npm-like packages (I use yarn). I do not enjoy keeping one set of code in a non standard location, but rather both sets of code in the root repo directory. The yarn package.json file keeps the versions and dependencies listed and gives me a single location to update dependency versions.
shinyreactlog contains:
Building:
While maybe overkill, I use grunt to call webpack to bundle the app. The grunt workflow makes more sense to me over a raw webpack config and is nice to run multiple / different tasks if I have more tasks to be run. For now, it is a single build task and a single watch task. Inside my build task, I call webpack on my ./src/index.js file that imports the local flowtype es6 javascript files. Webpack can automatically read from the ./node_modules directory where all of the external dependencies are stored and automatically performs light weight dead code tree shaking to provide the smallest bundle file. By adding in the commented BundleAnalyzerPlugin plugin in the webpack config, I can inspect where the bulk of my javascript file is coming from. Babel (called from webpack) is happen when the source files are located in the src
folder. I store the output bundle in the ./inst folder to be able to be found as an external file in the R package bundle (system.file("reactlogAsset/reactlog.js", package = "shinyreactlog")
). Any javascript configuration file is placed in the top level folder and ignored in .Rbuildignore
.
./package.json:
I have many scripts that I have found to be useful inside my ./package.json file. This allows me to type yarn watch
or yarn lint-all
without having to populate my $PATH
for a local project. For more complicated scripts, I've made executable files in the ./bin folder. I have the package.json field "private": true,
as there is no intention of releasing it as a yarn/npm package.
Given that all dependencies are in the package.json (paired with a yarn.lock file), anyone who downloads the repo and calls yarn install
in the base folder will be able to yarn build
the javascirpt bundle or yarn watch
for changes during development.
Always commit linted code:
By installing the dev-dependencies husky and lint-staged, scripts execute (that must succeed) for matching files being committed before any git commit is allowed. In shinyreactlog's pre-commit script, it makes sure all javascript files pass the lint check, pass a prettier code formatting check, and do not break flowtype. If all three pass, the matching files are added to git (may have been altered in the linting process) and the commit is accepted.
Non-standard dependencies:
Sometimes extra libraries do not have a local package.json file. To keep these non-standard dependencies, the napa package can be used to install git repos using a commitish tag. leaflet.extras does this with three packages. I used full commit tags rather than the branch name only to enforce explicit version control. You can install an unpublished, standard package (such as a fork with extra changes) using a github commitish tag instead of a version in the dependency. leaflet.extras does this with seven packages.
To require these dependencies, you can import the javascript file directly. For example to import jquery like normal import $ from "jquery"
, but to explicitly import the jquery.slim.js file in the ./dist folder of jquery, import $ from "jquery/dist/jquery.slim"
.
htmlwidget:
Down the road, I imagine that shinyreactlog could be an htmlwidget. If so, the bundled code could be placed in the (standard practice) folder of inst/htmlwidgets
along with a shinyreactlog.yaml file describing the single javascript bundle dependency. This would be similar to how leaflet produces a htmlwidget, except the folder would only contain the yaml, a single bundled javascript file, and a single css file (with no extra libraries).
shiny application:
Once the javascript is bundled, it should be placed in the www folder of your shiny application
├── server.R
├── ui.R
└── www
└── myJsBundle.js
Inside your ui.R file, include the bundle file with: tags$head(tags$script(src="myJsBundle.js"))
. For more communication between shiny and javascript, I defer to Joe's article: https://shiny.rstudio.com/articles/communicating-with-js.html
I have briefly touched on MANY difficult topics. If you would like more information on any of them, I'd be happy to expand on any of them!
Please let me know if you have any more questions!
Best,
Barret