前言
关于Vite和Vue3的讨论越来越多,看了官网的特性后,真是按捺不住想尝试一下。开发环境秒开?Composition API
? SFC Style CSS Variable Injection
? 看起来哪个都比webpack+Vue2香呀。(尤大都向React推荐Vite了,难道你还不试一下Vite么?)
其实在去年,我们在LOFTER的哈利波特街区活动中就尝试使用了Vite2 + Vue3搭建活动主街区页面,当时对Vue3的语法还不是很适应,为啥ref要用value去取值?reactive解构后的响应性怎么没了?Vite热更新咋不太灵光?为了保证活动页在Android6以下能满足基本交互需要,还引入了es6-proxy-polyfill
,友好的提示用户系统不支持。出于兼容性和稳定性的考虑,我们没有在其他项目中应用。
时间来到今年七月,随着Vue2.7的正式发布,很多Vue3的编码特性被正式迁移到了Vue2,更像是Vue3的基础试用版(Vue:再过18个月,我也懒得管Vue2了)。
The Composition API is backported using Vue 2's getter/setter-based reactivity system to ensure browser compatibility.
就是官方的这句话,让我觉得把现在的Vue2项目完全升到2.7是一件百利而无一害的事情。我们的Vue2项目随着业务迭代承载着更加复杂的业务场景,如何提升项目的开发体验也提上了日程。实际使用中,Vite还是挺多坑要踩的,但是Vue2.7更新的内容,结合社区生态,对业务实现和开发体验的提升都提供了助力。
实战——检验Vite+Vue2.7
那就找个场景先预研一下吧,于是稻米节活动被拿来做了实验(但愿不会被稻米看到)。
通过对官方文档的阅读,以及查看社区的讨论,Vue2.7最次也是支持option api的(万一遇到解决不了的bug降级一下写法就好了)。既然做实验,就要有实验目的。
- 检验SFC setup语法糖的各种写法兼容性
- 探索Vite现在发展的怎么样了
- 检验Vite生产环境的稳定性
先贴个结论,1和2的结果都挺好,3就是坑多多了。
实验流程:
项目搭建
单纯的Vite Getting Started
流程实在是过于简单,啥配置都没有,难不成每个功能都要去搜索引擎查?还好有 awesome-vue 这个项目,集结了大家的智慧(踩过的坑),参阅了各种脚手架模板后,结合业务需要,项目架子初步搭好。
简单贴一下package.json
"dependencies": {
"@sentry/browser": "^5",
"@sentry/integrations": "^5", // sentry用来收集异常
"@vueuse/core": "^9.0.2", // 基于Composition API的工具函数,同时支持Vue2, Vue3
"axios": "^0.27.2",
"nejsbridge": "^1.7.19",
……
"vue": "^2.7.8",
"vue-clipboard2": "^0.3.3",
"vue-router": "^3.5.4"
},
"devDependencies": {
"@antfu/eslint-config": "^0.25.2", // Anthony Fu是Vue和Vite团队的核心成员,有很多开源作品
"@vitejs/plugin-legacy": "^2.0.0", // 自动生成传统版本的chunk及与其相对应ES语言特性方面的polyfill
"@vitejs/plugin-vue2": "^1.1.2", // plugin only works with Vue@^2.7.0.
"autoprefixer": "^10.4.8",
"eslint": "^7.32.0",
"less": "^4.1.3",
"nei-ts-helper": "^0.1.3", // 组内接口生成TS声明的工具
"terser": "^5.4.0", // 生产环境打包代码需要
"typescript": "^4.6.4",
"unplugin-auto-import": "^0.10.1", // vue函数的自动导入
"unplugin-vue-components": "^0.21.2", // vue组件库的自动按需导入
"vite": "^3.0.4",
"vue-tsc": "^0.38.4"
}
重点推荐一下unplugin-auto-import
和unplugin-vue-components
。
unplugin-auto-import解决了vue3-hook、vue-router、useVue等多个插件的自动导入,也支持自定义插件的自动导入,是一个功能强大的Typescript
支持工具。基于unplugin,在构建和打包的时候自动解析模块并引入。
// vite.config.js
plugins: [
AutoImport({
imports: [
'vue',
'vue-router',
'@vueuse/core',
],
// 解决eslint报错问题
eslintrc: {
enabled: false,
globalsPropValue: true,
},
dts: 'src/auto-imports.d.ts',
}),
……
]
// ==> 自动生成全局声明 auto-imports.d.ts
declare global {
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
……
}
unplugin-vue-components支持自定义组件自动引入,同样基于unplugin
// vite.config.js
plugins: [
Components({
transformer: 'vue2', // vue2.7必需
dirs: ['src/components'],
extensions: ['vue'],
dts: 'src/components.d.ts',
}),
……
]
// ==> 自动生成全局声明 auto-imports.d.ts
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Container: typeof import('./components/Container.vue')['default']
Header: typeof import('./components/Header.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
……
}
}
业务开发
- 使用插件后代码的书写简便了很多,格式更加清爽,import理论上可以通过完善插件做到完全消除。
<script setup>
import {
getNosThumbWebP,
} from '@/common/utils';
const props = defineProps(['blogNickName']);
const name = computed(() => (props.blogNickName ? `@${props.blogNickName}` : '你的'));
const bgUrl = `url(${getNosThumbWebP('https://lofter.lf127.net/xxx/header.png', {
thumbnail: '1125x0',
})})`;
</script>
<template>
<div class="header">
<div class="user-name">{{name}}</div>
</div>
</template>
<style scoped lang="less">
.header {
width: 100%;
height: 1333px;
background-image: v-bind(bgUrl);
background-size: 100% 100%;
background-repeat: no-repeat;
position: relative;
.user-name {
……
}
}
</style>
- SFC Style CSS Variable Injection,力荐的style管理方案。可以实现JS和CSS的通信与样式的响应式更新,在主题切换场景中应用十分方便。具体可以查看 Vue3 的 SFC Style CSS Variable Injection 提案实现的背后
注意:v-bind作用的元素如果有v-if
的场景,最好不是template
里的根元素,否则可能导致css变量无处挂载。
- setup语法糖,自己用了才知道,真的好用。还需要在更复杂的业务场景中实践,结合类似
vueuse
的用法做逻辑封装。 - 热更新经常无效,
Vue.extend(ToastConfig)
创建的组件完全不会更新,需要重启dev server才行。
打包测试
- 踩过坑才知道下面这部分内容有多重要
- 构建生产版本-浏览器兼容性
- 用于生产环境的构建包会假设目标浏览器支持现代 JavaScript 语法。默认情况下,Vite 的目标是能够 支持
原生 ESM script 标签
、支持原生 ESM 动态导入
和import.meta
的浏览器:- 你也可以通过 build.target 配置项 指定构建目标,最低支持 es2015。
- 请注意,默认情况下 Vite 只处理语法转译,且 默认不包含任何 polyfill。你可以前往 Polyfill.io 查看,这是一个基于用户浏览器 User-Agent 字符串自动生成 polyfill 包的服务。
- 传统浏览器可以通过插件
@vitejs/plugin-legacy
来支持,它将自动生成传统版本的 chunk 及与其相对应 ES 语言特性方面的 polyfill。兼容版的 chunk 只会在不支持原生 ESM 的浏览器中进行按需加载。
// 通过监测支持import.meta.url和动态引入判断是否为现代浏览器
<script type="module">try{
import.meta.url;
import("_").catch(()=>1);
}catch(e){}
window.__vite_is_modern_browser=true;
</script>
<script type="module">!function(){
if(window.__vite_is_modern_browser)return;
console.warn("vite: loading legacy build because dynamic import or import.meta.url is unsupported, syntax error above should be ignored");
var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)
}();</script>
线上的一个白屏问题,发现报错源自try{}catch{}
这种ES10才支持的语法。
vite3修改了默认的打包配置,仅支持Chrome >=87。需要通过修改build: target提升构建产物的兼容性。
- _VITE_ASSET legacy.js打包的css相对路径不对,全换成cdn资源解决(最新版vite已修复该问题)。
暂没有遇到其他兼容性问题。
进阶——将一个重度Webpack+Vue2工程升级为Vite+Vue2.7
LOFTER市集工程用webpack的重度体现在
- 一套dev+build+init命令行交互
- 自定义页面参数配置,结合HtmlWebpackPlugin形成动态页面配置,HtmlWebpackPlugin确实是多页应用构建的神器
- 有些年头的css,babel配置
- 私有的webpack插件,离线包插件等
这些要么移除掉,要么在Vite中找到替代方案。
开始升级。。。
- 安装
vite
,vue2.7
,@vitejs/plugin-vue2
,@vitejs/plugin-legacy
去掉vue-template-compiler
- 增加
vite.config.js
,配置alias
,less
,vite会默认按root
配置的文件目录,自动查找文件目录下的index.html
,页面模板中添加代码
<script type="module" src="/cardHome/index.js"></script>
然后就是疯狂报错
- require url问题
vue.runtime.esm.js:4573 [Vue warn]: Error in created hook: "ReferenceError: require is not defined"
import.meta.url 是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL
// icon: require('@/common/images/card/upgrade/home.png') =>
icon: new URL('@/common/images/card/upgrade/home.png', import.meta.url).href
- svg 雪碧图插件替换,
svg-sprite-loader
->vite-plugin-svg-icons
symbolId的组装方式不同,stroke color替换后者有问题,暂时可以通过样式覆盖解决
- css-loader中
@value
的用法在vite中不支持,可以使用less变量的方式替换,不全局使用css变量一个是兼容性,还有是市集本身颜色不需要切换
/* @value --default_common_bg_tertiary: #3E3E3E; */
@default_common_bg_tertiary: #3E3E3E;
/* background: --default_common_bg_tertiary; */
background: @default_common_bg_tertiary;
- 使用HTTP2,可以配置
server.https
来启用,像卡牌首页的代码模块很多,通过H2可以改善请求量大导致的页面刷新耗时较长的问题。但是
注意:当 server.proxy 选项 也被使用时,将会仅使用 TLS。
这会导致如果配置了接口代理的情况下项目又用不了H2了。
可以使用nginx
解决该问题,最新版nginx(1.22.0)
支持配置listen 443 ssl http2;
结合server.https
和base
配置的路径,可以实现在开发环境下使用H2加速页面刷新。
Webpack与Vite开发环境比较
基于win10,i5-6500 CPU,16.0 GB内存
webpack devtool: 'cheap-module-eval-source-map', | vite 便于比较,使用http/1.1 | |
---|---|---|
无缓存run dev | 全部40s 首页+背包24s | 暂无全部数据 首页+背包3s+,需要更长渲染时间 |
有缓存run dev | 全部30s 首页+背包18s | 暂无全部数据 首页+背包3s- |
热更新 | 首页7s左右 | 无提示,看起来很快,有时候改了组件代码刷新也没用,只能重新run dev |
背包页无缓存刷新 | 38 requests 3.1 MB transferred 12.7 MB resources Finish: 5.44 s DOMContentLoaded: 1.79 s Load: 2.52 s | 191 requests 3.7 MB transferred 3.7 MB resources Finish: 3.49 s DOMContentLoaded: 1.81 s Load: 3.46 s |
背包页有缓存刷新 | 34 requests 12.6 kB transferred 12.7 MB resources Finish: 1.99 s DOMContentLoaded: 1.20 s Load: 1.91 s | 191 requests 446 kB transferred 3.7 MB resources Finish: 3.02 s DOMContentLoaded: 1.48 s Load: 2.99 s |
首页无缓存刷新 | 82 requests 20.1 MB transferred 38.0 MB resources Finish: 5.55 s DOMContentLoaded: 2.57 s Load: 4.35 s | 278 requests 20.8 MB transferred 24.9 MB resources Finish: 4.84 s DOMContentLoaded: 2.46 s Load: 4.42 s |
首页有缓存刷新 | 81 requests 120 kB transferred 38.0 MB resources Finish: 3.39 s DOMContentLoaded: 1.82 s Load: 3.09 s | 278 requests 557 kB transferred 24.4 MB resources Finish: 3.88 s DOMContentLoaded: 1.92 s Load: 3.15 s |
- 比较1:在使用esm的情况下,页面的渲染速度并没有显著提升,使用H2或加强模块异步加载的处理会稍微好点。
在实际项目中,Rollup 通常会生成 “共用” chunk Vite 将使用一个预加载步骤自动重写代码,来分割动态导入调用,以实现当 A 被请求时,C 也将 同时 被请求: Entry ---> (A + C) C 也可能有更深的导入,在未优化的场景中,这会导致更多的网络往返。Vite 的优化会跟踪所有的直接导入,无论导入的深度如何,都能够完全消除不必要的往返。
- 比较2:esm会发出更多的requests,浏览器等待耗时明显,偶尔可能会阻塞导致更长的渲染时间
- 比较3:Vite热更新不理想,需要经常刷新或者重启,重启dev虽然很快,但是页面渲染等待耗时可能较长
关于vite的热更新: Vite 通过特殊的 import.meta.hot 对象暴露手动 HMR API。 Vite 热更新问题排查 可以监听自定义 HMR 事件,在插件中处理未更新的情况
还有一些应用场景进一步实践后再和大家分享。
一些感想:
- Vite更像是一个上层建筑,集合了esbuild,rollup,各种loader等,如果项目比较复杂,仍然需要深度定制,逃不开配置的递归使用。
- 新项目还是很推荐用Vite构建,直接使用Vite和rollup的生态。
- 老项目从webpack迁移还是缺少一些完备的插件,也是为社区贡献的机会,开发环境Vite生产环境webpack理论上是可行的,需要处理两者之间的差异,比如环境变量,插件的差异等等。开发环境和生产环境的构建方式不同总感觉会有坑,还是需要慎重。