0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ReScriptでViz.jsを使ってグラフを描く

Posted at

準備

ReScriptのインストール
npm create rescript-app@latest
実行結果
実行結果
> npx
> create-rescript-app

┌  create-rescript-app 1.7.1
│
◇  Welcome to ReScript! ─────────────────────────────────╮
│                                                        │
│  Fast, Simple, Fully Typed JavaScript from the Future  │
│  https://rescript-lang.org                             │
│                                                        │
├────────────────────────────────────────────────────────╯
│
◇  New Project ───────────────────────────────────────────╮
│                                                         │
│  Create a new ReScript 11 project with modern defaults  │
│  ("Core" standard library, JSX v4)                      │
│                                                         │
├─────────────────────────────────────────────────────────╯
│
◇  What is the name of your new ReScript project?
│  viz-test
│
◇  Select a template
│  Basic
│
◇  Versions loaded.
│
◇  ReScript version?
│  11.1.1
│
◇  ReScript Core version?
│  1.5.0
│
◇  Project created.
│
└  Happy hacking!
Viz.jsのインストール
cd viz-test
npm install @viz-js/viz

ReScriptのインストール時に指定したプロジェクト名(今回はviz-test)でディレクトリが作成される。そのディレクトリに移動してからインストールを実行する

viz_test.html
<!doctype html>
<html>
<head>
<title>Viz Test</title>
<meta charset="utf8">
</head>
<body>
<script type="module" src="VizTest.res.js"></script>
</body>
</html>
VizTest.res
type viz
@module("@viz-js/viz") external instance: () => promise<viz> = "instance"
@send external renderSVGElement: (viz, string) => Dom.element = "renderSVGElement"
@get external body: Dom.document => Dom.element = "body"
@send external appendChild: (Dom.element, Dom.element) => unit = "appendChild"

let str = `digraph {
  "見 て い る" -> "望遠鏡 で" [label = implement]
  "見 て い る" -> "星 を" [label = object]
  "女の子 を" -> "見 て い る" [label = adjectivals]
  "見 た" -> "公園 から" [label = source]
  "見 た" -> "女の子 を" [label = object]
}`

let viz = await instance()
document->body->appendChild(viz->renderSVGElement(str))
ディレクトリ構成
viz-test/
├── rescript.json
├── package.json
└── src/
    ├── viz_test.html
    └── VizTest.res

VizTest.resでやっていること

instanceのバインド

Viz.jsinstanceをバインドします。
https://viz-js.com/api/#viz.instance を見るとinstanceの型は() => promise<viz>です。

external instance: () => promise<viz> = "instance"

JavaScriptのモジュールをバインドするので頭に@module()を付けます。

@module("@viz-js/viz") external instance: () => promise<viz> = "instance"

renderSVGElementのバインド

renderSVGElementの型は https://viz-js.com/api/#viz.Viz.renderSVGElement を見るとstring => SVGSVGElementですが、SVGSVGElementってなんでしょう。
最後はappendChildで貼り付けるのでDom.Element型を返すんだろうと判断しました。
オブジェクトメソッドなので@sendでバインドします。

@send external renderSVGElement: (viz, string) => Dom.element = "renderSVGElement"

DOMのバインド

ReScriptでDOMを扱うを御参照ください。

ビルドして実行

ビルド
npm run res:build

コンパイルはエラーなく終了します。
ローカルサーバを起動してviz_test.htmlを読み込ませ、コンソールを見るとエラーが出ています。

viz_test.html:1 Uncaught TypeError: Failed to resolve module specifier "@viz-js/viz". Relative references must start with either "/", "./", or "../".

npmパッケージが読み込めないようです。

Parcelでバンドル

Parcelのインストール
npm install --save-dev parcel
バンドルの実行
npx parcel build src/VizTest.res.js
🚨 Build failed.

@parcel/optimizer-swc: await isn't allowed in non-async function

バンドルに失敗しました。
トップレベルでawaitを使っているのが駄目なんでしょうか。

VizTest.resの修正

thenを使って書き換えます。

VizTest.res
instance()->then(viz => {
  document->body->appendChild(viz->renderSVGElement(str))
})

npm run res:buildでコンパイルするとエラーが出ます。

The value then can't be found

thenが見つからないと言っています。居場所を教えます。

VizTest.res
instance()->Promise.then(viz => {
  document->body->appendChild(viz->renderSVGElement(str))
})

コンパイルするとまだエラーが出ます。

This has type: unit
  But it's expected to have type:
    RescriptCore.Promise.t<'a> (defined as promise<'a>)

Promise.thenの型はlet then: (t<'a>, 'a => t<'b>) => t<'b>です。1
いま第一引数がpromise<viz>なので、返り値はpromise<'b>でないといけません。

VizTest.res
instance()->Promise.then(viz => {
  document->body->appendChild(viz->renderSVGElement(str))
  Promise.resolve()
})

解決済みのPromiseを返すようにしました。
コンパイルするとまたエラーです。

This function call is at the top level and is expected to return `unit`. But it's returning `RescriptCore.Promise.t<unit>`.

トップレベルなのにunitが返ってないとのことです。
ignore付けます。

VizTest.res
instance()->Promise.then(viz => {
  document->body->appendChild(viz->renderSVGElement(str))
  Promise.resolve()
})->ignore

これでコンパイルが通ります。

再びParcelでバンドル

エラーがでたのでキャッシュを消しました。

rm -rf .parcel-cache

バンドルします。

npx parcel build src/VizTest.res.js

今度は成功しました。

ディレクトリ構成
viz-test/
├── rescript.json
├── package.json
├── src/
│   ├── viz_test.html
│   └── VizTest.res
└── dist/
    ├── VizTest.res.js
    └── VizTest.res.js.map

viz_test.htmlの修正

distの下のVizTest.res.jsを読み込むよう修正します。

viz_test.html
<script src="../dist/VizTest.res.js"></script>

ブラウザで確認するとエラーなく動いています。

おまけ

node_modules/@viz-js/viz/libにあるviz-standalone.mjsをコピーしてきてsrcに置きます。

ディレクトリ構成
viz-test/
├── rescript.json
├── package.json
└── src/
    ├── viz_test.html
    ├── VizTest.res
    └── viz-standalone.mjs

これで

VizTest.res
type viz
@module("./viz-standalone.mjs") external instance: () => promise<viz> = "instance"
@send external renderSVGElement: (viz, string) => Dom.element = "renderSVGElement"
@get external body: Dom.document => Dom.element = "body"
@send external appendChild: (Dom.element, Dom.element) => unit = "appendChild"

let str = `digraph {
  "見 て い る" -> "望遠鏡 で" [label = implement]
  "見 て い る" -> "星 を" [label = object]
  "女の子 を" -> "見 て い る" [label = adjectivals]
  "見 た" -> "公園 から" [label = source]
  "見 た" -> "女の子 を" [label = object]
}`

let viz = await instance()
document->body->appendChild(viz->renderSVGElement(str))

と、相対パスでインポートすると、なにもかもうまくいきます。
node_modulesからモジュールを引っ張ってくるのが正しいのか分からなかったので、Parcelでバンドルすることにしました。

  1. https://rescript-lang.org/docs/manual/latest/api/core/promise#value-then

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?