起源!

某天,我发现了Shiny这个东西,当时兴冲冲的尝试官网上各种各样的例子,最后发现这个东西似乎只能充当一个“玩具”。如果要在本地运行,它需要一个完整的R环境,这对相当一部分用户来说是极度不友好的。另外,Rstudio主张将Shiny部署在https://www.shinyapps.io/,但是看到这个价格以及资源限制以后进一步被劝退了。

毕竟很多科研工作者的出发点是将自己的研究过程和结果分享展示给他人,而不是出于商业的目的,部署在服务器上供他人使用需要持续投入计算资源和维护成本,这不是长久之计。


目的?

那么,如果我们实现了一个精妙的Shiny App,如何0成本的分享给别人,且别人能够方便的使用呢?为了达到这个目的,最好的结果是将R中的Shiny App转换为一个独立运行的exe文件,也就是一个这样的桌面应用:

对,我实现了,过程中还是踩了一些坑的,现在就把具体的方法分享给大家。这是我自己思考的方法,因为本人也是刚开始研究,可能还有些地方理解的不是很清楚,如果您有更好的建议,恳请不吝斧正。

刚开始我是看了这个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/,感觉上可行,尝试以后发现跑通了,确实可以。但是以上都不好作为最终的解决方案。

那么一个最为方便且易于实现的思路是这样的:

  • 安装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/

强烈建议这个3.6.3版本,比较稳定,4.0.0编译暂时有问题。

安装比较简单,注意将路径设置为我们新建的工作目录,安装完成即可。

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(忽略图中的大小写笔误)

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文件:

成功!

将Shiny APP搭建为独立的桌面可执行程序 - Deploying R shiny app as a standalone application的更多相关文章

  1. R︱shiny实现交互式界面布置与搭建(案例讲解+学习笔记)

    要学的东西太多,无笔记不能学~~ 欢迎关注公众号,一起分享学习笔记,记录每一颗"贝壳"~ --------------------------- 看了看往期的博客,这个话题竟然是第 ...

  2. R Shiny app | 交互式网页开发

    网页开发,尤其是交互式动态网页的开发,是有一定门槛的,如果你有一定的R基础,又不想过深的接触PHP和MySQL,那R的shiny就是一个不错的选择. 现在R shiny配合R在统计分析上的优势,可以做 ...

  3. 如何搭建一个独立博客——简明Github Pages与Hexo教程

    摘要:这是一篇很详尽的独立博客搭建教程,里面介绍了域名注册.DNS设置.github和Hexo设置等过程,这是我写得最长的一篇教程.我想将我搭建独立博客的过程在一篇文章中尽可能详细地写出来,希望能给后 ...

  4. 转Android APP安装后不在桌面显示图标的应用场景举例和实现方法

    转http://www.cnblogs.com/allenzheng/p/4510725.html#3186608 Android APP安装后不在桌面显示图标的应用场景举例和实现方法 最近在为公司做 ...

  5. 使用GitHub+hexo搭建个人独立博客

    前言 使用github pages服务搭建博客的好处有: 全是静态文件,访问速度快: 免费方便,不用花一分钱就可以搭建一个自由的个人博客,不需要服务器不需要后台: 可以随意绑定自己的域名,不仔细看的话 ...

  6. 使用Hexo + Github Pages搭建个人独立博客

    使用Hexo + Github Pages搭建个人独立博客 https://linghucong.js.org/2016/04/15/2016-04-15-hexo-github-pages-blog ...

  7. Android APP安装后不在桌面显示图标的应用场景举例和实现方法

    最近在为公司做一款车联网的产品,由于公司本身擅长于汽车解码器的研发,所以该产品的诊断功能的实现除了使用目前市面上车联网产品中大量使用的OBD协议外,还会使用一些专车专用协议去实现一些特殊的诊断功能,如 ...

  8. 1-开发共享版APP(搭建指南)-快速搭建到自己的服务器

    该APP安装包下载链接: http://www.mnif.cn/appapk/IotDevelopmentVersion/20190820/app-debug.apk 或者扫描二维码下载 注:该下载可 ...

  9. Building [Security] Dashboards w/R & Shiny + shinydashboard(转)

    Jay & I cover dashboards in Chapter 10 of Data-Driven Security (the book) but have barely mentio ...

随机推荐

  1. Flink的sink实战之一:初探

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. SQL存储过程返回值

    1 SQL存储过程返回值有3种 1.1 直接return返回(例如 return 1): 1.2 通过参数output返回(例如字符串类型): 1.3 直接返回程序集(Dataset程序集). 2 用 ...

  3. 文科妹子都会用 GitHub,你这个工科生还等什么

    在某乎上刷到一条关于 GitHub 的留言,如下: 点赞人数还不少,这说明还真有不少工科生不会用 GitHub,你看大小写都没有区分(手动狗头).所以我就想写篇文章科普下,"新手如何使用 G ...

  4. 无法将“add-migration”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。解决方案

    在程序包管理控制台中执行 Install-Package Microsoft.EntityFrameworkCore.Tools

  5. create event[]

    一.前言自 MySQL5.1.6起,增加了一个非常有特色的功能–事件调度器(Event Scheduler),可以用做定时执行某些特定任务(例如:删除记录.对数据进行汇总等等),来取代原先只能由操作系 ...

  6. [MIT6.006] 4. Heaps and Heap Sort 堆,堆排序

    第4节课仍然是讲排序,但介绍的是一种很高效的堆排序. 在编程过程中,有时候会需要进行extrat_max的操作,即从一个数列里挨个抽取最大值并将其它从原数列中移除.而排序问题也可以看作是一个extra ...

  7. slideUp和slideDown的区别

    slideUp():通过使用滑动效果,隐藏被选元素,如果元素已显示出来的话.语法:$(selector).slideUp(speed,callback).speed:可选,表示动画运行的时候.call ...

  8. Spring接口

    FactoryBean接口 Spring中有两种类型的Bean:一种是普通的JavaBean:另一种就是工厂Bean(FactoryBean),这两种Bean都受Spring的IoC容器管理. Fac ...

  9. RTSP服务端开发概述

    一 概述 RTSP(Real Time Streaming Protocol),RFC2326,实时音视频流传输协议,是TCP/IP协议体系中的一个应用层协议.该协议定义了一对多应用程序如何有效地通过 ...

  10. [web安全原理分析]-文件上传漏洞基础

    简介 前端JS过滤绕过 待更新... 文件名过滤绕过 待更新 Content-type过滤绕过 Content-Type用于定义网络文件的类型和网页编码,用来告诉文件接收方以什么形式.什么编码读取这个 ...