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

Deno で npmで提供されている UI コンポーネントを使いたかったが、これまでいろいろと試してきた。
限定された状況で、唯一ある程度できる方法がわかったのでまとめておきたい。

参考

動作できないパターン

一旦動作ができないパターンについて記しておきたい。

Packupでの環境の準備です。

以下のように構築します。

1
2
3
4
5
$ tree
.
|-- index.html
`-- src
`-- script.tsx
index.html
1
2
3
4
5
6
7
8
9
10
<html>
<head>
<title>packup-React-cloud-scape</title>
<link rel="stylesheet" href="https://esm.sh/@cloudscape-design/global-styles/index.css" />
<script src="src/script.tsx"></script>
</head>
<body>
<div id="main"></div>
</body>
</html>
src/script.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "https://esm.sh/react@17.0.2";
import ReactDOM from "https://esm.sh/react-dom@17.0.2";
import { Button } from "https://esm.sh/@cloudscape-design/components";

function App() {
return (
<div>
<Button variant="primary" onClick={() => console.log(123)}>button</Button>
</div>
);
}

function main() {
ReactDOM.render(React.createElement(App), document.querySelector("#main"));
}

addEventListener("DOMContentLoaded", () => {
main();
});

実行してみると次のようにエラーになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ packup -p 8080 index.html
Server running at http://localhost:8080
Using "static" as static directory
Download https://esm.sh/v91/@cloudscape-design/components@3.0.25/internal/base-component/styles.css
Download https://esm.sh/v91/@cloudscape-design/components@3.0.25/internal/base-component/styles.css
> deno:file:///usr/src/app/packup-cloud-scape/src/script.tsx:3:23: error: [plugin: deno] Unreachable.
3 │ import { Button } from "https://esm.sh/@cloudscape-design/components";
╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Error: Build failed with 1 error:
deno:file:///usr/src/app/packup-cloud-scape/src/script.tsx:3:23: error: [plugin: deno] Unreachable.
at failureErrorWithLog (https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:1428:15)
at https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:1110:28
at runOnEndCallbacks (https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:900:63)
at buildResponseToResult (https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:1108:7)
at https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:1215:14
at https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:588:9
at handleIncomingPacket (https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:685:9)
at readFromStdout (https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:555:7)
at Object.worker.onmessage (https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:2276:36)
at https://deno.land/x/esbuild_loader@v0.12.8/vendor/browser.js:2267:41

ということで esm.sh で変換したものが動かない。
このエラーで、実は2カ月ほどいろいろ試していた。

対策する

ということで対策する。
この対策方法、きっかけは同僚との雑談なので感謝しかない。

環境構築

Node.jsの環境とDenoの環境を別のコンテナで用意から始めます。

最終的なディレクトリ構成は以下の通りです。

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
.
|-- DockerfileDeno
|-- DockerfileNode
|-- docker-compose.yml
|-- packup-cloud-scape
| |-- builded-cloud-scape
| | |-- dist
| | | |-- builded-cloud-scape.es.js
| | | |-- builded-cloud-scape.umd.js
| | | |-- style.css
| | | `-- vite.svg
| | |-- index.html
| | |-- lib
| | | `-- main.ts
| | |-- node_modules
| | |-- package-lock.json
| | |-- package.json
| | |-- public
| | | `-- vite.svg
| | |-- src
| | | |-- counter.ts
| | | |-- main.ts
| | | |-- style.css
| | | |-- typescript.svg
| | | `-- vite-env.d.ts
| | |-- tsconfig.json
| | `-- vite.config.js
| |-- index.html
| |-- src
| | `-- script.tsx
| `-- static
`-- tsconfig.json
DockerfileDeno
1
2
3
4
5
6
7
FROM denoland/deno:1.24.0

RUN apt-get update && apt-get install -y wget
RUN mkdir /usr/src/app
WORKDIR /usr/src/app

EXPOSE 8080
DockerfileNode
1
2
3
4
5
6
7
8
FROM node:18

RUN apt-get update && apt-get install -y wget
RUN mkdir /usr/src/app
WORKDIR /usr/src/app


EXPOSE 8080
docker-compose.yml
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
version: "3"
services:
app:
build:
context: .
dockerfile: DockerfileDeno
privileged: true
command: tail -f /dev/null
ports:
- "8080:8080"
- "35729:35729"
volumes:
- .:/usr/src/app:cached
tty: true
node:
build:
context: .
dockerfile: DockerfileNode
privileged: true
command: tail -f /dev/null
ports:
- "8081:8080"
volumes:
- .:/usr/src/app:cached
tty: true

以上の設定で Node と Deno で同じディレクトリをマウントさせる。

