packup(Deno) で Vue.js を動かす

前回、petite-vue を packup(Deno)で動かしてみました。
となると Vue.js 本体を書けないものだろうか?と考えてみるところです。
やってみたのでメモです。

結論 「Vue.js は」動かせる

結論としては、Vue.js は動く。
ただし、確認した範囲では、vue-router が動かない。
将来的には解決できるものと考えられる。

実装

とりあえず packup で Vue.js を動かせたソースは以下の通り。

index.html
1
2
3
4
5
6
7
8
9
10
11
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="./style.scss" />
</head>

<body>
<script type="module" src="./main.ts"></script>
<div id="app"></div>
</body>
</html>
main.ts
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
import {
createApp,
defineComponent,
reactive,
} from "https://esm.sh/vue@3.2.31/dist/vue.esm-bundler.js";
import Item from "./item.ts";

const HelloVueApp = defineComponent({
components: { Item },
setup() {
const message = "Hello Vue!!";
const data = reactive({ list: [1, 2, 3, 4, 5] });

setInterval(() => {
data.list.push(1);
}, 1000);

return { message, list: data.list };
},
template: `
<span>
Hello
</span>
<ul>
<li v-for="d in list">{{d}}c</li>
</ul>
{{message}}
<Item :text="message"/>
`,
});

createApp(HelloVueApp).mount("#app");
item.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { defineComponent } from "ttps://esm.sh/vue@3.2.31/dist/vue.esm-bundler.js";

export default defineComponent({
props: {
text: {
type: String,
},
},
setup(props) {
const list = [1, 2, 3, 4, 5];
return { list };
},
template: `
<span>{{text}}</span>
<ul>
<li v-for="d in list">{{d}}c</li>
</ul>`,
});

ここまで作って、以下のコマンドで起動する。

1
$ packup index.html

これでアクセスしてみるとリストが増えていくだけの画面が表示できる。
これで一旦 Vue3 が動くことを確認できる。

.vue ファイルは、使えないだろうことは予測していたので、template オプションを使ってテンプレートを指定した。
ただし、文字列で全部定義していくとハイライトもなく辛い。
できれば vite の text ファイルのインポートみたいな形で、外出しできるといいなぁと感じる。

Vue.js のインポートはhttps://esm.sh/vue@3.2.31/dist/vue.esm-bundler.jsから行った。
https://esm.sh/vue@3.2.31https://esm.sh/vue では動かないのでこの点はかなり注意事項。

続けて vue-router を導入してみる=>できなかった

vue-router のドキュメントに従い導入してみます。

main.ts
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
33
34
35
36
37
import {
createApp,
defineComponent,
reactive,
} from "https://esm.sh/vue@3.2.31/dist/vue.esm-bundler.js";
import Item from "./item.ts";
import { router, RouterLink, RouterView } from "./router.ts";

const HelloVueApp = defineComponent({
components: { Item, RouterLink, RouterView },
setup() {
const message = "Hello Vue!!";
const data = reactive({ list: [1, 2, 3, 4, 5] });

setInterval(() => {
data.list.push(1);
}, 1000);

return { message, list: data.list };
},
template: `
<div>
<router-link to="/">HOME</router-link>
<router-link to="/page1">PAGE1</router-link>
<span>
Hello
</span>
<ul>
<li v-for="d in list">{{d}}c</li>
</ul>
{{message}}
<router-view></router-view>
</div>
`,
});

createApp(HelloVueApp).use(router).mount("#app");
router.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {
createRouter,
createWebHistory,
RouterLink,
RouterView,
} from "https://esm.sh/vue-router@4.0.12/dist/vue-router.esm-bundler.js";
import PageA from "./a.ts";
import PageB from "./b.ts";
import PageC from "./c.ts";

const routes = [
{ path: "/", name: "home", component: PageA },
{ path: "/page1", name: "page1", component: PageB },
{ path: "/page2", name: "page2", component: PageC },
];

const router = createRouter({
history: createWebHistory(),
routes,
});

export { router, RouterLink, RouterView };

main.ts に RouterLink と RouterView が無いので、router.ts 経由で渡しています。

これを動かしてみるとエラー、難読化されていて理由の判断がつかないのでギブアップです。
どうやらいくつかプロパティが足りない様子。

暫定対策

Vue.js のドキュメントに、vue-router を使わないシンプルなルーティングについて記載があります。

Vue.js routing

コチラを参考に自作してみます。

main.ts
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import {
computed,
createApp,
defineComponent,
onMounted,
ref,
} from "https://cdn.jsdelivr.net/npm/vue@3.2.31/dist/vue.esm-browser.js";

import Item from "./item.ts";

import a from "./a.ts";
import b from "./b.ts";
import c from "./c.ts";

