# TreeShaking da API Global
breaking

# Sintaxe v2.x

Se você já teve que manipular manualmente o DOM no Vue, pode ter encontrado este padrão:

import Vue from 'vue'

Vue.nextTick(() => {
  // algo relacionado ao DOM
})
1
2
3
4
5

Ou, se você fez testes unitários em seu aplicativo envolvendo componentes assíncronos, provavelmente você escreveu algo assim:

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

test('um recurso assíncrono', async () => {
  const wrapper = shallowMount(MyComponent)

  // execute alguma tarefa relacionada ao DOM

  await wrapper.vm.$nextTick()

  // execute suas asserções
})
1
2
3
4
5
6
7
8
9
10
11
12

Vue.nextTick() é uma API global exposta diretamente em um único objeto Vue - na verdade, o método de instância $nextTick() é apenas um wrapper em torno de Vue.nextTick() com o contexto this do retorno de chamada automaticamente vinculado à instância atual por conveniência.

Mas e se você nunca teve que lidar com manipulação manual do DOM, nem está usando ou testando componentes assíncronos em seu aplicativo? Ou, e se, por qualquer motivo, você preferir usar o bom e velho window.setTimeout() em vez disso? Nesse caso, o código para nextTick() se tornará um código morto - ou seja, o código que foi escrito, mas nunca usado. E código morto dificilmente é uma coisa boa, especialmente em nosso contexto do lado do cliente, onde cada kilobyte é importante.

Os empacotadores de módulo, como o webpack (opens new window), oferecem suporte à tree-shaking (opens new window), que é um termo sofisticado para “eliminação de código morto”. Infelizmente, devido à forma como o código é escrito nas versões anteriores do Vue, APIs globais como Vue.nextTick() não podem ser eliminadas com tree-shaking e serão incluídas no pacote final, independentemente de onde sejam realmente usadas ou não.

# Sintaxe v3.x

No Vue 3, as APIs globais e internas foram reestruturadas tendo em mente o suporte à tree-shaking. Como resultado, as APIs globais agora podem ser acessadas apenas como exportações nomeadas para a construção de Módulos ES. Por exemplo, nossos blocos de códigos anteriores agora devem ser semelhantes a este:

import { nextTick } from 'vue'

nextTick(() => {
  // algo relacionado ao DOM
})
1
2
3
4
5

e

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

test('um recurso assíncrono', async () => {
  const wrapper = shallowMount(MyComponent)

  // execute alguma tarefa relacionada ao DOM

  await nextTick()

  // execute suas asserções
})
1
2
3
4
5
6
7
8
9
10
11
12
13

Chamar Vue.nextTick() diretamente agora resultará no abominável erro undefined is not a function.

Com essa mudança, dado que o empacotador de módulos suporte tree-shaking, APIs globais que não são usadas em seu aplicativo Vue serão eliminadas do pacote final, resultando em um ótimo tamanho de arquivo.

# APIs Afetadas

Essas APIs globais no Vue 2.x são afetadas por esta mudança:

  • Vue.nextTick
  • Vue.observable (substituído por Vue.reactive)
  • Vue.version
  • Vue.compile (apenas em compilações completas)
  • Vue.set (apenas em compilações de compatibilidade)
  • Vue.delete (apenas em compilações de compatibilidade)

# Ajudantes Internos

Além das APIs públicas, muitos dos componentes/ajudantes internos agora também são exportados como exportações nomeadas. Isso permite que o compilador produza um código que importa apenas recursos quando eles são usados. Por exemplo, o seguinte template:

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

é compilado em algo semelhante ao seguinte:

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

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

Isso significa essencialmente que o componente Transition só é importado quando o aplicativo realmente faz uso dele. Em outras palavras, se o aplicativo não tiver nenhum componente <transition>, o código que suporta esse recurso não estará presente no pacote final.

Com o tree-shaking global, os usuários “pagam” apenas pelos recursos que realmente usam. Melhor ainda, sabendo que os recursos opcionais não aumentarão o tamanho do pacote para aplicativos que não os utilizam, o tamanho do framework se tornou muito menos uma preocupação para recursos centrais adicionais no futuro, isso se houver.

Importante

O que foi dito acima se aplica apenas as Construções de Módulos ES para uso com empacotadores capazes de aplicar tree-shaking - o construtor UMD ainda inclui todos os recursos e expõe tudo na variável global Vue (e o compilador produzirá a saída apropriada para usar APIs fora do global em vez de importar).

# Uso em Plugins

Se o seu plug-in depende de uma API global do Vue 2.x afetada, por exemplo:

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

No Vue 3, você terá que importá-lo explicitamente:

import { nextTick } from 'vue'

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

Se você usar um empacotador de módulo como webpack, isso pode fazer com que o código-fonte do Vue seja agrupado no plug-in e, na maioria das vezes, não é o que você esperava. Uma prática comum para evitar que isso aconteça é configurar o empacotador de módulo para excluir Vue do pacote final. No caso do webpack, você pode usar a opção de configuração externals (opens new window):

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

Isso dirá ao webpack para tratar o módulo Vue como uma biblioteca externa e não empacotá-lo.

Se o empacotador de módulo de sua escolha for Rollup (opens new window), você basicamente obterá o mesmo efeito de graça, pois por padrão o Rollup tratará IDs de módulo absolutos ('vue' em nosso caso) como dependências externas e não incluí-las no pacote final. No entanto, durante o empacotamento, ele pode emitir um aviso “Tratando vue como dependência externa” (opens new window), que pode ser suprimido com a opção external:

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