將Shiny APP搭建為獨(dú)立的桌面可執(zhí)行程序 - Deploying R shiny app as a standalone application

起源物臂!

某天旺拉,我發(fā)現(xiàn)了Shiny這個(gè)東西,當(dāng)時(shí)興沖沖的嘗試官網(wǎng)上各種各樣的例子棵磷,最后發(fā)現(xiàn)這個(gè)東西似乎只能充當(dāng)一個(gè)“玩具”账阻。如果要在本地運(yùn)行,它需要一個(gè)完整的R環(huán)境泽本,這對(duì)相當(dāng)一部分用戶來(lái)說(shuō)是極度不友好的。另外姻僧,Rstudio主張將Shiny部署在https://www.shinyapps.io/规丽,但是看到這個(gè)價(jià)格以及資源限制以后進(jìn)一步被勸退了。

畢竟很多科研工作者的出發(fā)點(diǎn)是將自己的研究過(guò)程和結(jié)果分享展示給他人撇贺,而不是出于商業(yè)的目的赌莺,部署在服務(wù)器上供他人使用需要持續(xù)投入計(jì)算資源和維護(hù)成本,這不是長(zhǎng)久之計(jì)松嘶。


目的艘狭?

那么,如果我們實(shí)現(xiàn)了一個(gè)精妙的Shiny App翠订,如何0成本的分享給別人巢音,且別人能夠方便的使用呢?為了達(dá)到這個(gè)目的尽超,最好的結(jié)果是將R中的Shiny App轉(zhuǎn)換為一個(gè)獨(dú)立運(yùn)行的exe文件官撼,也就是一個(gè)這樣的桌面應(yīng)用:

例1
例2

對(duì),我實(shí)現(xiàn)了似谁,過(guò)程中還是踩了一些坑的傲绣,現(xiàn)在就把具體的方法分享給大家。這是我自己思考的方法巩踏,因?yàn)楸救艘彩莿傞_(kāi)始研究秃诵,可能還有些地方理解的不是很清楚,如果您有更好的建議塞琼,懇請(qǐng)不吝斧正菠净。

剛開(kāi)始我是看了這個(gè)stone大神寫(xiě)的貼作為啟蒙:https://zhuanlan.zhihu.com/p/121003243,但是我沒(méi)能在自己電腦上實(shí)現(xiàn),因?yàn)閑lectricShine這個(gè)東西是一個(gè)寫(xiě)死的包嗤练,寫(xiě)死既被動(dòng)榛了,在調(diào)用npm的時(shí)候總會(huì)有小小的問(wèn)題導(dǎo)致全盤(pán)失敗。雖然沒(méi)有成功實(shí)現(xiàn)煞抬,但是我肯定是不服的霜大。后來(lái)我又看了某機(jī)構(gòu)的博客:https://foretodata.com/how-to-make-a-standalone-desktop-application-with-shiny-and-electron-on-windows/,感覺(jué)上可行革答,嘗試以后發(fā)現(xiàn)跑通了战坤,確實(shí)可以。但是以上都不好作為最終的解決方案残拐。

那么一個(gè)最為方便且易于實(shí)現(xiàn)的思路是這樣的:

  • 安裝R-Portable作為開(kāi)發(fā)途茫、部署、分發(fā)的R環(huán)境
  • 在上述環(huán)境中開(kāi)發(fā)ShinyApp(推薦使用golem)
  • 通過(guò)electron-quick-start將R-Portable和ShinyApp打包成exe
    該方法基于Windows實(shí)現(xiàn)了打包exe溪食,理論上可以在mac上實(shí)現(xiàn)打包dmg

怎么做兰伤?

0 準(zhǔn)備工作

  • 熟悉R及Rstudio
  • 熟悉命令行操作
  • 了解Shiny App及其基本結(jié)構(gòu)
  • 確定了解我們的目的
  • 新建一個(gè)工作目錄C:\myShinyApp

1 下載安裝R-portable

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

強(qiáng)烈建議這個(gè)3.6.3版本,比較穩(wěn)定泪勒,4.0.0編譯暫時(shí)有問(wèn)題钮糖。

安裝比較簡(jiǎn)單,注意將路徑設(shè)置為我們新建的工作目錄枢析,安裝完成即可玉掸。

2 配置 Rstudio

