背景
我们有个用umi搭建的项目一直在迭代增加功能,项目引入的第三方库也越来越多,本地开发webpack启动dev-server越来越慢,就拿我自己用的Macbook pro 14寸来说,搭载了M1 Pro芯片都要一分钟左右,热更新要3秒左右,其他前端小伙伴用的机子启动要3分钟,热更新等10多秒,这是非常影响开发体验和效率的,目前构建工具除了webpack之外还有esbuild以及swc,而目前基于esbuild的vite是非常不错的选择,所以有了迁移vite的动力
vite VS webpack
相信都2022年了大家应该都或多或少了解过vite,简介我就不再赘述了,简单列一下vite与webpack的优缺点:
vite | Webpack | |
---|---|---|
启动开发服务器 | 利用浏览器的原生ESM加载源码模块,启动时处理一次依赖预构建即可,二次启动秒开,访问时浏览器原生解析依赖 | 递归依赖分析、转换代码、编译、打包输出主流浏览器可直接执行的js文件,浏览器访问直接渲染 |
构建打包器 | 基于ESBuild使用Go编写,构建速度比nodejs快10-100倍 | webpack由javascript编写运行在nodejs环境,运行效率较差 |
热更新 | 精准更新已修改的ESM模块 | 修改文件后需要重新构建文件,且随着应用体积增大而花费更长时间 |
生态 | vite生态只能说勉强够用,某些功能可能需要妥协或者自己实现 | Loader和Plugin生态丰富,方案齐全 |
生产环境 | Esbuild本身存在一些限制,所以生产环境采用的rollup | 依托丰富的生态,稳定可靠 |
vite就像苹果刚出M1芯片的那种新世代的感觉,第一感觉是快,上手后是真香,但生态是个小遗憾。
总结一下优缺点:
在启动开发服务器、热更新方面vite掌握了绝对优势,而在生态与生产环境构建的场景下rollup对比webpack优势不明显,rollup比较适合于类库的项目打包,所以对于生产环境构建和生态考虑我依然会选择webpack
所以从对比来看vite和webpack 各自的优点都非常绝妙的满足了对方的缺点,这难道就是命中注定的另一半么,那么为何不取长补短,将双方的优势合为一体呢
vite + webpack
我们只需要在开发环境用vite,生产环境用webpack就可以形成互补,即能提高开发效率,还能保证生产稳定,值得注意的是这两套构建工具要在同一套代码里正常执行。
vite对于文件结构有特殊要求(模块化的样式文件名必须要有.module),与webpack的配置形式也大相径庭,所以还需要一个适配器(Adapter)
,只使用一种配置形式实现两套构建工具正常执行。
这个适配器
,就是来自阿里飞冰团队的ice.js`,ice.js从2.0开始同时支持Webpack@5 以及 Vite@2 两种模式,只用一套特定的配置形式,只要通过环境变量就可以实现开发环境用vite,生产环境用webpack了。
实现vite + webpack
我们可以快速初始化一个ice.js项目:
1 | $ npm init ice <projectName> |
根据交互提示选择想要的模板,创建完就可以进入项目里安装依赖启动:
1 | $ cd <projectName> |
执行上述命令后,会自动打开浏览器窗口访问 http://localhost:3333,这时应该看到默认的页面。
然后在package.json
里配置启动命令区分开发环境使用vite模式,生产环境使用webpack模式即可:
1 | "scripts": { |
个性化定制
由于ice.js内置了很多约定式的第三方能力(路由、状态管理、请求库、权限,封装过的entry等),而我从旧项目迁移必定会有不兼容写法的情况,所以想放弃内置功能,不要约定式的文件结构,自己写entry,自己接入路由、请求库、状态管理等功能,ice.js也是支持这么做的,需要在ice的配置中关闭这些功能:
1 | store: false, // 关闭自带的状态管理 |
然后根据自己需要配置src/app.tsx的entry代码就可以了,我这里有一个已经引入了react-router-dom、dva的项目可以参考:lipten/ice-vitepack-project: icejs2.0 + antd + dva.js 定制entry和工程配置的初始项目 (github.com)
常见问题
第三方库不兼容ESModule:
vite只能加载ESModule规范的模块,对于其他模块规范文件需要特殊处理,vite的依赖预构建默认会找package.json里的依赖包自动处理兼容的写法,所以第三方库我们可以放到src路径下,通过yarn add ./src/xxx 添加本地库
添加后就会在package.json自动有下面这行依赖
1 | "xxx": "./src/libs/xxx", |
require语句换成Import xxx from ‘xxx’
一些静态资源可能用了require语句,需要换成ESM的写法
动态路径加载文件地址
当vite需要实现require(@/assets/images/${imgPath}
)这种动态路径加载时,换成以下写法:
1 | // 仅支持相对路径 |
可以做一下兼容webpack的处理
1 | export const importAssetsPath = (path: string) => { |
动态加载模块import().then()
vite不支持动态加载模块import(‘xxxx’).then()
vite自带的rollup支持这种用法,所以可以增加 /* @vite-ignore */ 可以支持import(‘xxxx’).then()
引入@ant-design/compatible报错
因为@ant-design/compatible依赖了draft-js里面用了global这个变量在浏览器会报错,只要在入口js赋值一下global即可
1 | window.global = window |
require()使用动态路径时必须要有src/xxx作为静态路径
webpack可能在分析require路径时需要确定src下以及路径作为静态字符串目录
下面的代码是无法正常解析的:
1 | const path = 'assets/images/xxx.png' |
应该改为下面的路径写法:
1 | const path = 'images/xxx.png' |