將Shiny APP搭建爲獨立的桌面可執行程序 - Deploying R shiny app as a standalone application

起源!

某天,我發現了Shiny這個東西,當時興沖沖的嘗試官網上各類各樣的例子,最後發現這個東西彷佛只能充當一個「玩具」。若是要在本地運行,它須要一個完整的R環境,這對至關一部分用戶來講是極度不友好的。另外,Rstudio主張將Shiny部署在https://www.shinyapps.io/,可是看到這個價格以及資源限制之後進一步被勸退了。html

畢竟不少科研工做者的出發點是將本身的研究過程和結果分享展現給他人,而不是出於商業的目的,部署在服務器上供他人使用須要持續投入計算資源和維護成本,這不是長久之計。node


目的?

那麼,若是咱們實現了一個精妙的Shiny App,如何0成本的分享給別人,且別人可以方便的使用呢?爲了達到這個目的,最好的結果是將R中的Shiny App轉換爲一個獨立運行的exe文件,也就是一個這樣的桌面應用:react

例1

例2

對,我實現了,過程當中仍是踩了一些坑的,如今就把具體的方法分享給你們。這是我本身思考的方法,由於本人也是剛開始研究,可能還有些地方理解的不是很清楚,若是您有更好的建議,懇請不吝斧正。git

剛開始我是看了這個stone大神寫的貼做爲啓蒙:https://zhuanlan.zhihu.com/p/121003243,可是我沒能在本身電腦上實現,由於electricShine這個東西是一個寫死的包,寫死既被動,在調用npm的時候總會有小小的問題致使全盤失敗。雖然沒有成功實現,可是我確定是不服的。後來我又看了某機構的博客:https://foretodata.com/how-to-make-a-standalone-desktop-application-with-shiny-and-electron-on-windows/,感受上可行,嘗試之後發現跑通了,確實能夠。可是以上都很差做爲最終的解決方案。github

那麼一個最爲方便且易於實現的思路是這樣的:shell

  • 安裝R-Portable做爲開發、部署、分發的R環境
  • 在上述環境中開發ShinyApp(推薦使用golem)
  • 經過electron-quick-start將R-Portable和ShinyApp打包成exe
    該方法基於Windows實現了打包exe,理論上能夠在mac上實現打包dmg

怎麼作?

0 準備工做

  • 熟悉R及Rstudio
  • 熟悉命令行操做
  • 瞭解Shiny App及其基本結構
  • 肯定了解咱們的目的
  • 新建一個工做目錄C:\myShinyApp

1 下載安裝R-portable

連接:https://sourceforge.net/projects/rportable/files/R-Portable/3.6.3/npm

強烈建議這個3.6.3版本,比較穩定,4.0.0編譯暫時有問題。windows

安裝比較簡單,注意將路徑設置爲咱們新建的工做目錄,安裝完成便可。bash

2 配置 Rstudio

如今咱們要開啓R-Portable做爲R環境
打開Rstudio,鼠標點:Tools>Global Options>General>Change R version>Browse
定位咱們剛纔安裝的R-Portable路徑(C:\myShinyApp\R-Portable\App\R-Portable)
而後點選擇文件夾,選擇64位版本服務器

一路點OK,最後重啓Rstudio
.libPaths()裏有咱們剛纔裝好的R-Portable就行了:

> .libPaths()
[1] "C:/Users/XXX/Documents/R/win-library/3.6"   
[2] "C:/myShinyApp/R-Portable/App/R-Portable/library"

注意:這裏出現了兩個路徑,[1]是我原來就有的,[2]是剛裝的,ShinyApp中全部要用到的包必須裝在[2]裏。

3 搭建Shiny App

golem包是開發Shiny App的輔助開發工具,用它可讓開發過程更加方便。
先在Rstudio中安裝這個包:

install.packages('golem',dependencies = T)

安裝完成後,在Rstudio中點菜單:File>New Project>New Directory>Package for Shiny App using golem

將Directory name隨意設置爲shinyapptest,路徑定位到咱們的工做目錄

建立完成後,咱們就在Rstudio中開闢了一個新的Project和工做環境,且工做目錄出現了一個相似於R包的結構:

根據golem的Document,咱們主要關注./dev中的三個腳本01_start.R02_dev.R03_deploy.R以及./R中的三個腳本app_ui.Rapp_server.Rrun_app.R

假如咱們如今要實現文章開頭例2提到的csv表格查看器。

3.1 添加模塊

載入csv文件的按鈕就是一個模塊(按鈕自己是模塊的UI,讀取csv文件是這個模塊的功能),咱們運行./dev/02_dev.R中的add_module添加一個模塊

