Gulp でビルドスクリプトを書く(PureScript で Hello, World!)

これらに続く第4話。前回では NPM の scripts を使ってコマンドを書いてビルドをしていたんだが、sh と cmd と2つ管理しないといけなかったり、ワンライナーなんで保守がしづらかったりと問題があるので、今回はビルドツールである Gulp を使って PureScript のコードをビルドするようにする。

おさらい

前回使ったワンライナーはこんなんだった。

bower install && psc --ffi bower_components\purescript-*\src\**\*.js bower_components\purescript-*\src\**\*.purs src\Main.purs && psc-bundle output\*\*.js --module Main --main Main -o output\main.js && copy src\index.html output\

4つのステップがあるのでそれぞれ見ていく。

bower install

bower.json に記載した依存をインストールする。

psc --ffi bower_components\purescript-*\src\**\*.js bower_components\purescript-*\src\**\*.purs src\Main.purs

PureScript ソースコードコンパイルして JavaScript に。

psc-bundle output\*\*.js --module Main --main Main -o output\main.js

CommonJS 仕様の JS コードをブラウザーで実行できるように変換する。

copy src\index.html output\

JS を読み込む HTML を成果物フォルダーにコピーする。

bower install は初回にこれをそのまま実行してもらうとして、それ以外を Gulp スクリプトに変換していく。

gulpfile.js

Gulp スクリプトは単なる JS のコードで require('gulp') すると使える内部 DSL である。今のワンライナーでは psc コマンドで生成される途中成果物と psc-bundle した後の最終成果物が同じフォルダー下にあって管理の問題上分けたいので、最終成果物は dest フォルダー下に置かれるようにする。そうすると、所望の Gulp スクリプトは下記のようになる。これを gulpfile.js として保存する。

'use strict';

var gulp = require('gulp');
var purescript = require('gulp-purescript');

var sources = [
  'src/**/*.purs',
  'bower_components/purescript-*/src/**/*.purs',
];

var foreigns = [
  'src/**/*.js',
  'bower_components/purescript-*/src/**/*.js'
];

var destination = 'dest';

gulp.task('make', function () {
  return purescript.psc({ src: sources, ffi: foreigns });
});

gulp.task('bundle', ['make'], function () {
  return purescript.pscBundle({
      src: 'output/**/*.js',
      output: destination + '/main.js',
      module: 'Main',
      main: 'Main'
  });
});

gulp.task('copy', function () {
  return gulp.src(['src/**/*.html'])
             .pipe(gulp.dest(destination));
});

gulp.task('default', ['bundle', 'copy']);

雰囲気でだいたい分かると思うが簡単に説明すると、require('gulp-purescript') で Gupl スクリプト内で PureScript 固有の記述ができるようになる。gulp.task 関数は第1引数にタスク名、第2引数に依存するタスクの配列、第3引数にタスクの内容のクロージャーを渡すようになっている。第2・第3引数は省略することができる。

このようにタスクを記述すると下記のようにタスクを実行することができるようになる。

> gulp make

引数なしで gulp とだけ実行すると default タスクが実行される。

依存の追加

新たに、Gulp を使用するようになったので package.jsondevDependenciesgulpgulp-purescript を追加する。

  "devDependencies": {
    "purescript": "0.7.2",
    "bower": "^1.4.1",
    "gulp": "^3.9.0",
    "gulp-purescript": "^0.7.0"
  }

NPM でインストールした Bower と Gulp を呼べるように package.jsonscripts を下記のようにする。当然既存のワンライナーのビルドコマンドはもう要らない。

  "scripts": {
    "bower": "bower",
    "gulp": "gulp"
  }

ビルド手順

以上をまとめるとビルド手順は下記となる。

> npm install
> npm bower install
> npm gulp

ビルドを少し速くする

今の copy タスクでは前回ビルドから変更点がなくてもコピーしてしまっているので、最終変更日時を確認して必要のあるときだけコピーするようにする。変更点は下記である。

