从Vue 2到Vue 3的迁移指南之破坏性特性(二、全局API的tree-shaking)__Vue.js
发布于 4 年前 作者 banyungong 1795 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

1、内容系个人翻译自 Vue 3.0 Beta 版文档中 Migration From Vue 2 章节
2、不一定准确,欢迎交流(别人都翻过几百遍了吧,再说也没人看)
3、水平有限、随时太监

· 从Vue 2到Vue 3的迁移指南之破坏性特性(一、全局API)

全局 API Treeshaking <MigrationBadges :badges="$frontmatter.badges" />

2.x 语法

如果你曾需要在 Vue 中手动操作 DOM 的话,你可能见过下面这种模式:

import Vue from 'vue'

Vue.nextTick(() => {
  // DOM相关操作
})

或者当你在对一个异步组件进行单元测试的时候,你可能写过像这样的东西:

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()

  // 运行你的断言
})

Vue.nextTick() 是一个从 Vue 对象直接暴露出来的全局 API,其实 $nextTick() 只是 Vue.nextTick() 的一个简易包装,只是为了方便而把后者的回调函数的 this 绑定到了当前的实例。

但如果你从来没有手动操作 DOM 的需要,或者你不需要在你的应用中使用或者测试异步组件,又或者因为一些理由你更喜欢用老的 window.setTimeout() 来处理问题,这样一来, nextTick() 的相关源码都会变成 “dead code” ,也就是写出来了但是从未被使用到的代码。这类无用代码确实不是什么好东西,尤其是在客户端侧这种寸土寸金的环境。

webpack 这样的打包工具是支持 tree-shaking(精简无用代码的术语)的,不幸的是,由于此前 Vue 中的写法,像 Vue.nextTick() 这样的全局 API 是无法进行 tree-shaking 的,而最终无论你是否有用到,打包后的代码都会包含这些 API。

3.x 语法

在 Vue 3 中,我们在考虑到 tree-shaking 的基础上重构了全局和内部 API,而结果就是,现在的全局 API 需要通过原生 ES Module 的引用方式进行具名引用,前文提到的代码段现在需要改写成这样:

import { nextTick } from 'vue'

nextTick(() => {
  // DOM相关操作
})

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()

  // 运行你的断言
})

现在执行 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>

上面的代码会被编译成类似下面这样:

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

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

这意味着 Transition 这个组件仅当应用真的用到了它的时候它才会被导入。换句话说,如果应用没有用到 <transition> 组件,那么最终打包完成的包内就不会有这个方法相关的代码。

通过全局的 tree-shaking,用户就只需要对自己真正使用到的那些方法“买单”。更大的好处是,我们知道这些可选功能即便不使用也不会额外增加打包后的体积,这样一来以后我们就不用再那么关注框架的大小了。

重要 上述的变化仅适用于 ES Modules 构建 的版本加上支持 tree-shaking 的打包工具时才有效,也就是说 UMD 构建的版本依旧会包含所有的这些方法、同时将暴露到 Vue 的全局变量环境中(并且编译器将生成恰当的输出结果使得可以在全局环境中访问到这些 API 而不是用导入的方式)。

插件中的用法

如果你的插件依赖于 Vue 2.x 中全局 API 的作用,例如:

const plugin = {
  install: Vue => {
    Vue.nextTick(() => {
      // ...
    })
  }
}

在 Vue 3 中你就得明确地把这些东西手动导入:

import { nextTick } from 'vue'

const plugin = {
  install: app => {
    nextTick(() => {
      // ...
    })
  }
}

如果你使用了像 Webpack 这样的打包工具,则它可能会将 Vue 的源码也一起打包到你的插件里面,这通常不是你想看到的事情。避免这种情况的通常做法是配置打包工具,让工具把 Vue 从打包结果中排除。像 webpack 的话,你可以配置 externals 参数:

// webpack.config.js
module.exports = {
  /*...*/
  externals: {
    vue: 'Vue'
  }
}

这样配置会告诉 webpack 把 Vue 当做一个外部的库,从而不会把它一起进行打包。

如果你选择的打包工具恰好是 Rollup 的话,你基本上就不用做额外的工作就可以获得相同的效果,默认情况下,Rollup 会将拥有确定ID(我们使用的是'vue')的模块视作是外部依赖,不会将其包含进最后的包。在编译的过程中可能会触发一条警告: “Treating vue as external dependency”,需要去除的话可以使用 external 这个参数:

// rollup.config.js
export default {
  /*...*/
  external: ['vue']
}

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: DarkNin 原文链接:https://juejin.im/post/6860178370928312328

回到顶部