# グローバル API の Treeshaking
breaking

# 2.x での構文

手動で DOM 操作を行う必要があった場合に、このようなパターンを目にしたと思います:

import Vue from 'vue'

Vue.nextTick(() => {
  // DOM に関連した処理
})
1
2
3
4
5

あるいは、非同期コンポーネント に関わるアプリケーションのユニットテストを行う場合、次のように書いていたことでしょう:

import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'

test('an async feature', async () => {
  const wrapper = shallowMount(MyComponent)

  // DOM 関連の処理を実行

  await wrapper.vm.$nextTick()

  // アサーションの実行
})
1
2
3
4
5
6
7
8
9
10
11
12

Vue.nextTick() は、個々の Vue オブジェクトに直接公開されるグローバルな API です - 実際に、インスタンスメソッドである $nextTick() は、まさに Vue.nextTick() を利便性のため this のコンテキストをそのインスタンスに自動的にバインドされたコールバックと共にラップしたものです。

しかし、もし手動で DOM 操作を行う必要がなかったり、アプリケーション内で非同期コンポーネントを扱ったり、テストすることがない時はどうでしょうか?もしくは、どんな理由であれ、代わりに古き良き window.setTimeout() を使いたい時は?そのような場合に、nextTick() を使うコードはデッドコードになります。 - すなわち、書かれてあっても使われないということです。そして、とりわけファイルサイズが大切になるクライアント側においては、デッドコードは少なくともいいものではありません。

webpack (opens new window) のようなモジュールバンドラーは、tree-shaking (opens new window) と呼ばれる、聞こえの良い "不要コード削除" をサポートします。残念ながら Vue の過去のバージョンではコードの作りに起因して Vue.nextTick() のようなグローバル API は tree-shaking 可能ではなく、実際にどこで使われているかそうでないかに関わらず、最終成果物の中に含まれてしまいます。

# 3.x での構文

Vue 3 では、グローバル API と内部 API は tree-shaking のサポートを念頭に置いて作り直されました。その結果、グローバル API は ES Modules ビルドの名前付きエクスポートとしてのみアクセスすることができます。例えば、以前のスニペットは次のようになります:

import { nextTick } from 'vue'

nextTick(() => {
  // DOM に関連した処理
})
1
2
3
4
5

そして

import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'
import { nextTick } from 'vue'

test('an async feature', async () => {
  const wrapper = shallowMount(MyComponent)

  // DOM 関連の処理を実行

  await nextTick()

  // アサーションの実行
})
1
2
3
4
5
6
7
8
9
10
11
12
13

Vue.nextTick() を直接呼び出すと、忌まわしい undefined is not a function エラーになるでしょう。

モジュールバンドラーが tree-shaking をサポートしているなら、この変更によって、Vue アプリケーション内で使用されていないグローバル API は最終成果物から削除され、最適なファイルサイズになります。

# 影響を受ける API

この変更により、次の Vue 2.x のグローバル API が影響を受けます:

  • Vue.nextTick
  • Vue.observable (Vue.reactive に置き換えられます)
  • Vue.version
  • Vue.compile (完全ビルドのみ)
  • Vue.set (互換ビルドのみ)
  • Vue.delete (互換ビルドのみ)

# 内部ヘルパー

公開 API に加え、多くの内部コンポーネントや内部ヘルパーも同様に、名前付きエクスポートされるようになります。これにより、コンパイラは機能が使われたときにのみインポートするコードを生成できるようになります。例えば、次のテンプレートは:

<transition>
  <div v-show="ok">hello</div>
</transition>
1
2
3

次のようなコードにコンパイルされます:

import { h, Transition, withDirectives, vShow } from 'vue'

export function render() {
  return h(Transition, [withDirectives(h('div', 'hello'), [[vShow, this.ok]])])
}
1
2
3
4
5

これは Transition コンポーネントが、実際にアプリケーションで使われた時のみインポートされるということを本質的に意味します。言い換えると、もしアプリケーション内に <transition> がない場合は、その機能をサポートするコードは最終成果物の中には存在しなくなります。

グローバルな tree-shaking によって、ユーザは実際に使う機能についてのみ "支払い" ます。更に良いことに、オプションな機能はそれらが使われていないアプリケーションのバンドルサイズを増加させないということは、将来的に追加される機能がどんなものであれ、フレームワークのサイズによる懸念はずっと小さくなります。

重要

上記は、tree-shaking が可能な ES Modules ビルド の利用についてのみ適用されます - 依然として、UMD ビルドはすべての機能を含み、すべてがグローバル Vue 変数上に公開されます(そして、コンパイラはインポートに代えて、グローバルの API を使用して適切な成果物を生成します)

# プラグインでの用途

例えば、もし影響を受ける Vue 2.x のグローバル API にプラグインが依存していた場合:

const plugin = {
  install: Vue => {
    Vue.nextTick(() => {
      // ...
    })
  }
}
1
2
3
4
5
6
7

Vue 3 では、明示的にインポートしなければいけません:

import { nextTick } from 'vue'

const plugin = {
  install: app => {
    nextTick(() => {
      // ...
    })
  }
}
1
2
3
4
5
6
7
8
9

webpack のようなモジュールバンドラーを使っている時、プラグインに Vue のソースをバンドルされてしまう可能性があり、これは大抵の場合に望まれた結果ではありません。これを防ぐ一般的な方法は、最終成果物から Vue を除外するようにモジュールバンドラーを設定することです。webpack の場合、 externals (opens new window) という設定のオプションが利用できます:

// webpack.config.js
module.exports = {
  /*...*/
  externals: {
    vue: 'Vue'
  }
}
1
2
3
4
5
6
7

これは、Vue モジュールを外部ライブラリとして扱い、バンドルしないように webpack に伝えます。

もし選んだバンドラーがたまたま Rollup (opens new window) であったなら、Rollup はデフォルトで絶対モジュール ID(この場合には 'vue')を外部依存として扱い、それらを最終成果物に含めないため、元から設定無しに同じ効果が得られます。バンドル中、 "Treating vue as external dependency" (opens new window) という注意が出ることがありますが、これは external オプションで抑制できます:

// rollup.config.js
export default {
  /*...*/
  external: ['vue']
}
1
2
3
4
5

Deployed on Netlify.
最終更新日: 2021-06-06, 08:44:48 UTC