@cloudscape-design をビルドする

以下の手順で vite の環境を構築する。(この作業はNodeのコンテナで実施)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ pwd
/usr/src/app/packup-cloud-scape

$ mkdir builded-cloud-scape

$ cd builded-cloud-scape

$ npm create vite@latest
✔ Project name: › .
✔ Select a framework: › vanilla
✔ Select a variant: › vanilla-ts

$ npm install
$ @cloudscape-design/components
$ mkdir lib
$

ここまで出来たら、 vite.config.js と main.ts を作成。

vite.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { resolve } from "path";
import { defineConfig } from "vite";

export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, "lib/main.ts"),
name: "builded-cloud-scape",
fileName: (format) => `builded-cloud-scape.${format}.js`,
},
rollupOptions: {
external: ["react"],
output: {
globals: {
react: "React",
},
},
},
},
});
main.ts
1
2
import "@cloudscape-design/global-styles/index.css";
export { Button } from "@cloudscape-design/components";

用意できたら、以下のコマンドでビルドを実行。

1
2
3
4
5
6
7
8
9
$ npm run build
> builded-cloud-scape@0.0.0 build
> tsc && vite build

vite v3.0.8 building for production...
✓ 1359 modules transformed.
dist/style.css 304.13 KiB / gzip: 150.98 KiB
dist/builded-cloud-scape.es.js 610.90 KiB / gzip: 145.15 KiB
dist/builded-cloud-scape.umd.js 409.03 KiB / gzip: 126.98 KiB

これで Cloudscape をビルドできた。

改めて packup で取り込み

Cloudscape をesm.shからの読み込みからビルド結果へ変更する。(ここからはDenoのコンテナ)

src/script.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "https://esm.sh/react@17.0.2";
import ReactDOM from "https://esm.sh/react-dom@17.0.2";
import { Button } from "../builded-cloud-scape/dist/builded-cloud-scape.es.js"; // ビルド結果

function App() {
return (
<div>
<Button variant="primary" onClick={() => console.log(123)}>button</Button>
</div>
);
}

function main() {
ReactDOM.render(React.createElement(App), document.querySelector("#main"));
}

addEventListener("DOMContentLoaded", () => {
main();
});
index.html
1
2
3
4
5
6
7
8
9
10
<html>
<head>
<title>packup-React-cloud-scape</title>
<link rel="stylesheet" href="./builded-cloud-scape/dist/style.css" />
<script src="src/script.tsx"></script>
</head>
<body>
<div id="main"></div>
</body>
</html>

そしてビルド結果の dist/builded-cloud-scape.es.js を少し変換。

from "react"from "https://esm.sh/react@17.0.2" に書き換える。

1
$ cat ./builded-cloud-scape/dist/builded-cloud-scape.es.js|  sed -e 's/from "react"/from "https:\/\/esm.sh\/react@17.0.2"/g' 1<> ./builded-cloud-scape/dist/builded-cloud-scape.es.js

書き換えたら、再度packupを実行。

1
2
3
4
$  packup -p 8080 index.html
Server running at http://localhost:8080
Using "static" as static directory
index.html bundled in 1344ms

以上で、アクセスしてみると、コンポーネントが表示されている。onClick に定義した動作も行われる。

ということで、動作できた方法は次のようになる。

vite でコンポーネントの使いたい要素を ライブラリモードでビルドする。
そしてそれを使う。

である。

補足

React が使われている MUI も試したが使えなかった。
react-bootstrap semantic-ui-react react-bulma-components は使えた。(いくつか抜粋でコンポーネントを動かしてみただけ、全体の保証はできない)

なんとなく、@emotion のインストールが必要なものは、この方法ではコケる気がする。

また、この方法で作った @cloudscape-design/components のビルド結果は、Packup では使えたが、Ultra などの他のフレームワークでは上手く動作しなかった。
SSRされるものは怪しい動作になっているように見える。

Packup はあくまでクライアント向けのjsのビルドなので問題はない様子。

今後Denoは Node互換性 npmの互換性 の向上を見込めるので、こういったものも動くようになればいいなぁ。


ということで、いろいろ試して見たが、冒頭の記述の通りかなり限定的な状況でだけ、使用できそうな方法だった。

上手く行かないので deno-ja のコミュニティに、「Deno でフロントを触っている皆さんは、どういう風にスタイリングしてますか」と質問を投げた。
Deno本体だと twind unocss を使っていることらしい。

twind unocss に馴染みは無かったが便利な書き方も共有いただいたので、この記事を一旦のまとめとして書きながら、そちらも調べていた。

そのうちこちらも書いておきたい。

では。