참고 :
https://v3-docs.vuejs-korea.org/guide/quick-start.html#enabling-import-maps
https://v3-docs.vuejs-korea.org/examples/#simple-component
build방식이 아닌 간단하게 화면 구성시 활용할 수 있는 CDN방식 + build방식에서 사용하는 html template방식으로 구성하는 예시.
base.html
<!DOCTYPE html>
<html lang="ko">
<head>
<title>{% block title %}{{ site.name }}{% endblock title %}</title>
<meta charset="UTF-8">
<meta name="csrf_token" content="{{ csrf_token }}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static "style/base.css" %}" />
<link rel="stylesheet" type="text/css" href="{% block extraStyle %}{% endblock extraStyle %}" />
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" />
<script type="text/javascript" src="{% static "javascript/UT.js" %}"></script>
<script type="text/javascript" src="{% static "javascript/UI.js" %}"></script>
<script type="text/javascript" src="{% static "javascript/SS.js" %}"></script>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js"
}
}
</script>
<script src="{% static "javascript/VueUtil.js" %}" type="module"></script>
<script src="{% block extraScript %}{% endblock extraScript %}" type="text/javascript"></script>
</head>
<body>
<div id="header">
{% include "header.html" %}
</div>
<div id="content">
{% block content %}{% endblock content %}
</div>
<div id="footer">
{% include "footer.html" %}
</div>
</body>
</html>
home.html
{% extends "base.html" %}
{% block title %}{{ site.name }}{% endblock title %}
{% load static %}
{% block extraStyle %}{% endblock extraStyle %}
{% block extraScript %}{% endblock extraScript %}
{% block content %}
<div id="app">
<button @click="count++">
숫자 세기: {% verbatim %}{{ count }}{% endverbatim %}
</button>
<ol>
<todo-item
v-for="item in groceryList"
:todo="item"
:id="item.id"
></todo-item>
</ol>
</div>
<script type="module">
import { createApp, ref } from "vue"
const app = createApp({
setup() {
const groceryList = ref([
{ id: 0, text: "Vegetables" },
{ id: 1, text: "Cheese" },
{ id: 2, text: "Whatever else humans are supposed to eat" }
])
const count = ref(0)
return {
count,
groceryList
}
},
mounted() {
console.log(this.groceryList)
}
})
.component("todo-item", VueUtil.loadModule("/vue/TodoItem.vue"))
.mount("#app")
app.config.errorHandler = (err, instance, info) => {
// 에러 핸들링: 서비스 에러 로그 기록
}
</script>
{% endblock content %}
TodoItem.vue
<template>
<li @click="test(todo.text)">{{ todo.id }} : {{ todo.text }}</li>
</template>
<script>
export default {
props: {
todo: Object
},
setup(props, context) {
console.log(props.todo)
// 속성 (비-반응형 객체, $attrs에 해당함)
console.log(context.attrs)
// 슬롯 (비-반응형 객체, $slots에 해당함)
console.log(context.slots)
// 이벤트 발송 (함수, $emit에 해당함)
console.log(context.emit)
// 로컬 속성 노출 (함수)
console.log(context.expose)
},
methods: {
test(text) {
console.log(text);
}
},
mounted() {
console.log("parkingLot_list.vue")
}
}
</script>
<style scoped>
li {
font-weight: bold;
color: red;
}
</style>
VueUtil.js
import * as Vue from "vue"
import { loadModule } from "https://unpkg.com/vue3-sfc-loader/dist/vue3-sfc-loader.esm.js"
const VueUtil = new (function()
{
function _vueUtil()
{
};
_vueUtil.prototype.loadModule = function(url)
{
const options =
{
moduleCache : {
vue: Vue
},
async getFile(url) {
const res = await fetch(url);
if (!res.ok)
throw Object.assign(new Error(res.statusText + " " + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
addStyle(textContent) {
const style = Object.assign(document.createElement("style"), { textContent });
const ref = document.head.getElementsByTagName("style")[0] || null;
document.head.insertBefore(style, ref);
},
}
return Vue.defineAsyncComponent(() => loadModule(url, options));
};
return _vueUtil;
}());
window.VueUtil = VueUtil;
출력결과