const routes = {
"/": a,
"/page1": b,
"/page2": c,
};

const HelloVueApp = defineComponent({
components: { Item },
setup() {
const message = "Hello Vue!!";
const list = [1, 2, 3, 4, 5];

let currentPath = ref(window.location.hash);

const currentView = computed(() => {
return routes[currentPath.value.slice(1) || "/"];
});

onMounted(() => {
window.addEventListener("hashchange", () => {
currentPath.value = window.location.hash;
});
});

return { message, list, currentPath, currentView };
},
template: `
<div>
<a href="#/">HOME</a> |
<a href="#/page1">PAGE1</a> |
<a href="#/page2">PAGE2</a> |
</div>
<span>
Hello
</span>
<ul>
<li v-for="d in list">{{d}}c</li>
</ul>
{{message}}
<Item :text="message"/>
<div>
{{currentPath}}
<component :is="currentView" />
</div>
`,
});

createApp(HelloVueApp).mount("#app");

これを動かすと<component :is="currentView" /> の部分が URL の#以降の URL に対応して切り替え出来ます。

拡張してみる

#付き URL で動かす前提になった時、routes は次のように書きたくなりました。

1
2
3
4
5
6
7
const routes = {
"/": a,
"/page1": {
"/page1-b": b,
"/page1-c": c,
},
};

実際の処理としては、次の形にオブジェクトが変換できていると既存の処理に合致します。

1
2
3
4
5
const routes = {
"/": a,
"/page1/page1-b": b,
"/page1/page1-c": c,
};

この変換を実装してみました。

path_object_convert.ts
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
interface PathStringKeyObject {
[key: string]: string;
}

const isPathStringKeyObject = (src: Object): src is PathStringKeyObject => {
return Object.keys(src).every((key) => key[0] === "/");
};

const keys = (src: Object, parentKey: string = "") => {
return Object.keys(src)
.map((key) => {
if (isPathStringKeyObject(src[key])) {
return keys(src[key], key)
.map((childKey) => `${parentKey}${childKey}`)
.flat();
}
return `${parentKey}${key}`;
})
.flat();
};

const dig = (src: Object, keys: string[]) => {
let tmp = src;
for (let i = 0; i < keys.length; i++) {
tmp = tmp[keys[i]];
}
return tmp;
};

const convert = (src: Object) => {
let keyStrings = keys(src);

let result = {};

keyStrings.forEach((key) => {
const paths = key
.split("/")
.map((path) => `/${path}`)
.slice(1);
result[key] = dig(src, paths);
});

return result;
};

export { convert };
main.ts
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import {
computed,
createApp,
defineComponent,
onMounted,
ref,
} from "https://cdn.jsdelivr.net/npm/vue@3.2.31/dist/vue.esm-browser.js";

import Item from "./item.ts";

import a from "./a.ts";
import b from "./b.ts";
import c from "./c.ts";

import { convert } from "./path_object_convert.ts";

const routes = convert({
// <= convert を追加
"/": a,
"/page1": {
"/page1-b": b,
"/page1-c": c,
},
});

// 変換されたオブジェクトは、次の形になっている
// routes = {
// "/": a,
// "/page1/page1-b": b,
// "/page1/page1-c": c,
// };

const HelloVueApp = defineComponent({
components: { Item },
setup() {
const message = "Hello Vue!!";
const list = [1, 2, 3, 4, 5];

let currentPath = ref(window.location.hash);

const currentView = computed(() => {
return routes[currentPath.value.slice(1) || "/"];
});

onMounted(() => {
window.addEventListener("hashchange", () => {
currentPath.value = window.location.hash;
});
});

return { message, list, currentPath, currentView };
},
template: `
<div>
<a href="#/">HOME</a> |
<a href="#/page1/page1-b">PAGE1</a> |
<a href="#/page1/page1-c">PAGE2</a> |
</div>
<span>
Hello
</span>
<ul>
<li v-for="d in list">{{d}}c</li>
</ul>
{{message}}
<Item :text="message"/>
<div>
{{currentPath}}
<component :is="currentView" />
</div>

`,
});

createApp(HelloVueApp).mount("#app");

これで、#以降の URL の構造をオブジェクトの構造で表現できます。
path_object_convert.ts 自体の実装はあまり賢いやり方ではない可能性は感じます。

hashchange というイベントは知らなかったので勉強になりました。


Vue.js を packup(Deno)で使ってみました。

ポイントは、インポート先を https://esm.sh/vue@3.2.31/dist/vue.esm-bundler.js にする。
https://esm.sh/vue@3.2.31 にしてしまうと全然ダメ。動かない。

vue-router は、調べてみたが動作を確認できないというところで、継続して調査。

packup で Vue.js の SPA であれば、十分動かせそうな雰囲気がありました。

ではでは。