## Add modules ----
## Create a module infrastructure in R/
golem::add_module( name = "csv_file" ) # Name of the module

結果./R路徑下生成了一個以mod_爲前綴的模塊文件,

mod_csv_file.R這個文件的內容改爲這樣的:

#' csv_file UI Function
#' @description A shiny Module.
#' @param id,input,output,session Internal parameters for {shiny}.
#' @noRd 
#' @importFrom shiny NS tagList 
mod_csv_file_ui <- function(id, label = "CSV file"){
  ns <- NS(id)
  tagList(
    fileInput(ns("file"), label),
    checkboxInput(ns("heading"), "Has heading"),
    selectInput(ns("quote"), "Quote", c(
      "None" = "",
      "Double quote" = "\"",
      "Single quote" = "'"
    ))
  )
}

#' csv_file Server Function
#' @noRd 
mod_csv_file_server  <- function(id, stringsAsFactors) {
  moduleServer(
    id,
    ## Below is the module function
    function(input, output, session) {
      # The selected file, if any
      userFile <- reactive({
        # If no file is selected, don't do anything
        validate(need(input$file, message = FALSE))
        input$file
      })
      # The user's data, parsed into a data frame
      dataframe <- reactive({
        read.csv(userFile()$datapath,
                 header = input$heading,
                 quote = input$quote,
                 stringsAsFactors = stringsAsFactors)
      })
      # We can run observers in here if we want to
      observe({
        msg <- sprintf("File %s was uploaded", userFile()$name)
        cat(msg, "\n")
      })
      # Return the reactive that yields the data frame
      return(dataframe)
    }
  )    
}

模塊的定義包含兩個部分:mod_csv_file_ui 定義模塊UI,mod_csv_file_server 定義模塊功能,若是要使用這個模塊只需在Shiny App的app_ui中調用前者,app_server中調用後者就能夠了。

3.2 寫AppUI和AppServer

咱們將app_ui.R改成這樣的:

#' The application User-Interface
#' @param request Internal parameter for `{shiny}`. 
#'     DO NOT REMOVE.
#' @import shiny
#' @noRd
app_ui <- function(request) {
  tagList(
    # List the first level UI elements here 
    fluidPage(
      sidebarLayout(
        sidebarPanel(
          mod_csv_file_ui("datafile", "User data (.csv format)") # 調用模塊UI
        ),
        mainPanel(
          dataTableOutput("table")
        )
      )
    )
  )
}

爲了節省空間我把golem導入外部資源的部分去除了。
而後將app_server.R改爲這樣的:

#' The application server-side
#' @param input,output,session Internal parameters for {shiny}. 
#'     DO NOT REMOVE.
#' @import shiny
#' @noRd
app_server <- function(input, output, session) {
  datafile <- mod_csv_file_server("datafile", stringsAsFactors = FALSE) # 調用模塊function
  output$table <- renderDataTable({
    datafile()
  })
}

3.3 測試App

改好這些文件之後咱們在./dev/run_dev.R腳本中測試一下咱們的Shiny App:

> # Detach all loaded packages and clean your environment
> golem::detach_all_attached()
錯誤: $ operator is invalid for atomic vectors
此外: Warning message:
In FUN(X[[i]], ...) :
  DESCRIPTION file of package 'shiny' is missing or broken

運行到上面這一條提示咱們尚未裝shiny這個包,那就裝吧:

install.packages(pkgs = 'shiny',
                 lib = .libPaths()[length(.libPaths())], # 保證裝到R-Portable的lib裏
                 dependencies = T) # 保證同時安裝依賴

再次運行這一條,發現成功了:

> # Detach all loaded packages and clean your environment
> golem::detach_all_attached()
>

最後運行run_app

# Run the application
library(golem)
library(shiny)
source('./R/app_server.R')
source('./R/app_ui.R')
source('./R/mod_csv_file.R')
source('./R/run_app.R')
run_app()

出現下面這個界面Shiny App基本上就成了,能夠打開一個csv文件本身測試一下。

3.4 打包Shiny App

假若有一天,咱們精妙的Shiny App終於大功告成了,那麼能夠將他打成package並安裝到R-Portable中。
先準備一下devtools:

if(!requireNamespace("devtools")){
  install.packages("devtools")
  library(devtools)
}

而後打包shinyapp,路徑爲當時golem建立的項目路徑:

devtools::build(path = "C:/myShinyApp/shinyapptest")
√  checking for file 'C:\myShinyApp\shinyapptest/DESCRIPTION' ...
-  preparing 'shinyapptest':
√  checking DESCRIPTION meta-information ... 
-  checking for LF line-endings in source and make files and shell scripts
-  checking for empty or unneeded directories
-  building 'shinyapptest_0.0.0.9000.tar.gz'
[1] "C:/myShinyApp/shinyapptest/shinyapptest_0.0.0.9000.tar.gz"

