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

webpack打包太慢?试试Bundleless

时间:2023-03-12 06:53:21 科技观察

阿里妹的介绍:Webpack将各种资源打包整合成一个bundle。当资源越来越多时,打包过程会越来越慢。如果我们不需要打包怎么办?让浏览器直接加载相应的资源,能不能达到质的提升?本文分享了Vite基于浏览器ESModule能力实现Bundless本地开发的相关思路、核心技术点及相关实现,以及在阿里巴巴供应链POS场景的落地实践。一、介绍Webpack最初的出现是为了解决前端模块化和利用Node.Js生态的问题。在过去的8年里,Webpack的能力越来越强大。但是因为多了一层打包构建,随着项目的增长,打包构建的速度越来越慢。每次启动都要等待几十秒甚至几分钟,然后再开始一轮构建优化。随着项目进一步变大,构建速度会再次下降,陷入不断优化的循环。当项目达到一定规模后,Bundle-based构建优化的收益越来越有限,无法实现质的提升。我们换个角度想,webpack之所以慢的主要原因是它把各种资源打包整合成一个bundle。如果我们不需要bundle打包的过程,让浏览器直接加载相应的资源,我们就有可能跳出这个循环,获得质的提升。在Bundleless架构下,我们不再需要构建一个完整的bundle,当修改文件时,浏览器只需要重新加载单个文件。由于没有构建层,我们将能够实现以下目标:极快的本地启动速度,只需要启动本地服务。极快的代码编译速度,一次只需要处理一个文件。项目开发建设的时间复杂度始终为O(1),使得项目能够持续保持高效建设。更简单的调试体验,不再严重依赖sourcemaps来实现稳定的单文件调试。基于以上的可能性,Bundleless将重新定义前端的本地开发,让我们找回10年前前端修改单个文件的体验,只需要刷新就可以立即生效。刷新也省略,保存后生效。实现Bundleless的一个非常重要的基础能力就是模块的动态加载能力。对此主要有两个思路:像System.js这样的ES模块加载器具有兼容性高的优点。直接使用Web标准ESModule是面向未来的,整体架构更简单。兼容性在本地开发过程中的影响不是特别大。同时ESModule已经覆盖了90%以上的浏览器。我们可以利用ESModule的能力,让浏览器独立加载需要的模块,从而以更低的成本实现Bundleless,面向未来。最近一两年,社区出现了很多基于ESModule的开发工具,比如Vite、Snowpack、es-dev-server等,本文将主要分享Bundless本地开发的相关思路和核心技术点基于浏览器的ESModule能力,以及Vite的相关实现及其在供应链POS场景下的实现。2Bundle和Bundleless从资源加载的区别下面以大家最熟悉的create-react-app默认项目为例,从实际的页面渲染资源加载过程比较Bundle和Bundleless的区别。上图中基于Webpack的bundle开发模式的具体模块加载机制可以简化为下图:在项目启动时重新打包,当有文件变化时,使得项目启动和二次构建需要做更多的工作事情,相应的耗时也会增加。基于ESModuleBundleless模式,从上图可以看出,不再有构建好的bundle、chunk等文件,而是直接加载本地对应的文件。从上图可以看出,在Bundleless机制下,项目的启动只需要启动一个服务器来接受浏览器的请求即可。同时,当文件发生改变时,只需要额外处理改变的文件即可。其他文件可以直接从缓存中读取。比较总结Bundleless模式可以充分利用浏览器的自加载特性,跳过打包过程,让我们在项目启动时获得极快的启动速度,本地更新时只需要重新编译单个文件。下面将分享如何基于浏览器ESModule的能力实现Bundleless开发。3如何实现Bundleless如何使用ESModule模块加载实现Bundleless的第一步是让浏览器独立加载相应的模块。Usetype="module"toopenESModule

