designudge
vol.120
2016-Jan-14 アドベントカレンダー特集2
Cover
今回のカバーも、初詣に行ったときのショットです。
前回、アドベントカレンダー向けに書いていたものをまとめて1日目から5日目まで掲載したのですが、今回もその続きです。
6日目から10日目までを掲載します。
僕は技術的な部分ではClojureとElectronにすごく可能性を感じていて、今回もそんな感じ一色のマガジンとなっています。
次々号あたりで通常運転に戻ると思います。

今回のカバーも、前号に続き初詣のときのもの。
田舎の小さな神社で、地元の人に大事にされています。
石畳の脇に立てられた行灯が社まで続く、印象的な境内です。
descjop で遊ぼう day 6 : Electronがうまくダウンロードできないとき
descjopを使おうと思って、Electronのダウンロードのところで詰まってしまうことがあります。
Electronがダウンロードし展開し終わったよという
Done, without errors.
が表示されず、そのままunzip状態で止まってしまう現象です。
こうなってしまうと、次に同様の作業をやった場合でもElectronが中途半端な状態で展開されてしまい、Electronのビルドから先のステップに進めなくなってしまいます。
なぜかというと、一回ダウンロードしたElectronを再利用するように出来ているためです。
こうなったときは、キャッシュされているElectronを消去してやることで再実行することが出来、無事Electronのダウンロードを終えることが出来ます。
download-atom-shell-task.coffee · atom/grunt-download-electronでは、次のように定義されています。
grunt.registerTask TaskName, 'Download electron', ->
@requiresConfig "#{TaskName}.version", "#{TaskName}.outputDir"
{version, outputDir, downloadDir, symbols, rebuild, apm, token, appDir} = grunt.config TaskName
downloadDir ?= path.join os.tmpdir(), 'downloaded-electron'
symbols ?= false
rebuild ?= true
apm ?= getApmPath()
version = "v#{version}"
versionDownloadDir = path.join(downloadDir, version)
appDir ?= process.cwd()
done = @async()
... 以下略
つまり、ダウンロードを一時的にされるディレクトリは、os.tmpdir()
つまりOSのテンポラリディレクトリに展開されるようになっています。
ただ、OSのテンポラリディレクトリを探すのがかったるいというケースもあるでしょう。
gruntfileの修正による対応
そこで、./Gruntfile.js
を少し修正します。
現状の./Gruntfile.js
は以下のようになっていると思います。
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
"download-electron": {
version: "0.35.0",
outputDir: "./electron",
rebuild: true
}
});
grunt.loadNpmTasks('grunt-download-electron');
};
これに、
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
"download-electron": {
version: "0.35.0",
outputDir: "./electron",
rebuild: true,
downloadDir: ".electron-download" // 追加します
}
});
grunt.loadNpmTasks('grunt-download-electron');
};
のように、downloadDir
というオプションを指定します。
このようにすると、カレントディレクトリ上にElectronのキャッシュがダウンロードされます。
.electron-download
というフォルダが出来ているのが見えましたよね。
以上、Electronがダウンロードうまくできないときのチップスでした。
descjop で遊ぼう day 7 : Hello Worldを出力してみるには
昨日は、ついにdescjopを使ってデフォルトのアプリケーションが起動しました。
Hello Worldも出たし言うことはないのですが、何かソースをいじった上でHello Worldを表示させてみたいものです。
ということで、コードを編集して、Hello Worldと表示させてみましょう。
./app/index.html
をエディタで開きます。
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using node.js <script>document.write(process.version)</script>
and Electron(atom-shell) <script>document.write(process.versions['electron'])</script>.
</body>
</html>
上記のコードを、下記のように書き換えて、アプリケーションを起動してみましょう。
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<p>Hello World!</p>
</body>
</html>
すると、画面にはHello World!と表示されるはずです。
どこからか「バカヤロー!!」という声が聞こえてきそうです。
HTMLを編集したんだから表示が変わって当たり前ですよね。「もっとこう、動的に変えるようなやつをやりたいんだ!」というご要望もごもっともですが、実はここは結構重要なポイントです。

