Vue Advent Calendar 2022 8日目の記事です。
はじめに
この記事では、Vue 3 に使うパッケージ群を CDN から取得することで、パッケージ管理ツール (npm, Yarn) やバンドラー/ビルドツール (webpack, Vite) を使わずに開発する方法を紹介します。
Vue を CDN から使うだけなら Vue 公式ページで紹介されているため、本記事を読む必要はあまりないので、ここではより実践的な内容として、Vuetify や VueUse といったライブラリと合わせて使う方法や、開発に役立つ Tips を盛り込んで紹介していこうと思います。
サンプルコードの注意事項
本記事で紹介するコードは説明に必要な部分だけを抽出して書いているため、そのままでは動かない可能性があります。
コードの全貌はサンプルリポジトリを用意していますので、こちらも合わせてご参照ください。
CDN とは
まず CDN について簡単に説明しましょう。
CDN とは、Content Delivery Network の略で、Web コンテンツを高速かつ安定して配信するためのシステムです。
障害が起きると Twitter でよく祭りが起きるあれです。
CDN は自身が作った Web コンテンツの配信にも使われますが、今回は提供されているライブラリを取得する用途で使います。
CDN から npm パッケージを使う
npm パッケージを配信している CDN サービスはいくつかありますが、この記事では jsDelivr を使って説明します。
CDN から npm パッケージを使う場合は、以下のように <script src="...">
で指定して呼び出します。
<script src="https://cdn.jsdelivr.net/npm/vue@3.2/dist/vue.global.js"></script>
<script>
console.log(Vue);
</script>
1行目の vue.global.js
内で Vue
がグローバルスコープで宣言されるため、2行目以降の <script>
内で使うことができます。
リリース時はこれだけでビルドする必要がなく、HTTTP 経由で確認するとブラウザのコンソールに出力されているのが確認できます。
少し前に Turbopack の実行速度が Vite の10倍ということで話題になりましたが、CDN のみで開発する場合のビルド時間は0、つまり ∞倍の開発効率が得られる わけです。
CDN から import
する
ES モジュールのように import
構文を使う場合は以下のようになります。
<script type="module">
import * as Vue from 'https://cdn.jsdelivr.net/npm/vue@3.2/dist/vue.esm-browser.js';
console.log(Vue);
</script>
import
を使うにはそのスクリプトがモジュールであることを宣言しなければならず、<script>
タグに type="module"
を指定する必要があります。
より詳細な解説は本筋とは異なるので JavaScript モジュール - JavaScript | MDN などをご参照ください。
また、ビルドツールのように、import
時にパッケージ名だけを指定したい場合は importmap を使います。
<script type="importmap">
{
"imports": {
"vue": "https://cdn.jsdelivr.net/npm/vue@3.2/dist/vue.esm-browser.js"
}
}
</script>
<script type="module">
import * as Vue from 'vue';
console.log(Vue);
</script>
これでだいぶ今風な書き方になってきましたね。
以降の説明では、ES モジュールと importmap
の組み合わせを使ったコードで解説します。
importmap
は執筆時点 (2022/12/08) では、動作しないブラウザがあります。
未対応ブラウザでも動作させるには、下の1行目を追加して es-module-shims を polyfill する必要があります。
<script async src="https://ga.jspm.io/npm:es-module-shims@1.6.2/dist/es-module-shims.js"></script>
<script type="importmap">
...
[Tips] CDN から使うファイルは何を選べばいい?
先の例で CDN から取得しているファイル名が異なっていることにお気づきでしょうか?
基本的に npm パッケージは複数の実行環境に対応できるように、モジュールごとのファイルを生成しているため、CDN から使う場合は自分の目的に合わせてファイルを選択する必要があります。
例えば jsDelivr で Vue を検索したときのページが以下です。
vue CDN Files
テーブルにいくつかファイル・ディレクトリが並んでいるのが見えます。
テーブルの1行目はデフォルトファイルで、URL の指定が {パッケージ名}(@{version})
までのときに自動的にそのファイルが返ります。
(バージョン指定は省略が可能)
多くの場合、これよりさらに下の dist/
ディレクトリにモジュールごとの JS ファイルが用意されています。
Vue の場合は以下のようになります。
(画像だと見切れるためコピペってます)
.
├── vue.cjs.js
├── vue.cjs.prod.js
├── vue.d.ts
├── vue.esm-browser.js
├── vue.esm-browser.prod.js
├── vue.esm-bundler.js
├── vue.global.js
├── vue.global.prod.js
├── vue.runtime.esm-browser.js
├── vue.runtime.esm-browser.prod.js
├── vue.runtime.esm-bundler.js
├── vue.runtime.global.js
└── vue.runtime.global.prod.js
Vue のように公式で紹介されているライブラリ以外は、この中から必要なファイルを探す必要がありますが、以下のようにファイル名からある程度は推測することができます。
- 名前に min が付く: 配信に最適なように圧縮されたファイル
- 名前に cjs, umd, esm が付く: それぞれ CommonJS, UMD, ES モジュール、どれにも該当しない場合は global スコープ扱いになることが多い
[Tips] importmap
で @
パス始まりのエイリアスを作る
importmap
を使って、create-vue で作ったプロジェクトのように @
パス始まりのエイリアスを作ることができます。
サンプルが以下です。
<script type="importmap">
{
"imports": {
"@/": "./src/"
}
}
</script>
importmap
では末尾に /
が付くキーの import
時はそれに続くパスを展開してくれるので、
import App from '@/app.js';
は
import App from './src/app.js';
と等価となります。
元は webpack の Code Splitting のように必要なファイルだけを import
する仕組みですが、相対パスへのエイリアスとして使うこともできます。
ただし、相対パスはローカルのディレクトリ構造ではなく、表示している URL に応じて変化するため、vue-router を使う場合は ハッシュモードでないと思ったように動作しない可能性があるのでご注意ください。
CND から Vue アプリを作る
それでは実際に CDN から Vue アプリを作ってみましょう。
Hello, world!
を表示
まずは手始めに Hello, world!
と表示するだけのアプリを作ってみましょう。
と言っても必要なことは CDN の章でほとんど説明してしまったので、ここではサンプルコードの紹介だけとします。
<body>
<script type="importmap">
{
"imports": {
"vue": "https://cdn.jsdelivr.net/npm/vue@3.2/dist/vue.esm-browser.prod.js"
}
}
</script>
<div id="app">
Hello, {{ name }}!
</div>
<script type="module">
import { createApp } from 'vue';
createApp({
data() {
return {
name: 'world',
};
},
}).mount('#app');
</script>
</body>
CDN から Vuetify を使う
Vuetify は、Material Design ベースで作られた Vue のコンポーネントライブラリです。
公式サイトで紹介されている CDN からの利用方法は global スコープでの説明しかないため、ES モジュールで書く場合は以下のようになります。
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@3.0/dist/vuetify.min.css" rel="stylesheet">
</head>
<body>
<script type="importmap">
{
"imports": {
"vue": "https://cdn.jsdelivr.net/npm/vue@3.2/dist/vue.esm-browser.prod.js",
"vuetify": "https://cdn.jsdelivr.net/npm/vuetify@3.0/dist/vuetify.esm.min.js"
}
}
</script>
<div id="app"></div>
<script type="module">
import { createApp, ref } from 'vue';
import { createVuetify } from 'vuetify';
import App from './app.js';
const vuetify = createVuetify();
const app = createApp(App);
app.use(vuetify);
app.mount('#app');
</script>
</body>
import { ref } from 'vue';
export default {
setup() {
const drawer = ref(null);
return {
drawer,
};
},
template: `
<v-app id="inspire">
<v-navigation-drawer v-model="drawer">
<!-- -->
</v-navigation-drawer>
<v-app-bar>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>Application</v-toolbar-title>
</v-app-bar>
<v-main>
<!-- -->
</v-main>
</v-app>
`,
};
Vuetify を使用するときは CSS ファイルもインストールする必要があるのでご注意です。
また、必要に応じて viewport の指定やアイコンフォントをインストールする必要があります。
上記のサンプルコードは Vuetify のワイヤーフレームを参考にしています。
npm に Vuetify の ES モジュールが用意されたのは3からなので、2で開発する場合は global スコープでしか利用できないので注意。
CDN から VueUse を使う
VueUse は、Vue 3 から導入された Composition API 用のユーティリティ集です。
言うなれば、Vue 専用 Lodash のような存在です。
こちらも CDN から使う場合は以下のようになります。
<body>
<script type="importmap">
{
"imports": {
"vue": "https://cdn.jsdelivr.net/npm/vue@3.2/dist/vue.esm-browser.prod.js",
"@vueuse/core": "https://cdn.jsdelivr.net/npm/@vueuse/core@9.6/index.min.mjs",
"@vueuse/shared": "https://cdn.jsdelivr.net/npm/@vueuse/shared@9.6/index.min.mjs",
"vue-demi": "https://cdn.jsdelivr.net/npm/vue-demi@0.13.11/lib/index.min.mjs"
}
}
</script>
<div id="app"></div>
<script type="module">
import { createApp, ref } from 'vue';
import App from './app.js';
createApp(App).mount('#app');
</script>
</body>
import { useMouse } from '@vueuse/core'
export default {
setup() {
const { x, y, sourceType } = useMouse()
return { x, y, sourceType };
},
template: `
<div>
<p>x: {{ x }}</p>
<p>y: {{ y }}</p>
<p>sourceType: {{ sourceType }}</p>
</div>
`,
};
VueUse は Vue 2, 3 のどちらにも対応できるように、外部ライブラリ vue-demi を使っているため、ES モジュールで import
する場合は、上のコードのように importmap
内で依存解決する必要があります。
さいごに
この記事で紹介した CDN を使う方法ですと気軽に試せる反面、ビルドステップを挟まないため、SFC (Single File Component) による分割や TypeScript による型付けができません。
そのため、アプリの規模が大きくなる場合は Vite などのビルドツールの導入をおすすめします。
CDN での開発は、サクッと動作を確認したいときなどにご活用ください。