Cloudscape を Fresh(Deno) で動かす

packup で スタイルが付いた React コンポーネントを取り扱う で Cloudscape を扱う記事を書いた。

その時はある程度できることをまとめたが、Fresh で動かす方法が分かったので、これもまた纏めてておきたい。

参考

Fresh With Cloudscape

参考にも載せている Issue で Fresh で MUI を使う方法について記述がある。

MUI の場合には、import map に次のような記述を試みている。

import_map.json
1
2
3
4
5
{
"imports": {
"@mui/material": "https://esm.sh/@mui/material@^5.10.17?alias=react:preact/compat,react/jsx-runtime:preact/compat/jsx-runtime&deps=preact@10.11.0"
}
}

これと同じことを Cloudscape に試してみる。

import_map.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"imports": {
"$fresh/": "https://deno.land/x/fresh@1.1.3/",
"preact": "https://esm.sh/preact@10.11.0",
"preact/": "https://esm.sh/preact@10.11.0/",
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4",
"@preact/signals": "https://esm.sh/*@preact/signals@1.0.3",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.0.1",
"twind": "https://esm.sh/twind@0.16.17",
"twind/": "https://esm.sh/twind@0.16.17/",
"@cloudscape-design/components/button": "https://esm.sh/@cloudscape-design/components@^3.0.220/button?alias=react:preact/compat,react/jsx-runtime:preact/compat/jsx-runtime&deps=preact@10.11.0",
"@cloudscape-design/components/badge": "https://esm.sh/@cloudscape-design/components@^3.0.220/badge?alias=react:preact/compat,react/jsx-runtime:preact/compat/jsx-runtime&deps=preact@10.11.0"
}
}

このように準備し、アプリケーション側で、コンポーネントを使用してみる。
Fresh 初期状態でのボタンを Cloudscape のボタンに差し替えて、バッチを仕込む。

routes/index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { Head } from "$fresh/runtime.ts";
import Counter from "../islands/Counter.tsx";
import Button from "@cloudscape-design/components/button";
import Badge from "@cloudscape-design/components/badge";

export default function Index() {
return (
<>
<Head>
<title>Fresh App</title>
<link
rel="stylesheet"
href="https://esm.sh/@cloudscape-design/global-styles/index.css"
></link>
<link
rel="stylesheet"
href="https://esm.sh/@cloudscape-design/components@^3.0.220/badge/styles.scoped.css"
></link>
<link
rel="stylesheet"
href="https://esm.sh/@cloudscape-design/components@^3.0.220/button/styles.scoped.css"
></link>
</Head>
<div class="p-4 mx-auto max-w-screen-md">
<Counter start={3} />
</div>
<div class="p-4 mx-auto max-w-screen-md">
<Button variant="primary">Click</Button>
<Badge color="red">Badge</Badge>
</div>
</>
);
}

island でもボタンとバッチを表示

islands/Counter.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useState } from "preact/hooks";

import Button from "@cloudscape-design/components/button";
import Badge from "@cloudscape-design/components/badge";

interface CounterProps {
start: number;
}

export default function Counter(props: CounterProps) {
const [count, setCount] = useState(props.start);
return (
<div class="flex gap-2 w-full">
<p class="flex-grow-1 font-bold text-xl">{count}</p>
<Button variant="primary" onClick={() => setCount(count - 1)}>
-1
</Button>
<Button variant="primary" onClick={() => setCount(count + 1)}>
+1
</Button>
<Badge color="blue">Badge</Badge>
</div>
);
}

これを起動すると、次の様に表示/動作する。

というわけで、いい具合で SSR でも CSR でもちゃんと動作する。

いくつかポイントがある。

  • コンポーネントの CSS を直接ロードさせる
    Head に https://esm.sh/@cloudscape-design/components@^3.0.220/[コンポーネント名]/styles.scoped.css を直接読ませる。
    これをやらないと、スタイルが適用されない。
  • islands で使うコンポーネントの CSS も SSR 側で読んでおく

alias=react:preact/compat~~~ is 何?

esm.sh が提供している依存関係のエイリアス機能。
依存先が react だった場合、それを preact に切り替えることができる。
切り替えたことで、preact が使われている Fresh とも協調できる。

起動時間が長くなる

denoland/import_map - Issues - Import Maps don’t support package address targets with query parameters という Issues が立っている。

クエリパラメータが有るのを仕様的には許容していない様子。

また、起動の都度 https://esm.sh/v110/@cloudscape-design/components@3.0.220/X-YS9yZWFjdC9qc3gtcnVudGltZTpwcmVhY3QvY29tcGF0L2pzeC1 ydW50aW1lLHJlYWN0OnByZWFjdC9jb21wYXQKZC9wcmVhY3RAMTAuMTEuMA/internal/base-component/styles.css が毎回ダウンロードされている。
これのせいもあるのか、普段の Fresh から比べると起動時間が非常に長くなってしまった。

ある程度、起動しっぱなしにできるなら起動時間が遅いのは許容できるが、deno deploy で動かすなら苦しいところが出てきそう。

常に起動が維持しておけるなら問題はなさそう。
一定間隔で、API を叩いてスタンバイさせておくというのは考えられるけど、Deno.inc からすると歓迎し難い可能性はある。

では。