figwheel+antizer项目shadow-cljs+electron改造

1. 使用的脚手架

https://github.com/qdhaiqiang/shadow-electron-starter

2. 将原项目figwheel编译的浏览器版改造成electron版本

将干净的项目clone下来以后按顺序做如下改动。

2.1 移入原项目文件

  • 移入resouce目录下的css和image文件,从 resouce/public/resouce/public/
  • 移入cljs文件
    需要注意的是移入以后需要根据新的文件目录,全文替换namespace名称,无论以前前面是什么,都改成app.renderer,这样项目的其他配置的地方就不用改了。

2.2 配置原项目依赖的版本

  • 引入npm和cljs依赖
    在根目录的shadow-cljs.edn文件中加入依赖
:dependencies [[re-frame "0.10.7"]
                [reagent "0.8.1"]
                [luminus-transit "0.1.1"]
                [org.clojure/core.async "0.4.500"]
                [kee-frame "0.3.3" :exclusions [metosin/reitit-core]]
                [binaryage/devtools "0.9.10"]
                [re-frisk "0.5.4.1"]
                [metosin/reitit "0.3.9"]
                [com.rpl/specter "1.1.3"]
                [antizer "0.3.1"]]

2.3 使用figwheel时引入的几个cljsjs文件手动处理

其中,antizer是用antd早的cljsjs的一个轮子。

(ns cljsjs.antd
  (:require ["antd" :as antd]))

(js/goog.exportSymbol "antd" antd)
  • cljsjs.echarts问题
    引入npm包
yarn add echarts

自建cljsjs.echarts文件

(ns cljsjs.echarts
(:require ["echarts" :as echarts]))

(js/goog.exportSymbol "echarts" echarts)
  • moment问题
    引入npm包
yarn add moment

namespace中增加

(require ["moment" :as moment])

代码替换

(.format (js/moment (js/Date.)) "YYYY-MM-DD")
# 替换成
(.format (moment (js/Date.)) "YYYY-MM-DD")

2.4 css和图片问题

  • css问题
    样式文件找到,删除原项目中样式的依赖,类似以下代码:
[:link {:rel "stylesheet" :href "/css/dhome.css"}]

将样式文件增加到index.html中

 <link href="css/dhome.css" rel="stylesheet" type="text/css">
  • 图片丢失问题
    图片文件是路径问题,原来css里的
 background-image: url('/img/notice.png');

改成:

 background-image: url('img/notice.png');

原来cljs里的

[:img.headDCBoxImg {:src "/img/moban.svg"}]

改为:

[:img.headDCBoxImg {:src "img/moban.svg"}]

2.5 增加本来开发re-frisk显示

shadow-cljs.ednbuilds配置中增加dev和devtools,配置时注意引入的re-frisk的版本要和reagent的版本的对应性,可以参考re-frisk说明,最后的配置文件如下:

;; shadow-cljs configuration
{:source-paths ["src"]

 :dependencies [[re-frame "0.10.7"]
                [reagent "0.8.1"]
                [luminus-transit "0.1.1"]
                [org.clojure/core.async "0.4.500"]
                [kee-frame "0.3.3" :exclusions [metosin/reitit-core]]
                [binaryage/devtools "0.9.10"]
                [re-frisk "0.5.4.1"]
                [metosin/reitit "0.3.9"]
                [com.rpl/specter "1.1.3"]
                [antizer "0.3.1"]]

 :dev-http {8080 "resources/public/"}

 :builds {:main {:target :node-script
                 :output-to "resources/main.js"
                 :compiler-options {:optimizations :simple}
                 :main app.main.core/main}

          :renderer {:target :browser
                     :output-dir "resources/public/js"
                     :asset-path "js"
                     :compiler-options {:optimizations :simple}
                     :devtools {:watch-dir "resources/public"
                                :ignore-warnings true
                                :preloads [re-frisk.preload]}
                     :dev {:closure-defines {"re_frame.trace.trace_enabled_QMARK_" true}}
                     :modules {:renderer {:init-fn app.renderer.core/init!}}}}}

