# Tutorial: Porting Blueprint to Shiny

### Introduction

Blueprint is a React-based UI library from Palantir. It provides a rich set of components for building web interfaces and it is similar in concept to Microsoft’s Fluent UI or Google’s MUI.

In this tutorial we will (begin to) create a `blueprint` R package, which will make it possible to use Blueprint in R/Shiny akin to how shiny.fluent does it for Fluent UI. It should give you enough understanding of shiny.react to allow you to use other React libraries in your projects, either by creating “wrapper” R packages or directly in you Shiny app.

This tutorial is aimed at advanced users who feel comfortable with both Shiny and React. You will need R and Node.js installed.

### Creating the package

To start off we create a new package called blueprint. The `js` directory will contain the Node.js toolchain and JavaScript sources which will be compiled into a single file. Only that file will be needed to use the package, so we add `js` to `.Rbuildignore` to decrease the size of our package.

    usethis::create_package("blueprint")
    usethis::use_build_ignore("js")

It is also a good idea to list the dependencies in the `DESCRIPTION` file:

    Imports:
      htmltools,
      shiny,
      shiny.react

### The R interface

In React, a component is a function which takes props and returns an element. These concepts map to R directly.

In R, elements are created with `shiny.react::reactElement(module, name, props)`. In the browser, shiny.react will create the element by calling `React.createElement(jsmodule[module][name], props)`. It is our task to ensure that `jsmodule[module][name]` yields the right component. To accomplish it, we will later create a `blueprint.js` script which will set up the `jsmodule` global appropriately.

To free the users of our package of having to include this script manually, we will use an HTML dependency. In `R/components.R` let’s define:

    blueprintDependency <- function() {
      htmltools::htmlDependency(
        name = "blueprint",
        version = "0.1.0",
        package = "blueprint",
        src = "www",
        script = "blueprint.js"
      )
    }

To define components succinctly, let’s create a helper. Remember - components are functions which take props and return elements:

    component <- function(name) {
      function(...) shiny.react::reactElement(
        module = "@blueprintjs/core",
        name = name,
        props = shiny.react::asProps(...),
        deps = blueprintDependency()
      )
    }

We can now add Blueprint components to our package easily! Let’s try a Switch and a ProgressBar for starters.

    #' @export
    ProgressBar <- component("ProgressBar")

    #' @export
    Switch <- component("Switch")

### Adding Blueprint

In the `js` directory we use `yarn` to add the Blueprint library. The documentation also suggests adding `react` and `react-dom`, but we skip them as they are already provided by shiny.react.

    yarn init --yes
    yarn add @blueprintjs/core

We will use a bundler to generate the `blueprint.js` script from the following `js/src/index.js` file:

    const Blueprint = require('@blueprintjs/core');

    require('@blueprintjs/core/lib/css/blueprint.css');

    window.jsmodule = {
      ...window.jsmodule,
      '@blueprintjs/core': Blueprint
    };

This script will make the Blueprint library available as `jsmodule[@blueprintjs/core]` on the browser. It will also load the necessary CSS.

### Bundling

We will use webpack to build the `blueprint.js` file.

There is a handy online tool which we can use to generate a configuration for that webpack. Let’s just pick CSS from the Styling section and copy the the script to `js/webpack.config.js`. We also add dev dependencies as suggested by the tool:

    yarn add --dev webpack webpack-cli css-loader style-loader

Now let’s tweak the config a bit. We change the output to `inst/www/blueprint.js`:

    output: {
      path: path.join(__dirname, '..', 'inst', 'www'),
      filename: 'blueprint.js'
    }

We add `externals` to let webpack know where to look for modules provided by shiny.react:

    externals: {
      'react': 'jsmodule["react"]',
      'react-dom': 'jsmodule["react-dom"]',
      '@/shiny.react': 'jsmodule["@/shiny.react"]'
    }

Lastly, we need a little hack for a problem present in Blueprint as of writing this:

    plugins: [
      new webpack.DefinePlugin({ 'process.env': '{}' })
    ]

Our final `js/webpack.config.js` looks as follows:

    const webpack = require('webpack');
    const path = require('path');

    const config = {
      entry: './src/index.js',
      output: {
        path: path.join(__dirname, '..', 'inst', 'www'),
        filename: 'blueprint.js'
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              'style-loader',
              'css-loader'
            ]
          }
        ]
      },
      externals: {
        'react': 'jsmodule["react"]',
        'react-dom': 'jsmodule["react-dom"]',
        '@/shiny.react': 'jsmodule["@/shiny.react"]'
      },
      plugins: [
        new webpack.DefinePlugin({ 'process.env': '{}' })
      ]
    };

    module.exports = config;

### Building the package

We are ready to build our package! First of all, we run webpack in the `js` directory:

    yarn webpack

This will generate the `inst/www/webpack.js` bundle. We should also generate the NAMESPACE file:

    devtools::document()

We can now install the package directly with `devtools::install()` and try it out!

### Using the package

Let’s try a simple app first to test our components:

    library(shiny)
    library(shiny.react)
    library(blueprint)

    shinyApp(
      ui = tagList(
        Switch(label = "Animate"),
        ProgressBar()
      ),
      server = function(input, output) {}
    )

Cool! Let’s try something more advanced:

    withDefault <- function(x, default) {
      if (is.null(x)) default
      else x
    }

    shinyApp(
      ui = tagList(
        Switch(
          onChange = JS("(event) => Shiny.setInputValue('animate', event.target.checked)"),
          defaultChecked = TRUE,
          label = "Animate"
        ),
        reactOutput("progress")
      ),
      server = function(input, output) {
        output$progress <- renderReact({
          ProgressBar(animate = withDefault(input$animate, TRUE))
        })
      }
    )

### Creating input wrappers

Even simple components can be cumbersome to use in Shiny, as evident in the last example. It is a good idea to create `.shinyInput` wrappers to simplify the life of your users.

We change our `js/src/index.js` to the following:

    const Blueprint = require('@blueprintjs/core');
    const { InputAdapter } = require('@/shiny.react')

    require('@blueprintjs/core/lib/css/blueprint.css');

    const Switch = InputAdapter(Blueprint.Switch, (value, setValue) => ({
      checked: value,
      onChange: (event) => setValue(event.target.checked),
    }));

    window.jsmodule = {
      ...window.jsmodule,
      '@blueprintjs/core': require('@blueprintjs/core'),
      '@/blueprint': { Switch }
    };

We also add these lines to `R/components.R`:

    input <- function(name, defaultValue) {
      function(inputId, ..., value = defaultValue) shiny.react::reactElement(
        module = "@/blueprint",
        name = name,
        props = shiny.react::asProps(inputId = inputId, ..., value = value),
        deps = blueprintDependency()
      )
    }

    #' @export
    Switch.shinyInput <- input("Switch", FALSE)

After rebuilding and reinstalling the package we can now rewrite the last Shiny app example as:

    shinyApp(
      ui = tagList(
        Switch.shinyInput(
          inputId = "animate",
          value = TRUE,
          label = "Animate"
        ),
        reactOutput("progress")
      ),
      server = function(input, output) {
        output$progress <- renderReact({
          ProgressBar(animate = input$animate)
        })
      }
    )

### Notes

The module name passed to `shiny.react::createElement()` can be arbitrary, but the following convention is recommended:

- For modules coming directly from npm, use the npm name, e.g. `@blueprintjs/core`.
- For modules with custom code, use the R package name with `@/` prefix, e.g. `@/blueprint`.