例えば、./src/helloworld/core.cljs
の(.on app "ready" ...)
の部分を下記のように変えてみましょう。
; ready listener
(.on app "ready"
(fn []
(reset! *win* (BrowserWindow. (clj->js {:width 800 :height 600})))
;; when no optimize comment out
;; (.loadUrl @*win* (str "file://" (.resolve path (js* "__dirname") "../index.html")))
(.loadUrl @*win* "https://designudge.org/ja/")
;; when no optimize uncomment
;; (.loadUrl @*win* (str "file://" (.resolve path (js* "__dirname") "../../../index.html")))
(.on @*win* "closed" (fn [] (reset! *win* nil))))))
変えた部分は、ロードするHTMLをファイルではなく、別のドメインのサイトにしている部分です。
するとどう表示されるかというと、サイトが表示されるはずです。
このブラウザウインドウは独立して普通のHTMLと同様にJavaScriptを読み込むことが出来ます。
つまり、動的にHello Worldを表示させたい場合は、HTML上で読み込むためのJavaScriptを出力させればいいわけです。
現在は、Electron用のClojureScriptのみがJavaScriptとして出力されていますが、ブラウザから読み込む用のプロジェクトを作成すれば良いのです。
ちなみに、デフォルトプロジェクトでは、ブラウザ向けのClojureScriptは用意してありません。
今から作成していっても良いのですが、最初からブラウザ向けのClojureScriptプロジェクトが用意されているテンプレートを利用しましょう。
descjopで、ブラウザ向けのClojureScriptプロジェクトを含んでいるプロジェクトは、OmベースのテンプレートとReagentベースのテンプレートの2つになります。
全体理解を深めるために、Hello Worldプロジェクトは置いておいて、こちらのプロジェクトを使ってみましょう。
descjop で遊ぼう day 8 : Om basedテンプレートを使ってみる
さて、前回と前々々回で、デフォルトテンプレートを使ったHelloWorldプロジェクトを生成してみました。
前回すこし話をしましたが、動的にHelloWorldをやるには、デフォルトプロジェクトよりも、ビルド設定が進んでいるOm basedプロジェクトやReagent basedプロジェクトのほうが手っ取り早いです。デフォルトプロジェクトは本当にまっさらな感じなので。
ということで、今回はOm based テンプレートからプロジェクトを生成し、実行してみましょう。
Om basedプロジェクトやReagent basedプロジェクトは、デフォルトテンプレートと構造が異なるだけでなく、実行方法も異なります。
1. プロジェクトの生成とNode.jsモジュールのインストール
Om basedテンプレートの利用方法は、デフォルトテンプレートと同様ですが、最後に+omとつけます。
$ lein new descjop helloworld_om_based +om
すると、下記のように出力されます。
$ lein new descjop helloworld_om_based +om
Generating fresh descjop +om project.
これでプロジェクトが出来ました。
helloworld_om_basedプロジェクトのディレクトリに移動しておきます。
$ cd helloworld_om_based
続いて、Node.jsモジュール各種をプロジェクト内へインストールします。
$ npm install
すると、下記のような表示が出て、node_modulesディレクトリに各種モジュールが配置されます。
$ npm install
grunt@0.4.5 node_modules/grunt
├── which@1.0.9
...
└── js-yaml@2.0.5 (esprima@1.0.4, argparse@0.1.16)
grunt-download-electron@2.1.2 node_modules/grunt-download-electron
├── progress@1.1.2
...
└── github-releases@0.2.1 (minimatch@0.2.12, optimist@0.4.0, prettyjson@0.8.1, request@2.27.0)
続いて、Electronのダウンロードをしましょう。これもデフォルトテンプレートと同様の手順です
$ grunt download-electron
Running "download-electron" task
downloading [===================] 100% 0.0s
Done, without errors.
2. ClojureScriptのコンパイル
まずは、ClojureScript用のexternの解決をするために下記を実行します。
$ lein externs > app/js/externs.js
ここもデフォルトテンプレートと同様です。
さて、続いてClojureScriptのコンパイルをします。
ここからすこしデフォルトテンプレートと変わってきます。
$ lein cljsbuild once
Compiling ClojureScript.
Compiling "app/js/cljsbuild-main.js" from ["src"]...
Successfully compiled "app/js/cljsbuild-main.js" in 14.819 seconds.
Compiling "app/js/front.js" from ["src_front"]...
Successfully compiled "app/js/front.js" in 8.773 seconds.
lein cljsbuild once
を実行すると、2つのビルドが走ったのがわかるとおもいます。
一つは普通のElectron起動用のClojurescriptで、もう一つはsrc_front
というディレクトリにあるClojureScriptファイルがコンパイルされて、app/js/front.js
として出力されています。
src_front
は、ブラウザウインドウ内に表示されるHTML上から呼び出されるフロントエンド用のJavaScriptを出力するためのプロジェクトで、Omによるプログラムが記述されています。
3. Figwheelサーバの起動
これで、コンパイルは完了し、実行可能状態になったわけですが、もう一つ手順があります。
まずは、別の新しいターミナルウインドウを開き、helloworld_om_basedプロジェクトのディレクトリに移動します。
$ cd helloworld_om_basedのあるディレクトリ
続いて下記コマンドを入力します。
$ lein trampoline figwheel frontend
すると、このように画面に表示されます。
$ lein trampoline figwheel frontend
Figwheel: Starting server at http://localhost:3449
Focusing on build ids: frontend
Compiling "app/js/front.js" from ["src_front"]...
Successfully compiled "app/js/front.js" in 8.435 seconds.
Started Figwheel autobuilder
Launching ClojureScript REPL for build: frontend
Figwheel Controls:
(stop-autobuild) ;; stops Figwheel autobuilder
(start-autobuild [id ...]) ;; starts autobuilder focused on optional ids
(switch-to-build id ...) ;; switches autobuilder to different build
(reset-autobuild) ;; stops, cleans, and starts autobuilder
(reload-config) ;; reloads build config and resets autobuild
(build-once [id ...]) ;; builds source one time
(clean-builds [id ..]) ;; deletes compiled cljs target files
(fig-status) ;; displays current state of system
(add-dep [org.om/om "0.8.1"]) ;; add a dependency. very experimental
Switch REPL build focus:
:cljs/quit ;; allows you to switch REPL to another build
Docs: (doc function-name-here)
Exit: Control+C or :cljs/quit
Results: Stored in vars *1, *2, *3, *e holds last exception object
Prompt will show when figwheel connects to your application
これで準備が整いました。