3. 配置打包

3.1 打包命令

package.json的scripts结构下增加:

"package-mac": "export CSC_IDENTITY_AUTO_DISCOVERY=false && npm run build && npx shadow-cljs release :main :renderer && npx electron-builder build --mac",
"package-win": "npm run build && npx shadow-cljs release :main :renderer && npx electron-builder build --win"

shadow-cljs.end文件的builds:main:renderer中都增加:compiler-options {:optimizations :simple},不用做js混淆,这样mac上打的包才能打开,否则打包后会提示如图错误

TypeError:Wa(...).$b is not a function at app.asar/resources/main.js

注意:mac上没有配置签名,所以在打包的命令行里要执行export CSC_IDENTITY_AUTO_DISCOVERY=false这个配置阻止签名。

(goog.string.format "%.1f" (/ (+ m 1) 10))))) (range 10))

修改为:

(:import
   [goog.string format])
(format "%.1f" (/ (+ m 1) 10))))) (range 10))

这一点原因还没有搞明白,先放几个查出来相关说法的链接
https://github.com/bhauman/lein-figwheel/issues/222

3.2 搞下app图标

找个logo的png图片,最小是512x512的,命令为icon.png,并将其在一些在线工具上转出一个icon.icoicon.icns,放在项目根目录下创建出来的build目录里。

# marvin @ 192 in ~/git/redcreation/alk-wxapi/alk-doctor/build on git:v_2.0 o [22:50:14] C:127
$ tree
.
├── icon.icns
├── icon.ico
└── icon.png

0 directories, 3 files

上面的图片也可以用这种命令自己转,话说mac还是很牛逼的。

sips -z 16 16  pic.png --out icon_16x16.png
sips -z 512 512  pic.png --out tmp.iconset/icon_512x512.png

3.3 手动检测新版本