package.json

  "devDependencies": {"gulp-newer": "^0.5.1"
  }

gulpfile.js

var newer = require('gulp-newer')
…
gulp.task('copy', function () {
  return gulp.src(['src/**/*.html'])
             .pipe(newer(destination))
             .pipe(gulp.dest(destination));
});

実行するコマンドに変更はない。

NPM と Bower に依存を管理させる(PureScript で Hello, World!)

前々回の記事「PureScript ver. 0.7.0 以上での Hello, World!」、前回の記事「PureScript ver. 0.7.0 以上で “ブラウザーで” Hello, World!」に次ぐ記事。

NPM の package.json とか Bower の bower.json とかに依存を書いてビルドを簡単にするのが今回の目的。

おさらいと前提

ファイル一覧。

> dir /b
src
> dir /b src
index.html
Main.purs

ファイルの中身。src\index.html は下記。

<html><head><script type="text/javascript" src="main.js" ></script>

src\Main.purs は下記。

module Main where

import Control.Monad.Eff.Console (log)

main = log "Hello, World!"

NPM と Bower と依存するパッケージはすでにインストールしてある前提。

package.json

NPM の設定ファイルは package.jsonnpm init コマンドでいくつか質問に答えれば下記のような package.json ができるはず。

{
  "name": "purescript-hello-console",
  "version": "1.0.0",
  "main": "index.html",
  "dependencies": {},
  "devDependencies": {
    "bower": "^1.4.1",
    "purescript": "^0.7.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/kakkun61/purescript-playground.git"
  },
  "author": "Kazuki Okamoto",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/kakkun61/purescript-playground/issues"
  },
  "homepage": "https://github.com/kakkun61/purescript-playground",
  "description": ""
}

dependencies のバージョン番号のところの ^ はセマンティックバージョンを考慮して互換性のあるバージョンを表す NPM の文法らしい。

bower.json

Bower の設定ファイルは bower.json。NPM と同じように bower init コマンドで質問に答えていけば下記のような bower.json ができるはず。

{
  "name": "purescript-hello-console",
  "version": "1.0.0",
  "main": "index.html",
  "authors": [
    "Kazuki Okamoto"
  ],
  "license": "MIT",
  "homepage": "https://github.com/kakkun61/purescript-playground",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "purescript-console": "~0.1.0"
  }
}