4. アプリの起動
先ほどのコンパイルをやっていたターミナルウインドウに戻り、Electronを起動しましょう。
OSXの場合は下記で起動します。
$ ./electron/Electron.app/Contents/MacOS/Electron app
これで、「Hello om world!」と出力されていれば成功です。
明日はいよいよボタンをつけてみます。
descjop で遊ぼう day 9 : Om basedテンプレートにボタンをつける
さて、今日はOm basedテンプレートから作ったhelloworld_om_based
プロジェクトにボタンでもつけていってみましょうか。
ようやくアプリ製作ぽくなってきましたね。
さて、アプリケーションの表示部分を変更するには、どこのファイルをいじればいいのでしょうか。
正解はsrc_front / helloworld_om_based_om / core.cljs
です。
ボタンなどを作るには、ここのClojurescriptファイルを修正します。
ボタンを置き、Alertを出してみる
Omは、独特な考え方でアプリケーションの状態を変化させていきます。
なので、その部分とは別に、まずは構造を変えてみるところからやっていきましょう。
まず、ちょっとDOMの構造を変えるところからやってみます。
現状、src_front / helloworld_om_based_om / core.cljs
のコードは下記の通りになっているはずです。
(ns helloworld_om_based-om.core
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[figwheel.client :as fw :include-macros true]))
(enable-console-print!)
(fw/watch-and-reload
:websocket-url "ws://localhost:3449/figwheel-ws"
:jsload-callback 'mount-root)
(defonce app-state (atom {:message "Hello om world!"}))
(defn mount-root []
(om/root
(fn [state owner]
(reify om/IRender
(render [_]
(dom/h1 nil (:message state)))))
app-state
{:target (. js/document
(getElementById "app"))}))
(defn init! []
(mount-root))
(init!)
このうち、
(defn mount-root []
(om/root
(fn [state owner]
(reify om/IRender
(render [_]
(dom/h1 nil (:message state)))))
app-state
{:target (. js/document
(getElementById "app"))}))
の部分が、画面に出力されるコンポーネントを定義しているところなのですが、ここにdom/h1
という関数があるのがわかるとおもいます。
これは、DOMツリーを構築するための関数でここでは、h1タグを生成しています。h1があるので、他にもdivとかそういうHTMLタグに相当するものがあります。
実際は、om.dom/h1は、React.DOM.h1が呼び出されます。
他にも下記のHTML要素がサポートされています。
a abbr address area article aside audio b base bdi bdo big blockquote body br
button canvas caption cite code col colgroup data datalist dd del details dfn
dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5
h6 head header hr html i iframe img input ins kbd keygen label legend li link
main map mark menu menuitem meta meter nav noscript object ol optgroup option
output p param picture pre progress q rp rt ruby s samp script section select
small source span strong style sub summary sup table tbody td textarea tfoot th
thead time title tr track u ul var video wbr
それでは、上記のコードをちょっと変えてみましょう。
下記のように変更します。
(defn mount-root []
(om/root
(fn [state owner]
(reify om/IRender
(render [_]
(dom/div nil
(dom/h1 nil (:message state))
(dom/button #js {:onClick (fn [e]
(js/alert "pressed!"))}
"Hello")))))
app-state
{:target (. js/document
(getElementById "app"))}))
dom/h1
をdom/div
でくるみ、dom/h1
の下にdom/button
を配置しました。
dom/button
は、ボタンです。
で、ここにon-click時のイベントとして"pressed!"というアラートが出るようにしています。

