ViteでVueを使いたいけどIEも対応したい


最近Webpackのビルドが遅すぎてイライラするので、早いと噂のViteに移行したいという話です。

ただ、ネックなのはタイトルにある通りIEに対応しなくてはいけないプロジェクトだということです。

Viteを使うときに最初にぶつかる壁は、公式で対応しているVueプラグインのバージョンが3からだという点です。

Vue3は「IE対応を打ち切る」ような記事がいくつかありましたが、正確にはTwitterの@vuejsdevelopersアカウントから「対応する価値あるのか?」みたいな意見が出ているってことみたいです。(2021/07/04 記述)
※間違っていたらごめんなさい

とりあえずとるべき道は
・Vue3をなんとか頑張ってIEで動かす
・Vue2をなんとか頑張ってViteで動かす
のどちらかになりそうです。

まずはプロジェクト作成

yarn create @vitejs/app
Project name: vite-project2
? Select a framework: › - Use arrow-keys. Return to submit.
    vanilla
❯   vue
    react
    preact
    lit-element
    svelte
? Select a variant: › - Use arrow-keys. Return to submit.
    vue
❯   vue-ts

ここで一つ注意なのは、Viteでビルドされるコードは「JavaScript モジュール」の利用を前提としていることです。

MDNの説明を見ればわかりますが、ブラウザ対応状況をみるとIEだけ悲しい感じになってます。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Modules
なのでVueのバージョン以前にViteのビルドをIEに対応させる必要がありますが、嬉しいことに公式でプラグインが提供されています。これをまずは設定してみます。
https://github.com/vitejs/vite/tree/main/packages/plugin-legacy
まずはパッケージを入れて

yarn add -D @vitejs/plugin-legacy

vite.config.tsに設定を追記します。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'; // 追加
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    // ここから
    legacy({
      targets: ['ie >= 11'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime']  
    }),
    // ここまで追加
  ],
})

これでIEでもViteでビルドしたコードが動くはずです...Vue3を使わなければ。
ということで、一旦Vueを初期化するコードはコメントで外します。

main.ts

// import { createApp } from 'vue'
// import App from './App.vue'
// createApp(App).mount('#app')

レガシー対応のプラグインはデフォルトで必要に応じてポリフィルを入れてくれるみたいなので
代わりにES6+のコードを使ってログでも出してみます。
※ DOM API のポリフィルは入れてくれないので必要に応じて入れる必要があります。詳しくはプラグインのドキュメントを読んでください。

main.ts

// import { createApp } from 'vue'
// import App from './App.vue'
// createApp(App).mount('#app')
const sayHello = (name: string) => console.log(`hello ${ name }`);
sayHello('Bob');

一旦表示できるか見てみます。devコマンドだとレガシー対応が反映されずにサーバーが動くみたいだったので
ビルドした結果をserveコマンドで見てみます。

yarn build
yarn serve --host

IEでログを見てみると「hello Bob」と表示されたのでここまでは問題なさそうです。
さて、ここからさらにVueの対応をして行かなくてはいけないのですが、
いったんコメントアウトしたコードを元に戻してみます。

main.ts

import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

コードを再ビルドしてserveしてみると、まぁ...エラーがでますよね。

Unhandled promise rejection TypeError: strict モードでは、関数または arguments オブジェクトの 'caller' プロパティを使用できません

そう言われましても、って感じのエラーでよくわかりません。
まだ「not defined」みたいなエラーならポリフィル入れるだけで済みそうなものなのに。

・Vue3をなんとか頑張ってIEで動かす

これを目指してしばらく調べると次の記事がヒットしました。ES6のProxyに対応すれば動くかも...って内容ですが、ポリフィル入れても変わらないエラー。
私は心が折れました。
https://stackoverflow.com/questions/64836337/using-vue-3-in-ie11

MDNにエラーの詳細は書いてありますが、Vueのパッケージを検索してもcallerにアクセスしてるコードなんか見つからないしよくわかりません。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode

だれかわかる人いたら教えて欲しいです。
が、取り急ぎ

・Vue2をなんとか頑張ってViteで動かす

これで行こうと思います。
ViteはrollupとESBuildの組み合わせってだけで、問題は提供されているVueのプラグインが@3からしか対応していない事なんですが、調べるとこんなパッケージを見つけました。これは希望が持てる!
https://www.npmjs.com/package/vite-plugin-vue2
とりあえずパッケージを導入し、公式のプラグインと入れ替えます。
あとVueのバージョンも@^2に下げておきます。

