p5.jsをVue3のコンポーネントにする

こんにちは｜こんばんは。カエルのアイコンで活動しております @kyamaz :frog: です。

本稿は、少し前に寄稿されている@shibuya01055さんのエントリ『[Vue3 Composition API + TypeScriptでp5.jsを動かす](https://qiita.com/shibuya01055/items/c2c7794140f7b517e214)』を参考[^1]に、p5.jsをVue3のコンポーネントで扱うための手順を取り上げます。
かなり類似のエントリになりますが、複数の手順を参考にしながら本稿にまとめてみました。

## Vue 3 + TypeScript + Vite の環境を整える
はじめに、プロジェクト名を`p5vue`（お好きな名前でよいです）として[Vue 3 + TypeScript + Vite + Jest](https://github.com/imomaliev/vue-ts)の環境をまず整えます。

```shell
% npm init vite@latest p5vue -- --template vue-ts

Scaffolding project in /hoge/fuga/p5vue...

Done. Now run:

  cd p5vue
  npm install
  npm run dev
```

成功しているかは気になるところですので、一旦出力された通りに`npm`を実行しておきましょう。`npm run dev`の後に次のように出力されますので、手元のブラウザで`http://localhost:5173/`にアクセスしてみましょう。
```
  VITE v5.0.XX  ready in　XXX ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
  ```
動作が確認できれば`Ctrl-C`でプロセスを終わらせて、次の手順に進みます。ついでに、プロジェクトルートで次のモジュールもインストールしておきましょう。

* `npm install vue-router`
* `npm install -D vitest`

準備されたフォルダの中身は次のようになります。
<details><summary>プロジェクトフォルダの中身</summary>

```
.
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
├── src
│   ├── App.vue
│   ├── main.ts
│   ├── style.css
│   ├── assets
│   │   └── vue.svg
│   ├── components
│   │   └── HelloWorld.vue
│   └── vite-env.d.ts
├── public
│   └── vite.svg
├── dist
│   ├── ..
│   └── ..
└── node_modules
     ├── ..
     │   └── ..
     └── ..
         └── ..
```
</details>

vue-router で動作するように整えていきます。`src`フォルダに`router.ts`を新規で作成します。

```typescript: router.ts
// router.ts
import { createRouter,createWebHistory } from 'vue-router';
import Home from "./pages/Home.vue";

const routes = [
    { path: '/', name: 'home', component: Home },
]

const router = createRouter({
    history: createWebHistory(), // HTML5 History モード
    routes,
})

export default router;
```

また、`main.ts`を次のように書き換えます。

```typescript: main.ts
// main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'; // router.tsをインポート

const app = createApp(App)
app.use(router) // Vue Routerを使用する
app.mount('#app')
```
そして、`src`フォルダの構成を変更して、必要なファイルを編集していきます。

* `src`フォルダの下に`pages`を作成し、そこに`App.vue`を`Home.vue`いうファイル名でコピーしておきます。
* `components`,`assets`フォルダを`pages`の下に移動します。（`mv src/components src/assets src/pages/`）
* `App.vue`を次のように変更します。

```vuejs:App.vue
<template>
<router-link to="/"></router-link>
<router-view/>
</template>

<script lang="ts">
export default {
}
</script>

<style scoped>
</style>
```
ここまでは、一般的な Vite＋Vue3＋VueRouter の開発テンプレートの構築手順だと思います。次から、このテンプレートにp5.jsを使えるようにしていきましょう。

## p5.jsをコンポーネントとして活用する

p5.jsのモジュールを使用できるようにインストールします。同時にTypeScriptで型チェックができるように、開発のためのモジュール`@types/p5`もインストールします。

```
% npm install p5
% npm install -D @types/p5
```
ここからは、`HelloWorld.vue`にp5.jsのコンポーネントを実装していきます。
その前に、トップページをシンプルになるように編集しておきましょう。`Home.vue`を次のように、コンポーネント`HelloWorld`を使うように変更します。

```vuejs: Home.vue
<script lang="ts">
import HelloWorld from './components/HelloWorld.vue'
export default {
  components: {
    HelloWorld
  }
}
</script>

<template>
  <div>
    <HelloWorld />
  </div>
</template>

<style scoped>
</style>
```
p5.jsをコンポーネントにするには次のように書きます。コンポーネントの`setup()`の中に、P5のスケッチを定義して、そのスケッチをもとに`p5`オブジェクトを作成するように書きます。

<details><summary>HelloWorld.vueのソースコード全体</summary>

```vuejs:HelloWorld.vue
<script lang="ts">
import { defineComponent } from "vue";
import p5 from "p5";

export default defineComponent({
  name: "HelloWorld",
  setup() {
    const canvasWidth = 400;
    const canvasHeight = 400;
    const sketch = (p: p5) => {
      let textScreen : p5.Graphics;
      p.setup = () => {
        // setup()
        p.createCanvas(canvasWidth, canvasHeight, p.WEBGL);
        textScreen = p.createGraphics(canvasWidth, canvasHeight);
        textScreen.textSize(42);
        textScreen.text('HelloWorld', 100, 100);
      };
      p.draw = () => {
        // draw()
        p.background(200);
        p.orbitControl();
        p.drawingContext.disable(p.drawingContext.DEPTH_TEST);
        p.image(textScreen, -p.width/2, -p.height/2);
        p.drawingContext.enable(p.drawingContext.BLEND);
      };
    };
    const P5 = new p5(sketch);
    return { P5 };
  },
});
</script>

<template>
</template>

<style scoped>
</style>
```
</details>

```vuejs:HelloWorld.vue（抜粋）
<script lang="ts">
import { defineComponent } from "vue";
import p5 from "p5";

export default defineComponent({
  name: "HelloWorld",
  setup() {
    const sketch = (p: p5) => {
      p.setup = () => {
        // setup()
      };
      p.draw = () => {
        // draw()
      };
    };
    const P5 = new p5(sketch);
    return { P5 };
  },
});
</script>
```
これを`npm run dev`で試してみると次のように３Dテキストを表示されます。

<img alt="HelloWorld画面" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/a95ac685-a3dc-2074-49c1-d8edb61735b4.png" width="40%" />

最後に、ビルドとプレビューも実行してみましょう。
```
% npm run build
:
% npm run preview
:
  ➜  Local:   http://localhost:4173/
:
```
ビルドでは留意事項が出ることはありますが、エラーはないことを確認してください。さらに、`npm run dev`とはポート番号が異なりますが、こちらでアクセスしても同様に動作することが確認できます。デプロイについてはまた別の機会に。


[^1]: @kasayuさんのエントリ『[Vue3 の CompositionAPI で p5.js を動かす](https://qiita.com/kasayu/items/9cde639d41cb578ebd47)』も参考にしました・