使用import-maps支持bareimport分享一个已经在chrome中实现的import-maps标准,让我们可以直接使用importReactfrom'react'。未来我们可以利用这个能力来实现线上的Bundleless部署。
上面我们介绍了浏览器中原生ESModule的使用方法。对于本地开发场景,我们只需要在本地启动一个devServer,将浏览器的请求映射到对应的本地文件,并将项目中导入的资源路径动态指向我们的本地地址,这样浏览器就可以直接加载本地比如可以使用如下的写法,将入口js文件直接指向本地路径,然后devServer拦截相应的请求,返回相应的文件。
如何加载非JS文件资源通过ESModule,我们借助浏览器的能力实现了JS的自加载,但是在实际的项目代码中,我们不仅会导入JS文件,还会有如下写法://main.jsximportReactfrom'react'importReactDOMfrom'react-dom'import'./index.css'//导入css文件importAppfrom'./App'//导入jsx文件//使用JSX语法ReactDOM.render(,document.getElementById('root'))而浏览器在处理文件时是基于Content-Type的,并不关心具体的文件类型,所以我们需要在浏览器发起请求的时候将对应的资源转成ESModule格式,并设置对应的Content-打成JS,返回给浏览器执行,浏览器会按照JS的语法进行解析处理。整体流程如下图所示:下面是Vite的相关实现。在请求返回的过程中,动态处理不同的文件:如何实现HotModuleReplaceHotModuleReplace可以在我们修改的时候修改代码最后不需要刷新页面,直接在当前场景生效。结合Bundleless的极速生效,我们可以实现几乎没有延迟的保存生效体验。对于React,在Webpack场景下只能使用react-hot-loader来实现,但这受限于具体的实现方式,在某些场景下可能会存在bug。作者还建议迁移到React团队实现的react-refresh。而这块目前还没有在Webpack中实现。在Bundleless场景下,因为我们每个组件都是独立加载的,所以要集成react-refresh,只需要在浏览器请求返回时在文件的顶部和底部添加相应的脚本即可完成集成。HotModuleReplace的完整实现会比上图复杂一些,需要一套依赖分析机制来确定替换哪些文件,文件发生变化时是否需要重新加载。在Bundleless场景下,因为不再需要打包成一个完整的bundle,而且我们也可以更灵活的修改单个文件,这样相关的实现会更简单。下面是Vite中的相关实现:如何优化大量请求导致页面加载慢Bundleless模式不再打包,提高启动速度,但是对于一些外部依赖较多或者自身大量依赖的模块自己的文件,需要发起大量的请求来获取所有的资源,这样会减少开发过程中的页面加载时间。比如在浏览器中直接导入lodash-es会发送大量请求:这方面我们可以做相应的优化,提前将外部依赖打包成一个文件,减少开发过程中过多的外部依赖。相反,发出了太多的网络请求。在Vite启动过程中,有一个vite优化过程,会借助Rollup自动将package.json中的依赖打包成一个ES6Module。除了提升页面加载速度,提前打包的好处,借助@rollup/plugin-commonjs,我们可以将commonjs的外部依赖打包为ESModule引入,进一步扩大了应用范围无捆绑。4.供应链POS场景实践。我们团队负责的供应链POS业务可以分为建材家居的家装行业和线下门店的零售行业。在技??术架构上,每个domainbundle都是独立开发的,最后借助底层sdk,合并成一个大的SPA形式。由于项目的复杂性,在日常开发过程中存在一些痛点:项目启动时间较长。改完后,第二次编译时间长。缺乏稳定的HMR能力,开发过程中需要重新创建场景。debug依赖于sourcemaps功能,有时它可能会变得不稳定。基于以上问题,借助Vite的相关实现,我们尝试并在本地开发环境中实现了Bundleless。在一些实验项目中,本地开发体验得到了很大的提升。大大提高了启动和修改的速度。目前已经实现了单一bundle维度的开发,打包构建速度:WebpackViteBundleless从上图可以看出。启动单个bundle时,Webpack大约需要10秒,而基于Bundleless的Vite只需要1秒左右,速度快了10倍。整体页面加载时间在4s左右,比起Webpack的打包构建时间还是要短的。同时从上面的视频可以看出,HMR的响应速度已经达到了毫秒级别,实现了保存生效的基本效果。不依赖sourcemap调试单个文件执行过程中遇到的问题,解决实际执行过程中遇到的问题。遇到的问题主要是相关模块不符合ESModule规范和一些书面规范:有些模块没有和ESModule一起打包。less取决于如何编写node_modules的规范。jsx文件扩展规范。由babel-runtime处理。部分模块打包时没有ESModule对于没有ESModule打包输出或者输出错误的包,根据不同的类型采用不同的策略:内部包:通过升级脚手架发布新版本的带有ESModule的包。外部依赖:通过issues、pullrequests等方式促进number-precision等模块的升级,同时一些由于历史原因无法导出为ESModule的包可以借助@rollup/打包为ESModule插件-commonjs。less依赖node_modules的规范@import'~@ali/pos-style-mixin/style/lst.less';//~webpack中只有less-loader支持,原生less不支持//统一迁移到followingmode@import'@ali/pos-style-mixin/style/lst.less';//同时在最初的webpack构建的less-laoder中配置lessOptions进行最终打包/*{loader:'less-loader',options:{lessOptions:{javascriptEnabled:true,paths:[path.resolve(cwd,'node_modules')],}}}*/JSX文件后缀规范Vite在运行过程中会根据文件的不同后缀进行相应的编译,并且在Webpack模式下,我们通常会将JSX、JS等文件丢给babel-loader处理,这使得一些原本是JSX的文件不写JSX后缀。Vite只会用esbuild编译/\.(tsx?|jsx)$/中的文件,纯JS会跳过esbuild过程。在这种情况下,我们逐渐将不是用JSX编写的错误文件迁移到JSX文件中。babel-runtime使用babel-plugin-transform-runtime处理后,打包输出如下:上面引用的@babel/runtime/helpers/extends是commonjs格式,不能直接使用。对于这种情况,有两种解决方案:1)对于内部打包的模块,可以在es6打包时添加useModules配置,这样打包后的代码会直接引用@babel/runtime/helpers/esm/extends
:2)重打包成本高的模块可以通过Vite的插件机制进行转换,在运行时可以将@babel/runtime/helpers替换为@babel/runtime/helpers/esm,可以通过alias配置来实现:以上开发于Vite环境迁移过程中遇到的一些问题及解决方案分享,这部分更大规模的实施还在进行中。Bundleless的实现,不仅是为了适应Vite的开发模式,也是为了以后规范各个模块的代码。将我们的模块标准化为ESModule,当新的工具和想法出现时,可以以更低的成本实现。降落。5、直接使用Bundleless部署的可行性受限于网络请求和浏览器的解析速度。对于更大的应用程序,bundle仍然可以在加载速度方面带来更大的好处。V8在2018年也给出了相关的性能建议:在本地开发和小型Web应用中使用。在如今的场景下,随着浏览器和网络性能的不断提升,结合ServiceWorker等缓存能力,网络负载的影响越来越小,对于一些不需要考虑兼容性问题的场景可以进行内部尝试,直接部署ESModule加载的代码。第六篇总结本文主要分享Bundleless架构下如何提升前端研发效率、具体业务场景下的实现思路和实践。Bundleless本质上是将原来Webpack中模块依赖解析的工作转移到浏览器去执行,减少了开发过程中的代码转换,大大提高了开发过程中的构建速度,也可以更好的利用浏览器。相关的开发工具。在当前背景下,Web各个领域的JavaScript/CSS/HTML相关标准已经成熟。同时,浏览器内核也趋于统一。前端工程的核心重心逐渐转移到研发效率上。它可以带来长期的启动和HMR速度,这是未来的一大发展趋势。随着浏览器内核和Web标准的不断统一,前端代码无需打包直接运行将成为可能,进一步提升整体研发效率。最后,非常感谢ESModule、Vite、Snowpack等标准和工具的出现,让前端开发体验有了很大的进步。