現(xiàn)在我們要開(kāi)啟R-Portable作為R環(huán)境
打開(kāi)Rstudio,鼠標(biāo)點(diǎn):Tools>Global Options>General>Change R version>Browse
定位我們剛才安裝的R-Portable路徑(C:\myShinyApp\R-Portable\App\R-Portable)
然后點(diǎn)選擇文件夾醒叁,選擇64位版本

一路點(diǎn)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"

注意:這里出現(xiàn)了兩個(gè)路徑,[1]是我原來(lái)就有的把沼,[2]是剛裝的啊易,ShinyApp中所有要用到的包必須裝在[2]里。

3 搭建Shiny App

golem包是開(kāi)發(fā)Shiny App的輔助開(kāi)發(fā)工具饮睬,用它可以讓開(kāi)發(fā)過(guò)程更加方便认罩。
先在Rstudio中安裝這個(gè)包:

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

安裝完成后,在Rstudio中點(diǎn)菜單:File>New Project>New Directory>Package for Shiny App using golem

將Directory name隨意設(shè)置為shinyapptest续捂,路徑定位到我們的工作目錄

創(chuàng)建完成后垦垂,我們就在Rstudio中開(kāi)辟了一個(gè)新的Project和工作環(huán)境,且工作目錄出現(xiàn)了一個(gè)類(lèi)似于R包的結(jié)構(gòu):

根據(jù)golem的Document牙瓢,我們主要關(guān)注./dev中的三個(gè)腳本01_start.R劫拗,02_dev.R03_deploy.R以及./R中的三個(gè)腳本app_ui.R矾克,app_server.R页慷,run_app.R

假如我們現(xiàn)在要實(shí)現(xiàn)文章開(kāi)頭例2提到的csv表格查看器。

3.1 添加模塊

載入csv文件的按鈕就是一個(gè)模塊(按鈕本身是模塊的UI酒繁,讀取csv文件是這個(gè)模塊的功能)滓彰,我們運(yùn)行./dev/02_dev.R中的add_module添加一個(gè)模塊

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

結(jié)果./R路徑下生成了一個(gè)以mod_為前綴的模塊文件,

mod_csv_file.R這個(gè)文件的內(nèi)容改成這樣的:

#' 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)
    }
  )    
}

模塊的定義包含兩個(gè)部分:mod_csv_file_ui定義模塊UI州袒,mod_csv_file_server定義模塊功能揭绑,如果要使用這個(gè)模塊只需在Shiny App的app_ui中調(diào)用前者,app_server中調(diào)用后者就可以了郎哭。

3.2 寫(xiě)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)") # 調(diào)用模塊UI
        ),
        mainPanel(
          dataTableOutput("table")
        )
      )
    )
  )
}

為了節(jié)省空間我把golem導(dǎo)入外部資源的部分去除了他匪。
然后將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) # 調(diào)用模塊function
  output$table <- renderDataTable({
    datafile()
  })
}

3.3 測(cè)試App

改好這些文件以后我們?cè)?code>./dev/run_dev.R腳本中測(cè)試一下我們的Shiny App:

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

運(yùn)行到上面這一條提示我們還沒(méi)有裝shiny這個(gè)包,那就裝吧:

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

再次運(yùn)行這一條夸研,發(fā)現(xiàn)成功了:

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

最后運(yùn)行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()

出現(xiàn)下面這個(gè)界面Shiny App基本上就成了邦蜜,可以打開(kāi)一個(gè)csv文件自己測(cè)試一下。

3.4 打包Shiny App

假如有一天亥至,我們精妙的Shiny App終于大功告成了悼沈,那么可以將他打成package并安裝到R-Portable中。
先準(zhǔn)備一下devtools:

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

然后打包shinyapp姐扮,路徑為當(dāng)時(shí)golem創(chuàng)建的項(xiàng)目路徑:

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"

安裝這個(gè)打包成功的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, # 這個(gè)參數(shù)一定要的
  dependencies = T
)

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

shiny具體的開(kāi)發(fā)文檔還是要研究一下:https://shiny.rstudio.com/articles/絮供。好了,R的工作完成了剩下的交給electron-quick-start溶握。

4 安裝并配置node.js

4.1 下載解壓

