起源物臂!
某天旺拉,我發(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)用:
對(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.R
,03_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_global
和node_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-x64
和C:\myShinyApp\node-v12.16.2-win-x64\node_global
(忽略圖中的大小寫(xiě)筆誤)
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),就先cd
到C:\myShinyApp\electron-quick-start
厨疙,然后clone項(xiàng)目:
$ git clone https://github.com/listen2099/electron-quick-start.git
如果不方便用git洲守,就直接下載連接中的zip文件解壓到C:\myShinyApp\electron-quick-start
:https://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文件:
成功虫溜!