CDN版Vue3で単一ファイルコンポーネント(SFC)の利用するためのモジュール

<vue-sfc-loader.mjs>

////////////////////////////////////////////////////////////////////////////////
// vue3 モジュール
////////////////////////////////////////////////////////////////////////////////
import * as Vue from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'

////////////////////////////////////////////////////////////////////////////////
// vue3-sfc-loader モジュール
////////////////////////////////////////////////////////////////////////////////
import { loadModule } from "https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.8.4/dist/vue3-sfc-loader.esm.js";

////////////////////////////////////////////////////////////////////////////////
// vue3-sfc-loader オプション
// SFCファイルから外部のモジュールをimportできるオプション
// 参考:https://github.com/FranckFreiburger/vue3-sfc-loader/issues/14#issuecomment-908849863
////////////////////////////////////////////////////////////////////////////////
const vue3_sfc_loader_options = {
    moduleCache: { vue: Vue },
    getFile(url) {
        url = /.*?\.js|.mjs|.css|.less|.vue$/.test(url)
            ? url
            : `${url}.vue`;
        const type = /.*?\.js|.mjs$/.test(url)
            ? ".mjs"
            : /.*?\.vue$/.test(url)
            ? ".vue"
            : /.*?\.css$/.test(url)
            ? ".css"
            : ".vue";
        const getContentData = (asBinary) =>
            fetch(url).then((res) =>
                !res.ok
                    ? Promise.reject(url)
                    : asBinary
                    ? res.arrayBuffer()
                    : res.text()
            );
        return { getContentData: getContentData, type: type };
    },
    addStyle(textContent) {
        let styleElement = document.createElement("style");
        document.head.insertBefore(
            Object.assign(styleElement, { textContent }),
            document.head.getElementsByTagName("style")[0] || null
        );
    },
    handleModule(type, getContentData, path, options) {
        switch (type) {
            case ".css":
                return options.addStyle(getContentData(false));
            case ".less":
                console.error(".......");
        }
    },
    log(type, ...args) {
        console.log(type, ...args);
    },
};

function getFileName(path) {
    return path.split('/').pop().split('.').shift();
}

//Appコンポーネントをマウントするラッパー関数
export default function MountSFC(vueFilePath, querySelector="body", model={}){
    const app = Vue.createApp({});
    if(Array.isArray(vueFilePath)){
        vueFilePath.forEach( e => {
            app.component(getFileName(e), Vue.defineAsyncComponent(() => loadModule(e, vue3_sfc_loader_options)));
        });
    }else{
        app.component(getFileName(vueFilePath), Vue.defineAsyncComponent(() => loadModule(vueFilePath, vue3_sfc_loader_options)));
    }
    app.mixin({
        methods: {
            // 各コンポーネント内の<script>タグ内でグローバル変数を参照するための関数
            _model(){
              return model;
            },
        },
        computed: {
          // 各コンポーネント内の<template>タグ内でグローバル変数を参照するための処置
          Model: {
            get: function () { return model },
          }
        }
    })
    app.mount(querySelector);
}

使用例
<index.html>

<!DOCTYPE html>
<html lang="jp">
	<head>
		<meta charset="UTF-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>test</title>
        <script type="module">
            import MountSFC from "./vue-sfc-loader.mjs"
            MountSFC(["./compo_a_parent.vue","./compo_b.vue"]);
        </script>
	</head>
	<body>
        <div>
            <compo_a_parent />
        </div>
        <div>
            <compo_b />
        </div>
	</body>	
</html>

<compo_a_parent.vue>

<script setup>
import test from './compo_a_child.vue'
</script>

<template>
  <div>コンポーネントA</div>
  <input type='text' v-model='message'>
  <test :value=message />
</template>

<script>
export default {
  data(){
    return {
        message: 'Hello VueJS!'
    }
  },
};
</script>

<compo_a_child.vue>

<template>
  <div>コンポーネントAから呼び出される子コンポーネント</div>
  <div>{{ value }}</div>
  <div>
    <ul>
      <li v-for="(listItem, index) in listItems" :key="index">
        {{ listItem }}
      </li>
    </ul>
  </div>
  
</template>

<script>
export default {
  props: {
    value: String
  },
  data() {
    return {
      listItems: [
        "親コンポ―ネントから受け取った値を",
        "表示する",
        "子コンポーネント",
      ],
    }
  },
};
</script>

<compo_b.vue>

<template>
  <div>コンポーネントB</div>
</template>

<script>
</script>

結果


この記事が気に入ったらサポートをしてみませんか?