babel-plugin-transform-runtimeだとIE11でVuexが使えない


Vueでアプリを開発する際、規模にかかわらずイベントバスがめんどい拡張性を担保するため、
基本的にVuexをインストールしています。
状態管理で幸せになれる便利なVuexライブラリですが、「IE11でVuex使えない!」というケースがあるので、原因と対策をまとめてみました。

webpackを使用していることを前提としています。
また、検証用に使用したvue-cli(2.9.6)でインストールされるbabelが6系のものだったので、
babel関連のモジュール名も6系のものに合わせています。
7系を使っている場合はbabel-XXX@babel/xxxに読み替えてください。

IE11のエラー

IE11でVuexが動いていない場合、ブラウザ表示が真っ白になって(何も表示されず)、コンソールで以下のエラーが吐かれるかと思います。

SCRIPT5022: [vuex] vuex requires a Promise polyfill in this browser.

どうやら、IE11でvuexを使うにはPromiseのポリフィルが必要なようです。

結論

いきなり結論ですが、上記のエラーの通り、Promiseのポリフィルを入れることでIE11にVuexを対応させることができます。
が、タイトルの通りbabel-plugin-transform-runtimeだとVuexライブラリ内のPromiseが変換されません。

ちなみに、vue-cli(3.x)ではBabel7およびpreset-envによるポリフィル注入がサポートされているため、デフォルトでIE11でもVuexが動作していることが確認できました。

言い換えると、

  • vue-cli 3.xではなくvue-cli 2.xでプロジェクトを作成した
  • ES2015+の関数をbabel-polyfillではなくbabel-plugin-transform-runtimeでトランスパイルしている
  • そもそもES2015+(Promise)のpolyfillをどこにも置いていない

といった場合にIE11でVuexが使えないケースが起こりうるのではないかと思います。

原因

Vuexライブラリでは内部的にPromiseが使用されています。
(actionsがPromiseを返す、とかそのあたりだと思う)

IE11はPromiseをサポートしていないため、Promiseって知らないからポリフィル入れてね、とエラーが出るわけですね。

transform-runtimeだとダメな理由

transform-runtimeはグローバルにpolyfillを置くものではありません(グローバル汚染しないなどの利点があります)。
ES2015+の関数があれば、JSファイルごとに必要な箇所を書き換えるよ、という静的解析するものなので、
node_modulesであるVuexライブラリ内部のPromiseに関しては変換の対象外となってしまいます。

babelの設定に関しては以下の記事が参考になります。

対処と検証

まずは実際にIE11でVuexが使えない環境を再現します。

検証用プロジェクトの作成

今回検証のためにvue-cli(2.9.6)を使ってプロジェクトを作りました。

Vue.js を vue-cli を使ってシンプルにはじめてみる」を参考にさせていただきました。

生成された.babelrcを見ると、pluginsに"transform-runtime"が使用されているようです。

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}

プロジェクトを作成したら、Vuexをインストールします。

npm i -S vuex

つぎに、ストアのインスタンスを新規作成します。

/src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    hoge: 'Storeのhogeです'
  }
})

export default store

作成したストアを/src/main.jsで読み込みます。

/src/main.js.diff
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'

+ import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
+ store,
  components: { App },
  template: '<App/>'
})

プロジェクト作成時にHelloWorld.vueが作成されていると思うので、ここにStoreの情報を表示するよう変更します。

/src/components/HelloWorld.vue.diff
<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
-     msg: 'Welcome to Your Vue.js App'
+     msg: this.$store.state.hoge
    }
  }
}
</script>

Vuexが使えていれば、以下のh1タグ内にStoreのhogeが表示されすはずです。

/src/components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1> //ここに表示されるはず

下記コマンドを叩いてlocalhostで確認します。

npm run dev

ChromeなどPromiseをサポートしているブラウザでは、問題なく下記のように表示されるかと思います。

IE11は何も表示されず、例のエラーが表示されます。

IE11でVuexを使えるようにする

前置きが長くなりましたが、いよいよ本題です。
以下の2つのやり方のいずれかで対処することができました。

1. es6-promiseをimportする方法

es6-promiseをインストールします。

npm i -D es6-promise

main.jsにes6-promiseをimportします。
注意点として、storeのimport文より前に記述してください。

/src/main.js.diff
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'

+ import 'es6-promise/auto'
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

2. babel-polyfillをバンドル時に結合する方法

babel-polyfillをインストールします。

npm i -D babel-polyfill

webpack.base.conf.jsを修正して、main.jsをバンドルする際にbabel-polyfillを結合します。

/build/webpack.base.conf.js.diff
module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
-   app: './src/main.js'
+   app: ['babel-polyfill','./src/main.js']
  },

同時に、.babelrc"transform-runtime"は不要になるはずなので、こちらを削除します。

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
- "plugins": ["transform-vue-jsx", "transform-runtime"],
+ "plugins": ["transform-vue-jsx"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}

結果

無事、IE11でもVuexが使用できるようになりました。

参考

下記を参考にしました。

FORK Advent Calendar 2018
20日目 git cloneできない時の調べ方と対策 〜AWS CodeCommit/Mac OSの場合〜 @izanari
22日目 Apache(php-fpm + Apache 2.4)で何を設定すればいいのかわからなかったので調べてみた @Kodak_tmo