~^ みたいなものっぽいけどいまいち分かってないので「node.js - What is the bower version syntax? - Stack Overflow」読んどいて。(丸投げ

ここまでで npm installbower install で依存をインストールすることはできるようにはなるけどビルドは手打ちなんでスクリプトにしておく。

NPM のスクリプト機能

package.jsonscripts の項目に下記を追記する。

    "build": "bower install && psc --ffi 'bower_components/purescript-*/src/**/*.js' 'bower_components/purescript-*/src/**/*.purs' src/Main.purs && psc-bundle output/*/*.js --module Main --main Main -o output/main.js && cp src/index.html output/",
    "build-cmd": "bower install && psc --ffi bower_components\\purescript-*\\src\\**\\*.js bower_components\\purescript-*\\src\\**\\*.purs src\\Main.purs && psc-bundle output\\*\\*.js --module Main --main Main -o output\\main.js && copy src\\index.html output\\",

ユーザーが NPM で Bower をインストールしたことを考えると bower コマンドにパスが通ってない可能性があるので $(npm bin)/bower ってしておくべきなんだけど、package.json の中に書く場合は npm bin がパスに追加されている。($(npm bin)/bower だけ cmd じゃない…… cmd で同じことしようとすると for /f "usebackq" %p in (`npm bin`) do %p\bower とかになるはず。)

ここまで準備するとユーザーは NPM をインストールした後、下記コマンドでビルドしできた output\index.html をブラウザーで開けばよい。

> npm install
> npm run build-cmd

編集

2015.10.18

package.jsondependencies に記述していた bowerpurescriptdevDependencies に移した。これらは成果物が依存しているわけではなく開発時にのみ依存するため。

PureScript ver. 0.7.0 以上で “ブラウザーで” Hello, World!

先日の記事では Node で Hello, World! するところまでできた。では、それをブラウザーで動かすにはどうするんだって思って調べてうまくいったのでまとめる。

CommonJS

psc コマンドで吐いた Javascript には require 関数が使われてて複数のファイルに分かれていてこいつどうするんだって思ってたんだけど、これは CommonJS っていう仕様なのね。ブラウザーは CommonJS に対応してないので(全部がそうか調べたわけじゃないけど)こいつをなくす変換をする必要がある。

Browserify

require ブラウザー」とかで検索すると Browserify (とか RequireJS)が引かっかるのでそれを使うのかと思ったんだけど、これを使わなくていいみたい。

psc-bundle

この WikiPureScript Without Node」に書いてあって psc-bundle コマンドを使えばいい。psc をインストールしたときについてきているはず。このコマンドを使うとモジュールに分かれた JS を1つにまとめた JS にしてくれる。

> psc-bundle output\*\*.js --module Main --main Main -o main.js

--module オプションと引数で、どのモジュールがエントリーポイントなのかを指定する。そのモジュールから辿れない関数は成果物から除去される。このオプションがなければ依存モジュールの全部の関数が成果物に含まれる。

--main オプションと引数で、実行される main 関数のあるモジュールを指定する。JS 読み込み時に実行したいわけではなかったらこのオプションは要らない。

-o オプションと引数でどのファイルに出力するかを指定する。なければ標準出力に出力される。

残りの引数には psc コマンドで生成された JS を指定する。

これで先日の Main.purs をコンパイルした JS をバンドルすると下記 JS が生成される。

// Generated by psc-bundle 0.7.1.0
var PS = { };
(function(exports) {
  /* global exports, console */
  "use strict";

  // module Control.Monad.Eff.Console

  exports.log = function (s) {
    return function () {
      console.log(s);
      return {};
    };
  };
 
})(PS["Control.Monad.Eff.Console"] = PS["Control.Monad.Eff.Console"] || {});
(function(exports) {
  // Generated by psc version 0.7.1.0
  "use strict";
  var $foreign = PS["Control.Monad.Eff.Console"];
  var Prelude = PS["Prelude"];
  var Control_Monad_Eff = PS["Control.Monad.Eff"];
  exports["log"] = $foreign.log;;
 
})(PS["Control.Monad.Eff.Console"] = PS["Control.Monad.Eff.Console"] || {});
(function(exports) {
  // Generated by psc version 0.7.1.0
  "use strict";
  var Control_Monad_Eff_Console = PS["Control.Monad.Eff.Console"];     
  var main = Control_Monad_Eff_Console.log("Hello, World!");
  exports["main"] = main;;
 
})(PS["Main"] = PS["Main"] || {});

PS["Main"].main();

そして下記の HTML ファイルを生成してブラウザーに読ませると無事ブラウザーのコンソールに Hello, World! が表示される。

<html><head><script type="text/javascript" src="main.js" ></script>

参考

PureScript ver. 0.7.0 以上での Hello, World!

PureScript 0.7.1.0 で Node でコンソールに Hello, World! するところまでできたのでそのまとめ。

実例による PureScript を読んで勉強していたんだけど、先月終わりのバージョンアップの関係でそのままでは動かなくって調べたんでまとめる。英語の本家の方のドキュメントもこれから対応させていくらしい。

コマンドは Windows コマンドプロンプトのを使っているので UNIX 系の人は適宜読み替えること。

環境構築

用意するものは、PureScript コンパイラー(psc)と Node(NPM も使う)と Bower。(好みによって Cabal も。)NPM と Bower (と Cabal)がどういう使い分けをされてるかは以下な感じ。

  • NPM
    • Bower のインストー
    • Grunt のインストール(自分はまだ使ってない)
    • 開発ツールの管理に使うっぽい
  • Bower
    • purescript-prelude のインストー
    • ライブラリーの管理はこっちでするっぽい
  • Cabal

psc のインストー

Homebrew なり Chocolatey なり NPM なり Cabal なり、直接バイナリーをインストールするなりする。詳しくはこちら

Cabal というか Stack が楽なので Stack でインストールする方法は次の通り。

Stack はここからバイナリーをダウンロード。stack setup して、stack install purescript すると Stackage にないファイルが必要だといわれるのでそれを設定ファイルに記述する。設定ファイルの場所は Windows なら %USERPROFILE%\AppData\Roaming\stack\global\stack.yaml。さっきのエラーにファイルの場所が書かれている。設定ファイルに追記するものもエラーに書かれているのでそれを下記のように追記する。

extra-deps:
    - aeson-better-errors-0.9.0
    - bower-json-0.7.0.0
    - boxes-0.1.4
    - pattern-arrows-0.0.2

で、もう1度 stack install purescript するとインストールされる。どこに実行ファイルが保存されたかが表示されるのでパスを通す。

Node と NPM のインストー

apt-get なり Homebrew なり Chocolatey なり直接インストーラーでインストールするなりする。

Bower のインストー

> npm install -g bower

Hello World プログラム

下記が 0.7.0 以降での Hello World プログラム。Main.purs で保存する。

module Main where

import Control.Monad.Eff.Console (log)

main = log "Hello, World!"

0.7.0 より前のは下記。

module Main where

import Debug.Trace

main = trace "Hello, World!"

0.7.0 での変更点から今回のプログラムに関係するものを取り出すと下記になる。移行ガイドもある。

  • Debug.Trace モジュールは Control.Monad.Eff.Console モジュールに置き換えられた
  • psc に標準ライブラリーがついてこなくなった

コンパイル

先のプログラムが依存するモジュールをインストールする。

> dir
Main.purs
> bower install purescript-console
> dir bower_components
purescript-console
purescript-eff
purescript-prelude

コンパイルする。

> psc --ffi 'bower_components\purescript-*\src\**\*.js' 'bower_components\purescript-*\src\**\*.purs' Main.purs
> dir output
Control.Monad.Eff
Control.Monad.Eff.Class
Control.Monad.Eff.Console
Control.Monad.Eff.Unsafe
Main
Prelude

実行

生成された output\Main\index.js ファイルを見ると main 関数をエクスポートするだけのプログラムになっているので、実行する Javascript を書く。ここでは main.js としておく。

require("Main").main();

Node で require するためのパスを指定して実行する。

> set NODE_PATH=%cd%\output
> node main.js
Hello, World!

参考

stack コマンドを使ってみた

6月9日に FP Complete から stack コマンドがリリースされました(公式ブログ)。とりあえずブログ1文目を訳すとこんな感じです。

stack は新しく、完璧で、クロスプラットフォームで動作する開発ツールで、新参も古参も対象にしていて、コンパイラーをインストールしてセットアップして、必要なパッケージをインストールして、ビルドして、プロジェクト内の1つ以上のパッケージのテストをするもしくはベンチマークを取るためのものです。

stack is a new, complete, cross-platform development tool aimed at both new and experienced Haskell developers alike, for installing and setting up the compiler, installing packages needed, and building, testing or benchmarking one or more packages in a project at a time.

とりあえずこれさえ入れておけば Haskell に必要なコマンドはこれで完結するというような謳い文句です。1つのプロジェクトをビルドしたら別のプロジェクトがビルドできなくなったというようないわゆる deps hell も解決すると書かれています。

stack コマンド

stack ghcGHC が起動します。インストールされていなければダウンロードしてインストールからしてくれます。同じように stack ghcistack runghc が使えます。

stack build でプロジェクトのビルドができます。設定は stack.yamlproject-name.cabal ファイルに書きます。stack は cabal を置き換えるものらしいですが、.cabal ファイルは使うようです。

cabal install に当たるようなコマンドはなさそうです。

設定ファイル

.cabal ファイルは cabal が使っていたものと同じです。stack.yaml は Stackage のどのバージョンを使用するかを表す resolver が必須になります。なので、最低限下記な感じ。

resolver: lts-2.13

Stackage に存在しないパッケージを利用する場合は extra-deps を追記します。

extra-deps:
- foo-1.2.3.4
- bar-9.8.7.6

パッケージ

インストールされるパッケージは、GHC と一緒についてくるものについては global に、Stackage にあるものについては snapshot に、extra-deps で指定したものについては local に保存されます。global には追加では一切パッケージは保存されなくて、snapshot はプロジェクト間で共有、local はプロジェクトごとに固有になっていて、deps hell を避けるようになっています。

感想

とりあえず Windows での Haskell 環境がめっちゃ楽になったぞい、です。

詳しい内容はこの辺Wiki を見てください。

ちなみにまだバージョン 0.0.0 ですが、0.1 が出ると tanakh さんがもっと詳しい記事を書いてくれると思います。

今見たら 0.0.1 は欠番?で 0.0.2 が出てた。

(2015.06.25 追記) tanakh さんがもう少し詳しくて新しい情報をまとめてくれました。

TEX Yoda TrackPoint Keyboard が来た&組み立てた

トラックポイントが付いてて Cherry MX スイッチなキーボードがクラウドファンディングで買えたのでポチった。

TEX Yoda TrackPoint Keyboard ってやつです。 特徴としては下記な感じ。

  • Cherry MX スイッチ(注文時に何色か選べる)
  • トラックポイント
  • 自分で組み立て(追加料金で組み立て済みもあった)

1月30日に購入して4月3日にアメリカ合衆国から発送されてドイツ経由で到着が4月15日。事業を始めたいっていうクラウドファンディングじゃなくて共同購入みたいな感じで購入者が多くなるほど廉くなるっていうしくみで、お値段 $199.99、送料込みで $261.47 でした。あと、日本の消費税が1000円と税関手数料が200円でそれは配達に来た郵便局員に手渡しました。消費税はどういう計算なんだろう…… そもそも税関で値段分からないような?

クラウドファンディングのサービスのロゴの入った箱とシールで来ました。

税関で箱開けられたみたいで郵便局のシールがその上に貼られてました。

量産品じゃないので TEX Beetle ってやつの箱に入ってました。

中身は筐体と回路基板とスイッチとキートップで、スイッチを1つずつ基板に半田付けしていきます。

スイッチをアルミ板にはめていって基板の穴にスイッチの端子を通したところ。キーの2倍の数の端子を穴に通さなくっちゃいけなくて結構手間取りました。

せっかく自分で半田付けするんで、秋葉原千石電商で追加のスイッチを買って場所によってスイッチを変えてみました。基本はキーボードと一緒に届いた黒軸で、修飾キーには黒より軽い赤軸を、マウスボタンにはクリック感のある茶軸を使ってみました。修飾キーを軽くしたのは小指で使うからで、マウスボタンはクリック感のあった方がいいかなとなんとなくです。追加のスイッチだけで2500円。

ここに組み立て手順書があるんですけど、単に「LED を付ける」って書いてあるところで実はスペーサーをかますべきだったみたいでした。後からパーツが余ったので気付きました。ただ、キートップが黒だとキーに埋もれるように LED が来てしまって分かりにくいのでスペーサーをかまさなかったのはわりとよかったです。

後はキートップを被せて完成。元々使ってた Filco Majestouch と。

裏に DIP スイッチがあって修飾キーの配置を変えられるので、OS でのリマップではどうにもならない Fn キーの位置だけを好みの場所に合わせました。

ちょっと値段が高かったけどトラックポイントが結構快適で満足な買い物でした。

一つ、箱の中の仕切りの紙がなぜ鏡文字だったかだけが謎。