当前位置: 首页 > 科技观察

我们从UmiJS迁移到Vite

时间:2023-03-15 18:59:39 科技观察

从UmiJS迁移到Vite已经半年多了。我们在迁移过程中也遇到了很多问题。好在Vite足够好,继承自Rollup的插件系统给了我们自由发挥的空间。目前,很多人都跃跃欲试Vite。Vite的开发经验是怎样的?今天,我将描述我迁移到Vite的个人经历。先说结论吧,Vite已经很成熟了,有条件的话强烈建议从webpack迁移过来。为什么放弃UmiJS2019年底,在Webpack横行,各种脚手架层出不穷的时代,选择了UmiJS。配置少,功能多,文档齐全,持续更新。一套完整的解决方案,非常适合大部分非React技术栈的团队。经过不断磨合,团队很快适应了这种React开发模式,开发效率也有所提升。一切总是有原因的,为什么要迁移。2021年初,为适应公司发展,前端架构也需要调整升级。随着项目越来越多,启动一个项目需要一分多钟,热更新慢到基本用不上。几乎机器配置启动项需要几分钟或耗尽内存。这种模式大大降低了开发效率。无论是自定义修改内部的webpack插件,多核编译、缓存等多角度的优化,都还是杯水车薪。虽然UmiJS提供了webpack5插件,但当时还没有。我们的主要矛盾是:启动时间长,热更新慢,过于臃肿,框架bug修复不及时,过度封装,插件定制困难。UmiJS还提供了微前端插件“乾坤”。但是还是解决不了根本的开发体验问题。因此,在基础脚手架上,我们寻求更多的可控性和透明性。(虽然UmiJS现在支持ModuleFederation的打包提速方案)为什么Vite市场上脚手架很多,但阵营很少,大部分都是基于webpack的上层打包。webpack的缺点很明显。当开发服务器冷启动时,基于打包程序的启动必须首先抓取并构建您的整个应用程序,然后再提供服务。在浏览器ESM支持非常普遍的今天,Vite堪称下一代前端开发构建工具。在Vite中,HMR是在原生ESM上执行的。编辑文件时,无论应用程序大小,HMR始终是最新的。在我们习以为常的webpack的阴影下,Vite的做法显得尤为惊人。可以说,Vite完美解决了我们所有的痛点。不过,Vite刚刚发布2.0,踩过的人也不少。让我们试试Vite。早期研究迁移的必要条件是在原有功能下找到替代方案,我们已经使用了UmiJS中的API和特性。UmiJS配置alias-配置别名(对应resolve.alias)base-设置路由前缀(对应base)define-用于提供代码中可用的变量(对应define)outputPath-指定输出路径(对应build.outDir)hash-配置是否让生成的文件包含hash后缀(Vite自带)antd-集成antd组件库(没有提供框架,可以在Vite中自己引用)dva-集成dva数据流(这个库有好久没更新了,在hooks时代用起来显得格格不入。我们用的不多,改写一个文件很容易)locale-国际化插件,用于解决i18n问题(需要自己实现国际化逻辑,全部基于react-intl封装,实现无压力inVite)fastRefresh-快速刷新(对应@vitejs/plugin-react-refresh插件)dynamicImport-是否开启按需加载(路由级按需加载,在Vite中封装在React.lazy中)targets-配置兼容浏览器的最低版本(对应@vitejs/plugin-legacy插件)theme-配置less变量(对应css.preprocessorOptions.less.modifyVars配置)lessLoader-设置less-loader配置项(同theme配置)ignoreMomentLocale-忽略moment的locale文件(alias可以通过aliasSolution设置)proxy-配置代理能力(对应server.proxy)externals-设置哪些模块不能打包(对应build.rollupOptions.external)copy-设置要复制到输出目录的文件或文件夹(对应rollup-plugin-copy)mock-配置mock属性(对应vite-plugin-mock)extraBabelPlugins-配置额外的babel插件(对应@rollup/plugin-babel)通过配置分析,基本上所有UmiJS配置都可以在Vite中找到替代方案。除了配置,UmiJS中还有一些约定@/*路径,而不是defineConfig({resolve:{alias:{'@/':`${path.resolve(process.cwd(),'src')}/`,},},});MigrateReview现有代码,找出可能存在的问题并进行统计。做好准备。先运行:从官方Vite模板创建项目,安装所需依赖。UmiJS内置了react-router和antdreact-intl包,这里我们需要手动添加BrowserRouter、ConfigProvider、LocaleProvider//App.tsxexportdefaultfunctionApp(){return();}根据之前的常规路由,添加对应的路由配置exportconstbasicRoutes=[{path:'/',exact:true,trunk:()=>import('@/pages/index'),},{path:'/login',exact:true,trunk:()=>import('@/pages/login'),},{path:'/my-app',trunk:()=>import('@/pages/my-app'),},//...];路由渲染组件,通过React。惰性实现dynamicImportconstroutes=basicRoutes.map(({trunk,...config})=>{constTrunk=React.lazy(()=>trunk());return{...config,component:(}>),};});exportdefaultfunctionRoutes(){return({routes.map((route)=>(route.component}/>))});}从原常规路由迁移完成,项目中主要不兼容的是从umi导入的成员import{useIntl,history,useLocation,useSelector}from'umi';我们需要导入umi中的所有变量,通过编辑器的定期替换批量修改替换国际化的useIntl通过封装语言文件和react-intl,导出一个全局的formatMessage方法Routing相关的APIs使用react-router-domexport替换Redux相关的ones,使用react-reduxexport替换searchitems使用require的地方,用dynamicimport替换。在搜索项目中使用process.env.NODE_ENV,替换为import.meta.env.DEV,因为Vite中没有node.js相关的API。将antd加入项目后,发现babel-plugin-import对应的vite插件好像有问题。dev模式下有些样式缺失,打包后正常。排查发现组件包中引用了antd,在dev模式下被“依赖预建”混淆了包名,导致插件无法正确插入antd样式。为此,我们自己写了一个插件,在dev模式下导入所有样式,插件只在prod中使用。简单,第一页成功。由于迁移后需要使用微前端,所以我们通过外部插件来管理公共配置。exportdefaultdefineConfig({server:{//每个项目配置不同的端口号port:3001,},plugins:[reactRefresh(),//公共配置插件baseConfigPlugin(),//AntD插件antdPlugin(),],});迁移后发现Vite需要配置的很少,提取出来的公共配置打包成一个Vite插件。importpathfrom'path';importLessPluginImportNodeModulesfrom'less-plugin-import-node-modules';exportdefaultfunctionvitePluginBaseConfig(config:CustomConfig):Plugin{return{enforce:'post',name:'base-config',config(){return{cacheDir:'.vite',resolve:{alias:{'@/':`${path.resolve(process.cwd(),'src')}/`,lodash:'lodash-es','lodash.debounce':'lodash-es/debounce','lodash.throttle':'lodash-es/throttle',},},server:{host:'0.0.0.0',},css:{preprocessorOptions:{less:{modifyVars:{'@primary-color':'#f99b0b',...config.theme,//自定义ant前缀'@ant-prefix':config.antPrefix||'ant',},plugins:[newLessPluginImportNodeModules()],javascriptEnabled:true,},},},};},};}迁移的整个过程并没有想象中那么复杂,反而比较容易。Vite对几乎常用的功能都有解决方案支持,这可能是Vite的强项。事实上,本质上的复杂性在于业务。项目的复杂度是代码量的体现。通过IDE的查找和替换,很快完成了迁移并成功运行。现在,我们所有的项目都基于Vite,再也不用担心等待和钓鱼。问题/解决方案转换less文件@import'~antd/es/style/themes/default.less'中的~别名配置less插件报错less-plugin-import-node-modulesSyntaxError:Therequestedmodule'xxx'doesnotprovideanexportnamed'default'之后我们将公共组件用作独立的npm包时出现的错误。本以为公共组件包不会自己编译,交给用户自己编译。这样就导出了TS源文件。一般来说,这种情况下是没有问题的。Vite一旦遇到CommonJS或UMD包,就无法解析。虽然可以将无法解析的包放入optimizeDeps.include。但是受不了包太多,就通过tsc翻译成js文件发布。打包提速第一次打包发现用了70多秒。我们优化打包结构,通过build.minify改成esbuild(最新版Vite已经默认esbuild)。Esbuild比terser快20-40倍,压缩率只差1%-2%。像babel-plugin-import这样的babel-like插件在打开时会严重减慢到30+秒,在总共不到40秒的时间内占用了10秒。我们有规律地做了一个插件,通过分析rollup完美解决了@ant-design/icons和lodash包的大量转换。我们在刚刚做的插件中也加入了这些包,运行了一小会儿,速度就提升到了16秒,那我们先动手吧。为什么把cacheDir放在根目录下cacheDir作为存放缓存文件的目录。这个目录会存放预先打包好的依赖或者vite生成的一些缓存文件。使用缓存可以提高性能。某些情况下需要联合调试node_modules包,导致修改不生效。这时候需要使用--force命令行选项或者手动删除目录,放在根目录下,方便删除。兼容性问题Vite的兼容性可以通过官方插件@vitejs/plugin-legacy解决。我们已经放弃了对IE11的支持,可以在生产中不受限制地使用ESM。你羡慕吗?结论如果你是一个新项目,你完全不需要考虑Webpack。Vite和rollup的全生态足以支持生产。如果你是Webpack生态中的老项目,受不了经验的折磨,满足迁移条件的话,不妨试试Vite,一定会给你带来惊喜。后面我会分享Vite和我自己的微前端的结合,以及Vite相关的插件,请继续关注。