yesodとwebpackを協調させてTypeScriptをビルドする方法

yesodとwebpackを協調させてTypeScriptをビルドする方法

現在yesodとTypeScriptを併用していて,yesodがhaskellアプリケーションをビルドする前にwebpackでTypeScriptをJavaScriptにビルドするということを行っています.

yesodのshakespeareのText.TypeScriptにはエラー表示が極めてわかりにくいとかグローバルにtscをインストールしないといけないとかそもそも何故か動かなくなる時があったので独自にwebpackを動かしています.

package.yaml

build-type: Custom

package.yamlでもcabalでもいいですが,build-type: Customを指定しましょう.これでstack buildにhookをかますことが出来ます.

Setup.hs

import           Distribution.PackageDescription
import           Distribution.Simple
import           Distribution.Simple.Setup
import           System.Process

main :: IO ()
main = defaultMainWithHooks simpleUserHooks
    { preBuild = pb
    }

pb :: Args -> BuildFlags -> IO HookedBuildInfo
pb _ _ = callProcess "yarn" ["run", "build"] >> return emptyHookedBuildInfo

このようにすればビルド前にyarn run buildを動かすことが出来ます.後はpackage.jsonで好きなようにすれば良いでしょう.

Application.hs

-- | main function for use by yesod devel
develMain :: IO ()
develMain = do
    (_, _, _, watchProcess) <- createProcess $ (proc "yarn" ["run", "watch"]) { create_group = True }
    develMainHelper getApplicationDev `finally` interruptProcessGroupOf watchProcess

yesod devel時にファイル変更をwebpack側などでキャッチしてyesod develの再起動をせずにTypeScriptなどをビルドしたい場合は,develMainに仕掛けを行う必要があります.今回はyarn run watchを動かしています.このようにプロセスをきちんと取り扱わないとyesod develが終了してもnodeのプロセスが終了しないので注意.

travis.yml

sudo: false

cache:
  yarn: true
  directories:
    - $HOME/.local
    - $HOME/.stack

language: node_js
node_js: "8"

addons:
  apt:
    packages:
      - libgmp-dev

before_install:
  - mkdir -p ~/.local/bin
  - export PATH=$PATH:$HOME/.local/bin
  - hash stack || travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
  - stack setup

install:
  - stack --jobs 2 --no-terminal test --only-dependencies
  - stack install hlint
  - yarn

script:
  - hlint src
  - stack --jobs 2 --no-terminal test

travisは普通にinstall時にyarnを実行すればいいでしょう.

細かい変更をmerge&rebaseして移している

goofysを使わないでS3のAPIを使うように変更するためのブランチで色々作業を行ってしまっていました.それがクラッシュ問題(別に前と比べてクラッシュ量が増えるというわけではないけど)で取り込まれるのが遅れそうなので,他のブランチに変更をrebaseしてaws関係ないところだけpickして移しています.

こんなことなら初めから別ブランチで変更を加えれば良かった…しかし,現在作業しているブランチに即座に変更を加えたい時についその場で変更してしまいます.別ブランチでcommitしてpushしておいて,travisの結果を待たずにその変更をローカルでmergeしておけば問題ないのかな.

細部の変更を別ブランチにしてpushしたらtravis ciのテストに結構時間がかかる.新しいブランチだとcache効かないんだっけ,そのへんの把握が曖昧.

jsをexternal指定してcdnjsを使う

reactとwebpackの構築は当初React & Webpack · TypeScriptを参考に行っていたのですが,importで全部結合されるからexternals要らなくね?ということで取り外していました.

当時はJavaScriptのモジュール機能とwebpackの行う作業が全くわからなかったというのも原因にあります.一時期はimportで結合しているのに外部でcdnjsを読み込んでいるというアホみたいなことを行っていました.

どうもそれが間違いということがわかってきました,大規模なjsファイルはcdnjsのようなpublic cdnを使った方が高速でキャッシュも効いて良さそうですね.現在はreactもjs-cookieも1つのページでしか使っていませんが,この先複数のページで使うこともあるかもしれません.あと吐き出されるscriptが少なくなるのでデバッグする時に便利.

なのでなるべくcdnjsを使うようにexternalsを使っていきましょう.

ES Modulesを使いたいなと思いますが流石に無理ですね,IEしかサポートしてないという状況になったら考えますが,まだfirefoxすらフラグ付きのサポートなので.

このサイト自体はimportどころかrequireとbrowserifyを使ってるんですけどね…

bootstrap関係のscriptもcdnjsのものに書き換えようとしたけどbetaになってtetherからpopperというライブラリに乗り換えていますね,とりあえず放置で…

SRI付きのaddScriptRemoteAttrsのボイラープレートを書いていくのが面倒くさくなってきたので以下の関数を追加しました.問題がなくてコミュニケーションコストが払えればyesod本体に追加の提案をしていきたい.

-- | `addScriptRemote`をSRI付きで行う
addScriptRemoteSri :: MonadWidget m => Text -> Text -> m ()
addScriptRemoteSri remote integrity =
    addScriptRemoteAttrs remote [("integrity", integrity), ("crossorigin", "anonymous")]

新しい@types/reactがエラーを吐くのでとりあえず問題ないバージョンに固定

cdn付きに切り替えるついでにパッケージのアップデートもしておいたらTS2403: Subsequent variable declarations must have the same type. Variable 'textarea' must be of type 'DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>', but here has type 'DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>'というエラーになった.

ググッてコンパイラオプションのせいかと思ってデフォルトに戻してみたが改善せず.

大体なんだこのエラーは,双方の型が完全に一致しているのにエラーが出る?意味不明.

問題のtextareaの場所に行って行削除してみたらコンパイルが通ってしまった.そもそもHTMLTextAreaHTMLTextareaどっちが正しいんだと思ってmdnを見に行ったらHTMLTextAreaが正しいらしい.HTMLTextAreaElement - Web APIs | MDN

じゃあなんでreactはTextareaHTMLAttributesなんだろうか.しかし全てこちら側で統一されているから問題ないはず,と思って@type/reactを修正してみたら今度は@type/react-domでエラーが発生した.

適当に本体にpull request出さなくて良かった,環境の問題っぽい.

どうも中途半端にアップデートしたからreactとreact-dom間でバージョンの不整合が発生しているのかなと思いyarn upgrade-interactive --latestしてみたら今度はreact-dom側で大量のエラーが発生しました.

@types/react-dom側はreact-15までしかサポートしてないので,@types/reactをバージョン15にしておくしかありませんね.

と思い@types/reactを15.5に限定してみましたがエラーは治らず.ならいっそのこと@types/react-dom側の15.5.4に固定してしまえば良いと思って選択してみましたが@types/reactは15.5を提供してないんですね…

@types/react-domは15.5しか提供しておらず,@types/reactは15.5を提供していない,詰んだかな.

詰んだので上手く行っていた時のバージョン@types/react-15.0.38と@types/react-dom-15.5.4にバージョンを固定したら上手くいきました.react-domがバージョン16に統一されるようになったらバージョンも統一されて治るだろう.

自分の環境のせいなのかライブラリがおかしいのかわからないし,どうせreact-domがバージョン16になれば治りそうなバグなので,issueを建てる気にはなりませんね.