☀️
M5Stackで蓄積したデータベースをグラフ表示するWebアプリを作る(準備編)
環境データをSDカードにSqlite3データーベースファイルで保存できたので、続いてグラフ表示するWebアプリをReactを使って作る。
TypeScript + SQLite3 WASMならたくさん記事があったので、今回はPureScript言語を採用してVite + React + PureScript + SQLite3 WASMの構成にする。
Vite+Reactプロジェクトを作成する。
~$ npm create vite@latest
> npx
> create-vite
✔ Project name: … vite-react-purs
✔ Select a framework: › React
✔ Select a variant: › JavaScript
Scaffolding project in /home/-----/vite-react-purs...
Done. Now run:
cd vite-react-purs
npm install
npm run dev
~$ cd vite-react-purs
~/vite-react-purs$ npm install
~/vite-react-purs$ npm run dev
VITE v5.4.10 ready in 145 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
ブラウザでhttp://localhost:5173/
を開いてページが正しく表示されているか確認後にCtrl+Cを押下してコンソールに戻る。
PureScriptのインストール
このページを参考にPureScriptをインストールする。
自分はもうすでにPureScriptがインストールされているので、特に何もしない。
PureScript+Reactのセットアップ
~/vite-react-purs$ spago init
[info] Initializing a sample project or migrating an existing one..
[info] Updating package-set tag to "psc-0.15.15-20241016"
Fetching the new one and generating hashes.. (this might take some time)
[info] Generating new hashes for the package set file so it will be cached.. (this might take some time)
[info] Found existing directory "src", skipping copy of sample sources
[info] Found existing file ".gitignore", not overwriting it
[info] Set up a local Spago project.
[info] Try running `spago build`
~/vite-react-purs$ spago build
~/vite-react-purs$ spago test
purs compile: No files found using pattern: src/**/*.purs
[info] Build succeeded.
🍝
You should add some tests.
[info] Tests succeeded.
テストが成功したことを確認する。
React+JavaScriptからReact+PureScriptに変更
purescript-react-basic-hooksのExampleを参考にする。
- index.htmlを編集する。
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.js"></script>
</body>
</html>
- src/index.jsを新規作成する。
src/index.js
import { main } from "../output/Main";
main();
- src/Main.pursを新規作成する。
src/Main.purs
module Main where
import Prelude
import App.Pages.Home (mkHome)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
import React.Basic.DOM.Client (createRoot, renderRoot)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window (document)
main :: Effect Unit
main = do
maybeRoot <- getElementById "root" =<< (map toNonElementParentNode $ document =<< window)
case maybeRoot of
Nothing -> throw "Root element not found."
Just r -> do
home <- mkHome
root <- createRoot r
renderRoot root (home unit)
- src/App/Pagesディレクトリを作成して、src/App/Pages/Home.pursを新規作成する。
src/App/Pages/Home.purs
module App.Pages.Home where
import Prelude
import React.Basic.DOM as DOM
import React.Basic.DOM.Events (capture_)
import React.Basic.Hooks (Component, component, useState, (/\))
import React.Basic.Hooks as React
type HomeProps
= Unit
mkHome :: Component HomeProps
mkHome = do
component "Home" \_props -> React.do
counter /\ setCounter <- useState 0
pure
$ DOM.div
{ children:
[ DOM.h1_ [ DOM.text "Home" ]
, DOM.p_ [ DOM.text "Try clicking the button!" ]
, DOM.button
{ onClick:
capture_ do
setCounter (_ + 1)
, children:
[ DOM.text "Clicks: "
, DOM.text (show counter)
]
}
]
}
- src/main.jsxは削除。
- src/index.cssは削除。
- src/App.cssは削除。
- src/App.jsxは削除。
npm run dev
してブラウザでPureScript+Reactが動作しているか確認する。
sqlite3-wasmのインストール
~$ npm install --save @sqlite.org/sqlite-wasm
vite.config.jsを編集する
Origin-Private-File-Systemを使うために
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
をvite.config.jsに追加する。
vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
},
optimizeDeps: {
exclude: ['@sqlite.org/sqlite-wasm'],
},
});
SQLite3 WASM Worker1 APIをPureScript FFIでラップする
おすすめのようなのでSQLite3 WASMのWorker API #1を使うことにして、config-getメソッドを呼ぶところまでをやってみる。
- src/Sqlite3Wasmディレクトリを作成して、src/Sqlite3Wasm/Sqlite3Wasm.jsを新規作成する。
最小限config-getメソッドを呼ぶためにcreateWorker1PromiserImpl
とconfigGetImpl
関数を定義する。
src/Sqlite3Wasm/Sqlite3Wasm.js
import {
sqlite3Worker1Promiser
} from '@sqlite.org/sqlite-wasm';
/* SQLite3 WASM Worker1 Promiser API */
export const createWorker1PromiserImpl = () => { return sqlite3Worker1Promiser.v2({}); };
export const configGetImpl = (promiser) => { return promiser('config-get', {}); };
- src/Sqlite3Wasm/Sqlite3Wasm.pursを新規作成する。
深く考えずにAPIのドキュメントを見ながらPurescript FFIでjavascriptに型を定義した(エラー処理はしていない)。
src/Sqlite3Wasm/Sqlite3Wasm.purs
module Sqlite3Wasm.Sqlite3Wasm
( ConfigGetResponse
, SQLite3Version
, Sqlite3Worker1Promiser
, configGet
, createWorker1Promiser
) where
import Prelude
import Control.Promise (Promise, toAffE)
import Effect (Effect)
import Effect.Aff (Aff)
import Effect.Uncurried (EffectFn1, runEffectFn1)
-- SQLite Version numbers
type SQLite3Version
= { libVersion :: String
, libVersionNumber :: Number
, sourceId :: String
, downloadVersion :: Number
}
{-
https://sqlite.org/wasm/doc/trunk/api-worker1.md
SQLite3 WASM Worker1 API
-}
foreign import data Sqlite3Worker1Promiser :: Type
foreign import createWorker1PromiserImpl :: Effect (Promise Sqlite3Worker1Promiser)
-- sqlite3Worker1Promiserを得る
createWorker1Promiser :: Aff Sqlite3Worker1Promiser
createWorker1Promiser = createWorker1PromiserImpl # toAffE
-- config-getメソッドの応答
type ConfigGetResponse a
= { type :: String -- "config-get"である
, messageId :: a -- 何らかの値が返却されてくる
, result :: -- 処理結果
{ version :: SQLite3Version
, bigIntEnabled :: Boolean
, vfsList :: Array String
}
}
foreign import configGetImpl :: forall a. EffectFn1 Sqlite3Worker1Promiser (Promise (ConfigGetResponse a))
-- config-getメソッド呼び出し
configGet :: forall a. Sqlite3Worker1Promiser -> Aff (ConfigGetResponse a)
configGet promiser = runEffectFn1 configGetImpl promiser # toAffE
SQLite3 WASM のバージョンをコンソールに出してみる
src/Main.pursを編集して、main関数からいま作ったSqlite3Wasm.Sqlite3Wasmモジュールを使ってSQLite3 WASMのconfig-getメソッドを呼んでみる。
src/Main.purs
module Main where
import Prelude
import App.Pages.Home (mkHome)
import Data.Maybe (Maybe(..))
import Data.Either (either)
import Effect (Effect)
import Effect.Aff (Aff)
import Effect.Aff as Aff
import Effect.Class.Console (log, logShow)
import Effect.Exception (throw)
import React.Basic.DOM.Client (createRoot, renderRoot)
import Sqlite3Wasm.Sqlite3Wasm (ConfigGetResponse)
import Sqlite3Wasm.Sqlite3Wasm as Sq3
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window (document)
sqlite3ConfigGet :: forall a. Aff (ConfigGetResponse a)
sqlite3ConfigGet = Sq3.createWorker1Promiser >>= Sq3.configGet
main :: Effect Unit
main = do
-- SQLite3 WASM Worker1 API config-getメソッドを呼び出す
Aff.runAff_ (either (log <<< Aff.message) (\r -> logShow r.result)) sqlite3ConfigGet
-- React
maybeRoot <- getElementById "root" =<< (map toNonElementParentNode $ document =<< window)
case maybeRoot of
Nothing -> throw "Root element not found."
Just r -> do
home <- mkHome
root <- createRoot r
renderRoot root (home unit)
途中適当にspago build
して不足するモジュールをspago install
する(コンパイルエラーに出てくるはず)。
実行結果
client:495 [vite] connecting...
client:614 [vite] connected.
react-dom.development.js:29895 Download the React DevTools for a better development experience: https://reactjs.org/link/react-devtools
foreign.js:3 { bigIntEnabled: true, version: { downloadVersion: 3470000.0, libVersion: "3.47.0", libVersionNumber: 3047000.0, sourceId: "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e" }, vfsList: ["unix-none","opfs","memdb","unix-excl","unix-dotfile","unix"] }
PureScript FFIを通じて SQLite3 WASMのバージョンが取得できた。
今回はここまで。
Discussion