lorca + Reactで作る簡単デスクトップアプリ

この記事は[あくあたん工房AdventCalendar](https://adventar.org/calendars/2957)24日目の記事です．

## はじめに

Electronより軽量なHTML5デスクトップアプリ用ライブラリとして最近注目されている[lorca](https://github.com/zserge/lorca)を使ってデスクトップアプリを作っていきます．
やることは簡単で，

- Reactで適当なSPAを作る
- Ginで静的ファイルを配信するエンドポイントを立てる
- lorcaでそれを見る

という感じです．

## lorca
> [README](https://github.com/zserge/lorca/blob/master/README.md)より
A very small library to build modern HTML5 desktop apps in Go. It uses Chrome browser as a UI layer. Unlike Electron it doesn't bundle Chrome into the app package, but rather reuses the one that is already installed. Lorca establishes a connection to the browser window and allows calling Go code from the UI and manipulating UI from Go in a seamless manner.

上記のとおり，Electronと決定的に違うのは，ユーザーがすでにインストールしているChromeをUIレイヤーとして活用するという点です．
これによって生成されるアプリケーションのサイズが軽量になります．
(もちろん，Chromeのタブの1つとして動作しているのでElectronより機能はかなり制限されています． e.g window menuが無い)

ただ，**GoのコードとJSのコードを紐付けることができる**ので，GoのCLIアプリをGUI化するときにめっちゃ役立ちそうです．今回は使いませんが．

## 作っていく
最初のリポジトリ構造はこれ

```bash
.
├── LICENSE
├── README.md
├── go.mod
├── go.sum
└── main.go
```

この中に`create-react-app`でReactアプリを作ります．

```bash
$ create-react-app app
$ tree . -I node_modules
.
├── LICENSE
├── README.md
├── app
│   ├── README.md
│   ├── build
│   │   ├── asset-manifest.json
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   ├── manifest.json
│   │   ├── precache-manifest.1b7c797c02d13deffb0ad84befafc406.js
│   │   ├── service-worker.js
│   │   └── static
│   │       ├── css
│   │       │   ├── main.5be34591.chunk.css
│   │       │   └── main.5be34591.chunk.css.map
│   │       ├── js
│   │       │   ├── 1.fa92c112.chunk.js
│   │       │   ├── 1.fa92c112.chunk.js.map
│   │       │   ├── main.88557385.chunk.js
│   │       │   ├── main.88557385.chunk.js.map
│   │       │   ├── runtime~main.229c360f.js
│   │       │   └── runtime~main.229c360f.js.map
│   │       └── media
│   │           └── logo.5d5d9eef.svg
│   ├── package.json
│   ├── public
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   └── manifest.json
│   ├── src
│   │   ├── App.css
│   │   ├── App.js
│   │   ├── App.test.js
│   │   ├── index.css
│   │   ├── index.js
│   │   ├── logo.svg
│   │   └── serviceWorker.js
│   └── yarn.lock
├── go.mod
├── go.sum
└── main.go
```

`yarn build`すると`build`ディレクトリにwebpack後のファイルが展開されるので，これを配信するサーバーとそれをlorcaで参照する処理を書いていきます．

```go:main.go
package main

import (
	"log"

	"github.com/gin-contrib/static"
	"github.com/gin-gonic/gin"
	"github.com/zserge/lorca"
)

var staticDir = "./app/build"

func main() {
	go runServer()

	err := runUI()
	if err != nil {
		log.Fatal(err)
	}
}

// app/build/以下を配信するサーバー
func runServer() {
	router := gin.Default()
	router.Use(static.Serve("/", static.LocalFile(staticDir, true)))
	err := router.Run(":9000")
	if err != nil {
		log.Fatal(err)
	}
}

// lorca起動
func runUI() error {
	ui, err := lorca.New("", "", 320, 480)
	if err != nil {
		return err
	}

	ui.Load("http://localhost:9000")
	<-ui.Done()
	return nil
}
```

実行すると`create-react-app`直後のお馴染みの画面がデスクトップアプリのような形で表示されると思います．

```bash
$ go run main.go
```

![スクリーンショット 2018-12-24 18.57.46.png](https://qiita-image-store.s3.amazonaws.com/0/231883/ff22728b-7395-d82d-3a78-eb7be4fcf159.png)

おぉ〜．

### 1つのデスクトップアプリケーションとしてパッケージ化する
[公式のexampleにあるcounter](https://github.com/zserge/lorca/tree/master/examples/counter)を見てもらうと分かりますが，Windows，MacOS，Linuxごとにアプリをパッケージ化できるようなシェルスクリプトが書いてあります．
~~パクり~~参考にしながら今回の構成に合わせるとこんな感じになります．

```bash:build-macos.sh
#!/bin/sh

APP="Example.app"
mkdir -p $APP/Contents/{MacOS,Resources}
go build -ldflags "-X main.staticDir=$(pwd)/Example.app/Contents/Resources/app" -o $APP/Contents/MacOS/lorca-example 
cat > $APP/Contents/Info.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleExecutable</key>
	<string>lorca-example</string>
	<key>CFBundleIconFile</key>
	<string>icon.icns</string>
	<key>CFBundleIdentifier</key>
	<string>com.zserge.lorca.example</string>
</dict>
</plist>
EOF
cp icons/icon.icns $APP/Contents/Resources/icon.icns
cp -r app/build/ $APP/Contents/Resources/app
find $APP

```

`-ldfrags`で埋め込んでるリソースのパス指定の方法が良くないですね．はい．
というかシングルバイナリ化したほうがいいのでしょうか？

スクリプトを走らせると，`Example.app`ができているはずです．
![スクリーンショット 2018-12-24 20.31.51.png](https://qiita-image-store.s3.amazonaws.com/0/231883/5a15f097-fdde-322f-31e3-5112d39b7319.png)


## おわりに
今回のリポジトリはこちら : [taxio/temo](https://github.com/taxio/temo)
本当はMacOS用の簡単なメモ帳アプリを作りたかったんですが，クリスマスケーキとチーズフォンデュが僕を呼んでいるので今日はここまで．

リポジトリのコードはちょくちょく更新していきます．