在登录页面调用一个获取最新版本的接口,返回新版本及更新内容。然后跟当前app的版本进行比较。(注意此代码要在render进程中,在main进程中的话会提示个错误,错误和原因见:https://stackoverflow.com/questions/59789539/electronjs-referenceerror-navigator-is-not-defined)

(def current-version
  (let [fs (js/require "fs")
        package (.readFileSync fs "./package.json" "utf-8")]
    (get (js->clj (.parse js/JSON package)) "version")))

(def mac? (->> (.-userAgent js/navigator)
               (re-seq #"Mac")
               (count)
               zero?
               not))

本地运行


image.png

完美。然而,打包以后


package.json not found

可能是我想简单了,文件都被打包到asar里了,目录结构也变了,打包以后不能用这种方式获取package.json文件了。
也许有别的办法可以找到这个文件,因为毕竟electron的api能找到,说明肯定有办法,electron提供了一个 app.getVersion()的API,用这个来解决。

那么问题又来了,这个api只能在main进程中调用,怎么跟render打交道呢?在前人Tony.Yang老师的多番指导下我也总算是拼出来了,也就是消息订阅和监听机制,让main和renderer联起来。ipcMain实现从主进程到渲染进程的通信,ipcRenderer从渲染器进程到主进程的异步通信。主要实现步骤:

  • 第一步:在main中注册ipcMain
(defn main []
  ; CrashReporter can just be omitted
  (.start crashReporter
          (clj->js
            {:companyName "MyAwesomeCompany"
             :productName "MyAwesomeApp"
             :submitURL "https://example.com/submit-url"
             :autoSubmit false}))

  (.on app "window-all-closed" #(when-not (= js/process.platform "darwin")
                                  (.quit app)))
  (.on app "ready" init-browser)
  
  (.on ipcMain "app-msg" (fn [_ arg]
                           (js/console.log "arg-------" arg)
                           (case (.-cmd arg)
                             "get-app-version" (send-message "app-version" (.getVersion app))
                             "其他消息"))))
  • 第二步:新建一个util,跟ipcMain的通信
;; electron render进程的内容引入
(defonce electron (js/require "electron"))
(defonce remote (.-remote electron))
(defonce web-contents (.-webContents remote))
(defonce browser-window (.-BrowserWindow remote))
(defonce ipc-render (.-ipcRenderer electron))

(defonce version (atom ""))

(defn current-version []
  (.on ipc-render "app-version" (fn [_ v]
                                  (reset! version v)))
  (.send ipc-render "app-msg" (clj->js {:cmd "get-app-version"})))
  • 第三步:使用
    那个页面想用这个版本号,只要订阅一下这个namespace的version这个atom便可。如果发生订阅不到的情况,在路由入口处调一下current-version也就可以了。

2021-01-12补充(没文化,真可怕)
其实没有必要这么麻烦,Tony告诉我render里也可以获取版本,于是我试了一下,果然前面有大半天的功夫在做无用功,在render里定义个函数就解决了的事:

(defonce electron (js/require "electron"))
(defonce remote (.-remote electron))

(defn get-version []
  (let [version (.getVersion (.-app remote))]
    (prn "render进程里获取到的当前版本:" version)
    version))

4. 加入本地缓存

策略是将所有db的内容缓存到本地文件中,代码较多不全部贴了,主要是有两个错误提一下。

  • 本地文件不存在
    在render进程里读写文件时提示,文件或者目录不存在,这需要在main进程中调用render里的函数来初始化这个文件。需要调用的函数init-app-data,整个file.cljs内容如下,包括文件创建、写入、读取。
(ns utils.file
  (:require
   [clojure.edn :refer [read-string]]))

(def fs (js/require "fs"))
(def file-exists? (.-existsSync fs))
(def mkdir   (.-mkdirSync  fs))
(defn read-file [file-path]
  (.readFileSync fs file-path "utf-8"))

(defn write-file [file-path content]
  (js/console.log "write at file-path:" file-path)
  (.writeFileSync fs file-path content))

(def path (js/require "path"))
(def path-join (.-join path))

(goog-define dev? false)

(defn get-app-data-path []
  (if dev?
    "."
    (let [platform (.-platform js/process)
          home-dir (.. js/process -env -HOME)
          app-data-dir (.. js/process -env -APPDATA)
          app-name "eallergy-doctor"]
      (case platform
        "darwin" (path-join home-dir  "Library" "Application Support" app-name)
        "win32"  (path-join app-data-dir app-name)
        "linux"  (path-join home-dir (str "." app-name))
        (do (js/console.error "Unsupported platform!")
            ((.exit js/process) 1))))))

(defn get-path-for-file [f]
  (path-join (get-app-data-path) f))

(defn file-path [file]
  (path-join (get-app-data-path) file))

(defn app-data-exists? []
  (if dev?
    true
    (file-exists? (get-app-data-path))))

(defn- write-init-db!
  "only call on production env"
  []
  (write-file (file-path "appdb.edn") (str {})))


(defn init-app-data []
  (when (and (not dev?)
             (not (app-data-exists?)))
    (mkdir (get-app-data-path)))
  (when (not (file-exists? (get-path-for-file "appdb.edn")))
    (write-init-db!)))

(defn file->str [file]
  (->> file
       file-path
       read-file
       str))

(defn file->edn [file]
  (->> file
       file->str
       read-string))

(defn read-db-from-disk [db]
  (file->edn db))

文件不存在报错如下:

file not exist

这需要在main里调用一下init-app-data即可。注意main线程是electron .的时候调用的,所以编译完应该关闭electron窗口,重新启动。

(def fs (js/require "fs")
(def fs (js/require "electron")

运行时报错如图:


require is not defined

链接中乃正解,github初始化的配置里缺少了,需在main线程中创建窗口的配置中应该增加以下配置:

mainWindow = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true
        }
    });

5. 增加自动更新

如果要实现自动更新,Tony推荐以下文章(这个项目没有实现自动更新,以后再补充):

5. 增加自动更新

如果要实现自动更新,Tony推荐以下文章(这个项目没有实现自动更新,以后再补充):

自查资料:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容