ここで、再びElectronを起動するか、もしくはすでに起動中のElectronをみると、ボタンが表示されており、クリックすると"pressed!"とアラートが出るでしょう。
明日も続きを書きます。
descjop で遊ぼう day 10 : Om basedテンプレートにボタンをつけるその2
続けてhelloworld_om_based
プロジェクトにボタンをつけて動的な処理をつけていきます。
前回の続きなので、今から読んだ方は前回のところまで作業を終えておいてください。
さて、ボタンを押すと、JavaScriptのアラートが出たのですが、まだそれだけです。
続いて、ボタンを押したら見出しが変わるようにしてみましょうか。
(js/alert "pressed!")
は、一旦コメントアウトして、(om/transact! state :message (fn [] "World."))
を追加します。
追加しおえたコードは、下記のとおりとなります。
(defn mount-root []
(om/root
(fn [state owner]
(reify om/IRender
(render [_]
(dom/div nil
(dom/h1 nil (:message state))
(dom/button #js {:onClick (fn [e]
;; (js/alert "pressed!")
(om/transact! state :message (fn [] "World.")))
:className "press"}
"Hello")))))
app-state
{:target (. js/document
(getElementById "app"))}))
transact!
というのは、ざっくりと言うと、アプリケーションの状態を書き換える関数で、
(defn transact!
([cursor f] ...)
([cursor korks f] ...)
([cursor korks f tag]) ...)
こんな風に利用できることが、APIマニュアルに書かれています。
今回の使用例でいうと、
(om/transact! カーソル マップのキーワード 実行される関数)
という形で書いています。
ここで、カーソルという概念が出てきました。カーソルというのは、アプリケーション全体の状態のうち、コンポーネントが管理するべきアプリケーションの状態の一部です。
カーソルが活躍するのはコンポーネントを使ってアプリケーションを構築しはじめたときです。
現時点では、アプリケーションの状態くらいにとらえておいてよいでしょう。
Omでは、アプリケーションを作成する際に、アプリケーションの状態を管理するマップを用意して、それを渡すという状態の管理方法を使っています。
ここで、コード全体を見渡してみます。
(defonce app-state (atom {:message "Hello om world!"}))
(defn mount-root []
(om/root
(fn [state owner]
(reify om/IRender
(render [_]
(dom/div nil
(dom/h1 nil (:message state))
(dom/button #js {:onClick (fn [e]
;; (js/alert "pressed!")
(om/transact! state :message (fn [] "World.")))
:className "press"}
"Hello")))))
app-state
{:target (. js/document
(getElementById "app"))}))
(defn init! []
(mount-root))
まず、(defonce app-state (atom {:message "Hello om world!"}))
というところで、最初の状態を定義しています。
app-state
がアプリケーションの状態を指しているのです。
mount-root
関数は今まで取り上げてきた通りですが、内容としてはom/root
を実行した結果を返します。
om/root
は、Omアプリケーションを作る際に必要なアプリケーションのルートで、実際にはコンポーネントなどを用意してそれらを組み合わせることでOmアプリケーションとして機能させます。
今回は直接OmアプリケーションのRoot内でいろいろやっていますが。
root関数は、こんな風に定義されており、
(defn root
([f value options] ...))
今回は、app-state
を第2引数に渡しています。
app-state
は、IRender
かIRenderState
のインスタンスを返す関数として定義されるf
に対して、ここではstate
という名前で渡ってくるように定義しています。
それによって、(:message state)
は、ここでは(:message @app-state)
と同様の結果が得られるのです。
Omでは、アプリケーションの状態の変化に応じて、仮想DOMを自動で更新することができます。
例えば下記のようにコードを書き換えてみましょう。
(defn mount-root []
(om/root
(fn [state owner]
(reify om/IRender
(render [_]
(dom/div nil
(dom/h1 nil (:message state))
(dom/p {:className "my-message"} (:message state))
(dom/button #js {:onClick (fn [e]
;; (js/alert "pressed!")
(om/transact! state :message (fn [] "World.")))
:className "press"}
"Hello")))))
app-state
{:target (. js/document
(getElementById "app"))}))
h1の下にpタグを出力する(dom/p {:className "my-message"} (:message state))
を置きました。
実行すると、ボタンを押した際にh1要素とp要素両方が変わっているのがわかるはずです。
編集後記
Greativeでは、1/4から業務開始をしているのですが、正月休みが明けて1週間経ちました。
そして、今週末また3連休です。
結構このコンボでリズムが狂ってしまう人っているのではないでしょうか。
次号はもう一回だけ、アドベントカレンダー特集をやります。
次は11日目から15日目まで。なぜそこで特集が終わるのかというと、そこまでで力尽きてしまったからです。
通常運転をしつつ、続きを書いていきます。
Colophon
- 編集長
- Greative GK. 原一浩 ( kara_d )
- 製版システム
- Clojure / Compojure / Ring / Enlive / markdown-clj / Jetty / MySQL
- Share this magazine!
- Follow designudge
- Follow @designudge