安裝這個打包成功的packageshinyapptest_0.0.0.9000.tar.gz

install.packages(
  pkgs = 'C:/myShinyApp/shinyapptest/shinyapptest_0.0.0.9000.tar.gz',
  lib = .libPaths()[length(.libPaths())],
  repos = NULL, # 這個參數必定要的
  dependencies = T
)

# 嘗試用包直接運行app
shinyapptest::run_app()

shiny具體的開發文檔仍是要研究一下:https://shiny.rstudio.com/articles/。好了,R的工做完成了剩下的交給electron-quick-start。

4 安裝並配置node.js

4.1 下載解壓

去這個連接下載zip壓縮文件:https://nodejs.org/download/release/v12.16.2/node-v12.16.2-win-x64.zip
我裝的是v12.16.2版本,若是嫌下載慢的話,想一想辦法,這裏我分享一個網盤給大家:
連接: https://pan.baidu.com/s/1QbLJcfhRqTsgUeQ10Wy7wA
提取碼: 4gzh
這是解壓版,安裝版也是同理的。下載完成後解壓到指定目錄,能夠是咱們的工做目錄,解壓完之後是這樣的:

4.2 配置環境變量

在這個目錄中新建兩個文件夾node_globalnode_cache

新建一個系統變量,變量名是NODE_PATH,值是nodejs的解壓或安裝目錄C:\myShinyApp\node-v12.16.2-win-x64

新建另外一個關鍵的系統變量,變量名是NODE_TLS_REJECT_UNAUTHORIZED,值是0,我以爲這個變量很關鍵:

編輯Path環境變量,新建這兩個值:C:\myShinyApp\node-v12.16.2-win-x64C:\myShinyApp\node-v12.16.2-win-x64\node_global(忽略圖中的大小寫筆誤)
image.png

4.3 配置npm參數

如今,以管理員身份打開優秀的Windows Powershell,檢查node和npm是否安裝正常:

> node -v
v12.16.2
> npm -v
6.14.4

配置一些必要的npm參數:

> npm config set prefix "C:\myShinyApp\node-v12.16.2-win-x64\node_global"
> npm config set cache "C:\myShinyApp\node-v12.16.2-win-x64\node_cahce"
> npm config set strict-ssl false
> npm config set registry http://registry.npm.taobao.org/

4.4 安裝 electron-packager

以上配置就是爲了可以成功安裝這個包

> npm install electron-packager -g

# 出現如下信息說明成功
# + electron-packager@15.2.0
# added 18 packages from 9 contributors, removed 10 packages and updated 8 packages in 4.188s

5 使用electron-quick-start模板

若是方便在命令行用git的話(我通常是用WSL+Cmder),就先cdC:\myShinyApp\electron-quick-start,而後clone項目:

$ git clone https://github.com/listen2099/electron-quick-start.git

若是不方便用git,就直接下載鏈接中的zip文件解壓到C:\myShinyApp\electron-quick-starthttps://github.com/listen2099/electron-quick-start/archive/master.zip
拉取或解壓成功後:

再次以管理員身份打開優秀的Windows Powershell:

> cd C:\myShinyApp\electron-quick-start
> npm install

# 出現如下信息就明名安裝成功
# > electron@5.0.7 postinstall C:\myShinyApp\electron-quick-start\node_modules\electron
# > node install.js
# added 148 packages from 139 contributors in 4.326s

接下來是關鍵的一步:
將R-Portable路徑C:\myShinyApp\R-Portable\App\R-Portable下的全部文件複製並替換C:\myShinyApp\electron-quick-start\R-Portable-Win路徑:

?還記得嗎?這個環境裏有咱們安裝好的R環境、寫好的ShinyApp以及依賴的R包(其實,ShinyApp也做爲包安裝在這個R環境了,依稀記得包名叫shinyapptest)。

回到C:\myShinyApp\electron-quick-start,編輯這個目錄下的app.R文件,這個文件是程序的入口,那麼你猜這個文件應該寫什麼?要不就試試寫這一行內容保存:

# app.R
shinyapptest::run_app()

最後一次打開優秀的Windows Powershell,完成最後的打包

> cd C:\myShinyApp\electron-quick-start
> npm run package-win

# 出現如下信息就說明成功了
# Packaging app for platform win32 ia32 using electron v5.0.7
# Wrote new app to ElectronShinyAppWindows\electron-quick-start-win32-ia32

6 完成

C:\myShinyApp\electron-quick-start文件夾下出現了一個新的目錄:

雙擊exe文件:

成功!

相關文章
相關標籤/搜索