去這個(gè)鏈接下載zip壓縮文件:https://nodejs.org/download/release/v12.16.2/node-v12.16.2-win-x64.zip
我裝的是v12.16.2版本,如果嫌下載慢的話蒸播,想想辦法睡榆,這里我分享一個(gè)網(wǎng)盤(pán)給你們:
鏈接: https://pan.baidu.com/s/1QbLJcfhRqTsgUeQ10Wy7wA
提取碼: 4gzh
這是解壓版,安裝版也是同理的袍榆。下載完成后解壓到指定目錄胀屿,可以是我們的工作目錄,解壓完以后是這樣的:

4.2 配置環(huán)境變量

在這個(gè)目錄中新建兩個(gè)文件夾node_globalnode_cache

新建一個(gè)系統(tǒng)變量包雀,變量名是NODE_PATH宿崭,值是nodejs的解壓或安裝目錄C:\myShinyApp\node-v12.16.2-win-x64

新建另一個(gè)關(guān)鍵的系統(tǒng)變量,變量名是NODE_TLS_REJECT_UNAUTHORIZED才写,值是0葡兑,我覺(jué)得這個(gè)變量很關(guān)鍵:

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

image.png

4.3 配置npm參數(shù)

現(xiàn)在赞草,以管理員身份打開(kāi)優(yōu)秀的Windows Powershell讹堤,檢查node和npm是否安裝正常:

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

配置一些必要的npm參數(shù):

> 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

以上配置就是為了能夠成功安裝這個(gè)包

> npm install electron-packager -g

# 出現(xiàn)以下信息說(shuō)明成功
# + 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項(xiàng)目:

$ 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
拉取或解壓成功后:

再次以管理員身份打開(kāi)優(yōu)秀的Windows Powershell:

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

# 出現(xiàn)以下信息就明名安裝成功
# > 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

接下來(lái)是關(guān)鍵的一步:
將R-Portable路徑C:\myShinyApp\R-Portable\App\R-Portable下的所有文件復(fù)制并替換C:\myShinyApp\electron-quick-start\R-Portable-Win路徑:

?還記得嗎?這個(gè)環(huán)境里有我們安裝好的R環(huán)境梗醇、寫(xiě)好的ShinyApp以及依賴的R包(其實(shí)知允,ShinyApp也作為包安裝在這個(gè)R環(huán)境了,依稀記得包名叫shinyapptest)叙谨。

回到C:\myShinyApp\electron-quick-start温鸽,編輯這個(gè)目錄下的app.R文件,這個(gè)文件是程序的入口唉俗,那么你猜這個(gè)文件應(yīng)該寫(xiě)什么嗤朴?要不就試試寫(xiě)這一行內(nèi)容保存:

# app.R
shinyapptest::run_app()

最后一次打開(kāi)優(yōu)秀的Windows Powershell,完成最后的打包

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

# 出現(xiàn)以下信息就說(shuō)明成功了
# 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文件夾下出現(xiàn)了一個(gè)新的目錄:

雙擊exe文件:

成功虫溜!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雹姊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衡楞,更是在濱河造成了極大的恐慌吱雏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘾境,死亡現(xiàn)場(chǎng)離奇詭異歧杏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)迷守,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)犬绒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人兑凿,你說(shuō)我怎么就攤上這事凯力。” “怎么了礼华?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵咐鹤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我圣絮,道長(zhǎng)祈惶,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任扮匠,我火速辦了婚禮捧请,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棒搜。我一直安慰自己血久,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布帮非。 她就那樣靜靜地躺著氧吐,像睡著了一般讹蘑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上筑舅,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天座慰,我揣著相機(jī)與錄音,去河邊找鬼翠拣。 笑死版仔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的误墓。 我是一名探鬼主播蛮粮,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谜慌!你這毒婦竟也來(lái)了然想?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤欣范,失蹤者是張志新(化名)和其女友劉穎变泄,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體恼琼,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妨蛹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晴竞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛙卤。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖噩死,靈堂內(nèi)的尸體忽然破棺而出颤难,到底是詐尸還是另有隱情,我是刑警寧澤甜滨,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布乐严,位于F島的核電站瘤袖,受9級(jí)特大地震影響衣摩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捂敌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一艾扮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧占婉,春花似錦泡嘴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)磺箕。三九已至,卻和暖如春抛虫,著一層夾襖步出監(jiān)牢的瞬間松靡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工建椰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雕欺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓棉姐,卻偏偏與公主長(zhǎng)得像屠列,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伞矩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容