LoginSignup
5
2

More than 1 year has passed since last update.

PureScript + Halogen + Tailwind で Web フロント開発環境構築 (in VSCode)

Last updated at Posted at 2022-11-30

概要

PureScript は活発に開発が行われているため、開発環境構築の方法を適宜まとめる必要があります。
この記事では PureScript のインストールから Web フロントエンドを開発できるところまでの環境構築をまとめます。

対象

  • PureScript の環境構築を VSCode でしたい
  • PureScript でフロントエンドの開発がしたい

TL;DR

にまとまってます

筆者の環境

細かくバージョン書いていますが多分気にしなくて大丈夫です。

  • Windows 11
    • 22H2
  • WSL2
    • 5.15.68.1
  • node
    • 18.12.0
  • npm
    • 8.19.2

PureScript のインストール

プロジェクトルートになるディレクトリを作って初期化します。

mkdir <project-name>
cd <project-name>
npm init -y

PureScript をインストールします

npm install --save-dev purescript

おおむね成功しますが、次のようにコケることがあります

...
npm ERR! [ FAILURE ] Check if 'stack' command is available
...

このあたりの事情にあまり詳しくないのですが、何回か npm install purescript をすると通ります。(それでいいのか?)
それでもダメなときは stack をインストールしてください。
stack は Haskell のプロジェクト / パッケージ管理ツールです。
詳しくはこちらから

(結構インストールめんどくさいです)

その他ツールチェインのインストール

  • spago
    • プロジェクト / パッケージ管理ツール
  • purs-tidy
    • フォーマッタ
  • esbuild
    • spago が専らバンドラとして間接的に使う。

をインストールします

npm install --save-dev spago purs-tidy esbuild

プロジェクトの初期化

npx spago init
npx spago run
...
[info] Build succeeded.
🍝

と出たら成功です!

VSCode の拡張機能のインストールと設定

VSCode 拡張 PureScript IDE をインス―ルします

VSCode の設定に次を追加しておきます

  "purescript.formatter": "purs-tidy",
  "purescript.addNpmPath": true,
  "[purescript]": {
    "editor.formatOnSave": true,
  },

formatOnSave はお好みで。addNpmPath はローカルに purescript をインストールするなら必須です。

フォーマッタの設定をします

npx purs-tidy generate-config         

できた .tidyrc.json の JSON を書き換えます(これもお好みで)

{
  "importSort": "ide",
  "importWrap": "source",
  "indent": 2,
  "operatorsFile": null,
  "ribbon": 1,
  "typeArrowPlacement": "first",
  "unicode": "source",
  "width": null
}

これでフォーマットや補完が効くようになったはずです。単に PureScript を使いたいのであれば、これで環境構築は終了となります

Halogen を使う

Halogen は PureScript の SPA フレームワークです。PureScript で SPA を作るときは一番使われているでしょう。(統計はないですが)

まず Halogen をインストールします

npx spago install halogen

Main.purs を書き換えます

module Main where

import Prelude

import Effect (Effect)
import Halogen (Component, defaultEval, mkComponent, mkEval)
import Halogen.Aff (awaitBody, runHalogenAff)
import Halogen.HTML as HH
import Halogen.VDom.Driver (runUI)

main :: Effect Unit
main = runHalogenAff do
  body <- awaitBody
  runUI component unit body

component :: forall q i o m. Component q i o m
component =
  mkComponent
    { initialState: \_ -> unit
    , render: \_ -> HH.div_ [ HH.text "Hello Halogen!" ]
    , eval: mkEval $ defaultEval
    }

バンドルします

npx spago bundle-app -y -t public/index.js

public/index.html ファイルを作ります

<!DOCTYPE html>
<html>
  <head>
    <title>Halogen App</title>
    <script src="./index.js"></script>
  </head>
  <body></body>
</html>

これを読み込めば Hello Halogen! と表示されるはずです!

Tailwind を使う

ちょっと面倒です

npm install --save-dev tailwindcss
npx tailwindcss init

tailwind.config.js を編集します

{
  ...
  content: ["./src/**/*.{purs,html,js}"],
  ...
}

src/index.css を作成します。

@tailwind base;
@tailwind components;
@tailwind utilities;

VSCode の Tailwind CSS IntelliSense 拡張をインストールします

VSCode の設定に諸々の追加をして tailwind の補完が出るようにします(任意の文字列で出てしまうので、うざいときは切ってください)

{
  "tailwindCSS.includeLanguages": {
    "purescript": ""
  },
  "[purescript]": {
    "tailwindCSS.experimental.classRegex": ["\"([^\"]*)\""],
  },
}

Main.purs をよしなに書き換えてビルドします

module Main where

import Prelude