yarn add -D vite-plugin-vue2
yarn add [email protected]^2

vite.config.ts

import { defineConfig } from 'vite'
// vite.config.js
import legacy from '@vitejs/plugin-legacy'
import { createVuePlugin } from 'vite-plugin-vue2';
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    createVuePlugin(/*options*/), // これでVue2が動くかもしれない!
    // vue(), 公式プラグインは無効にしておく
    legacy({
      targets: ['ie >= 11'],
      additionalLegacyPolyfills: [
        'regenerator-runtime/runtime',
      ]  
    }),
  ]
})

これで、ビルド...する前に、Vue2とVue3で初期化など書き方が異なる箇所があるので修正します。

main.ts

// import { createApp } from 'vue'
import App from './App.vue'
// createApp(App).mount('#app')
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';
Vue.use(VueCompositionAPI);
new Vue({
  render: (h) => {
    return h(App)
  },
}).$mount('#app');

App.vue

<template>
  <div> <!-- divで括る -->
    <img alt="Vue logo" src="./assets/logo.png" />
    <HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
  </div>
</template>
<script lang="ts">
// composition-apiに置き換える
// import { defineComponent } from 'vue'
import { defineComponent } from '@vue/composition-api'
import HelloWorld from './components/HelloWorld.vue'
export default defineComponent({
  name: 'App',
  components: {
    HelloWorld
  }
})
</script>
<style>
#app {
  ... 省略 ...

HelloWorld.vue

<template>
  <div> <!-- divで括る -->
    <h1>{{ msg }}</h1>
    ... 省略 ...
    <p>
      Edit
      <code>components/HelloWorld.vue</code> to test hot module replacement.
    </p>
  </div>
</template>
<script lang="ts">
// composition-apiに置き換える
// import { ref, defineComponent } from 'vue'
import { ref, defineComponent } from '@vue/composition-api'
export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: {
      type: String,
  ... 省略 ...

vue-shims.d.ts

// declare module '*.vue' {
//   import { DefineComponent } from 'vue'
//   const component: DefineComponent<{}, {}, any>
//   export default component
// }
declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

これでビルドできるはず!と思いましたが以下のエラー

failed to load config from /Users/snishiwaki/Documents/projects/vite-project/vite.config.ts
error during build:
Error: Cannot find module 'vue-template-compiler'
... 省略 ...

なんか、もう、わからんけど無いって言われてるやつ入れます。

yarn add -D vue-template-compiler

今度こそビルド...できた!!!
これは熱い!

yarn run v1.22.10
warning package.json: No license field
$ vue-tsc --noEmit && vite build
vite v2.3.8 building for production...
✓ 15 modules transformed.
dist/assets/index-legacy.282affa1.js       3.22kb / brotli: 1.21kb
dist/assets/vendor-legacy.b66c5159.js      71.43kb / brotli: 22.72kb
dist/assets/polyfills-legacy.80ac9ff5.js   80.20kb / brotli: 28.12kb
dist/assets/logo.03d6d6da.png    6.69kb
dist/index.html                  1.05kb
dist/assets/index.6bc7c6b6.css   0.34kb / brotli: 0.18kb
dist/assets/index.0cb1426a.js    3.28kb / brotli: 1.26kb
dist/assets/vendor.58c71f5b.js   71.19kb / brotli: 22.71kb
✨  Done in 15.95s.

あとはIEで見れるかどうか

yarn serve --host

表示された!!!

...がよく見るとレイアウトがおかしい。
cssが当たっていない?
よく見ると「#app」にスタイルが設定されていて、Vue2で初期化した時にそれがなくなっていただけみたいだったので、
コンポーネント自体にとりあえず「id="app"」をつけて再ビルド
今度こそ期待通りの表示になりました。
「Hello Vue 3」って書いてあるけど実際にはVue2ですw

IEでの確認は無理ですが、devコマンドでhmrの速度を試しました。
...早い気がする。
まだエントリーが一つだからちょっと体感しづらいかも。

今扱っているプロジェクトをViteに移行してみて速度がどれだけ変わるか試してみようと思います。

とりあえず今回は疲れたのでここまで。