Webpack を使わずに PHP で Vue の SFC (単一ファイルコンポーネント) みたいなことを実現する

webpack を使わずに SFC が使いたい！ webpack を導入するのは怖いし影響範囲が分からない！

こういう場合に、PHP の `include` と JavaScript の `innerHTML` を使って SFC のように書き換えてみます。ポイントとしては以下の2箇所になります。

```php
<?php include "components/my-component.php" ?>
```

`import ... from "...";` の代わりに PHP の `include` や blade の `@include` などを使い、コンポーネントが記述された別の PHP ファイルを読み込みます。

```javascript:components/my-component.php
const MyComponent = {
    template: document.querySelector('#my-component-template').innerHTML,
    ...
}
```

また、 `document.querySelector(...).innerHTML` を使って生の HTML からテンプレートを取得することで、テンプレートにシンタックスハイライトが有効になるようにしています。

# 具体例

例えば以下のような PHP ファイルがあったとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78152/2ce19aad-99c7-415a-5fee-99252b24b1c2.png)

```php:index.php
<html>

<head>
    <title>PHP + Vue Demo</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>

<body>
    <div id="app">
        <h1>PHP + Vue Demo</h1>
        <my-component my-fruit="Apple"></my-component>
        <my-component my-fruit="Orange"></my-component>
        <my-component my-fruit="Strawberry"></my-component>
    </div>

    <style>
        h1 {
            color: #FF9900;
        }

        .fancy-border {
            border: red 3px solid;
        }
    </style>

    <script>
        const MyComponent = {
            props: ['myFruit'],
            data() {
                return {
                    myText: '300 yen',
                }
            },
            template: `
                <div>
                    <input type='text' v-model="myText" />
                    <p class='fancy-border'>
                        The price of {{myFruit}} is {{myText}}.
                    </p>
                </div>
            `
        };

        const {
            createApp
        } = Vue

        createApp({
            components: {
                'my-component': MyComponent,
            }
        }).mount('#app')
    </script>
</body>

</html>
```

# 問題点

まず問題点として、1つのファイルにコンポーネントが複数あり、1ファイルが長くなってしまうというものが挙げられます。これは PHP の `include` や blade の `@include` などを使って対処できます。
また、template が文字列となっていてシンタックスハイライトが使えないという問題に関しては、生の HTML にテンプレートを書いておき、 `document.querySelector(...).innerHTML` で取得することで解決します。SFC と同じように [template タグ](https://developer.mozilla.org/ja/docs/Web/HTML/Element/template "<template>: コンテンツテンプレート要素 - HTML: HyperText Markup Language | MDN") を使用できますが、`<div style='display:none'>` のように書いても問題ありません。

（なお、Vue では `template: ...` の代わりに `el: '#app'` のように書くことも出来ますが、この場合はコンポーネントを複数個マウントすることができないので今回は使用できません。）

上記を踏まえてコードを書き換えると以下のようになります。

```php:components/my-component.php
<template id="my-component-template">
    <div>
        <input type='text' v-model="myText" />
        <p class='fancy-border'>
            The price of {{myFruit}} is {{myText}}.
        </p>
    </div>
</template>

<style>
    .fancy-border {
        border: red 3px solid;
    }
</style>

<script>
    const MyComponent = {
        props: ['myFruit'],
        template: document.querySelector('#my-component-template').innerHTML,
        data() {
            return {
                myText: '300 yen',
            }
        },
    };
</script>
```

```php:index.php
<html>

<head>
    <title>PHP + Vue Demo</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>

<body>
    <?php include "components/my-component.php" ?>

    <div id="app">
        <h1>PHP + Vue Demo</h1>
        <my-component my-fruit="Apple"></my-component>
        <my-component my-fruit="Orange"></my-component>
        <my-component my-fruit="Strawberry"></my-component>
    </div>

    <style>
        h1 {
            color: #FF9900;
        }
    </style>

    <script>
        const {
            createApp
        } = Vue

        createApp({
            components: {
                'my-component': MyComponent,
            }
        }).mount('#app')
    </script>
</body>

</html>
```

# おまけ：CodeSandbox のデモ

今回使ったファイルです。

[CodeSandbox デモ](https://codesandbox.io/p/sandbox/boring-farrell-54hjgj?file=%2Findex.php%3A1%2C7)

# おわりに

この方法の欠点を挙げると多すぎて書ききれなくなってしまうのでここには書きません。なるべく可能なら `.vue` ファイルを使える環境を構築した方がいいと思います。