import Effect (Effect)
import Halogen (ClassName(..), Component, defaultEval, mkComponent, mkEval)
import Halogen.Aff (awaitBody, runHalogenAff)
import Halogen.HTML as HH
import Halogen.HTML.Properties as HP
import Halogen.VDom.Driver (runUI)

main :: Effect Unit
main = runHalogenAff do
  body <- awaitBody
  runUI component unit body

component :: forall q i o m. Component q i o m
component =
  mkComponent
    { initialState: \_ -> unit
    , render: \_ -> HH.div [ HP.class_ $ ClassName "w-screen h-screen text-5xl flex justify-center items-center" ] [ HH.text "Hello Halogen!" ]
    , eval: mkEval $ defaultEval
    }
npx spago bundle-app -y -t public/index.js

CSS ファイルをビルドします

npx tailwindcss -i ./src/index.css -o ./public/index.css

index.html から読み込みます

  <head>
  ...
    <link rel="stylesheet" href="./index.css">
  ...
  </head>

これで Hello Halogen! が真ん中に移動します

ホットリロードできるようにする

live-server を使います

npm install --save-dev live-server  

次のコマンドをそれぞれ別のターミナルで同時に動かします

npx live-server public     
npx spago bundle-app -y -t public/index.js -w 
npx tailwindcss -i ./src/index.css -o ./public/index.css -w

これで localhost:8080 あたりを開くとファイルの変更がリアルタイムに反映される環境ができます

コマンドをまとめる

npm-run-all を使います

npm install --save-dev npm-run-all

package.jsonscript を書き換えます

  "scripts": {
    "bundle:script": "npx spago bundle-app -t ./public/index.js -y",
    "bundle:css": "tailwindcss -i ./src/index.css -o ./public/index.css -m",
    "bundle": "run-s bundle:*",
    "watch:script": "npx spago bundle-app -t ./public/index.js -w",
    "watch:css": "tailwindcss -i ./src/index.css -o ./public/index.css -w",
    "watch:server": "npx live-server ./public",
    "watch": "run-p watch:*"
  },

最終的にバンドルするには

npm run bundle

開発時にホットリロードするには

npm run watch

とします

余談

補完が効かないんだが?

PureScript の Laugage Server は output ディレクトリを読んで補完を行っているようです。
したがって、パッケージのインストール直後などはそのパッケージの補完が効きません

npx spago build --deps-only

をすると使っているパッケージのみビルドしてくれます。これをしてみましょう

これでもダメなら VSCode のコマンドパレットから PureScript: Restart/Reconnect purs IDE server を実行してみてください

逆にすでにアンインストールしたパッケージの補完が出てうざいというときは、output フォルダの該当するディレクトリを消すか、output ディレクトリごと消してから再ビルドすればよいです。

purs-backend-optimizer を使う

purs-backend-optimizer は、purescript がビルドした後のコードを改変してからバンドルするツールで、動作速度の向上やファイルサイズの削減などが狙えます。(Halogen だとファイルサイズは変わらないか、大きくなることがあるそうです。backend-optimizer はどちらかというと Effect や ST モナドの最適化とインライン化による速度向上を重視しているようです)

npm install --save-dev purs-backend-es

spago-bundle.dhall を作成します

./spago.dhall // { backend = "purs-backend-es build" }

.gitignore に追加します

/output-es

バンドルスクリプトを変更します

"bundle:script": "npx spago -x spago-bundle.dhall build && npx purs-backend-es bundle-app -s -t ./public/index.js -y",

Jelly を使う

最近 Jelly というライブラリを作っています

これを使ってアプリケーションを作ります

npx spago install jelly aff foldable-traversable jelly-hooks

Main.purs を書き換えます

module Main where

import Prelude

import Data.Foldable (traverse_)
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class (liftEffect)
import Jelly.Aff (awaitBody)
import Jelly.Component (Component, text)
import Jelly.Element as JE
import Jelly.Hooks (runHooks_)
import Jelly.Hydrate (mount)
import Jelly.Prop ((:=))

main :: Effect Unit
main = launchAff_ do
  bodyMaybe <- awaitBody
  liftEffect $ traverse_ (runHooks_ <<< mount component) bodyMaybe

component :: forall m. Component m
component =
  JE.div [ "class" := "w-screen h-screen text-5xl flex justify-center items-center" ] do
    text "Hello Jelly!"

これで OK です。Jelly についての記事はこちら

https://zenn.dev/yukikurage/articles/4735819c3b421b
https://zenn.dev/yukikurage/articles/d78a464b815aec
https://zenn.dev/yukikurage/articles/367d844d79de20

0.8.0 で破壊的変更を入れています。最新のドキュメントはここにあります↓

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2