安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

冷启动4minampampgt2s的构建优化,怎么做到的?

4月24日 藏于心投稿
  大家好,我是Echa哥。项目背景
  我们的系统(一个ToB的Web单页应用)经过多年的迭代,目前已经累积有大几十万行的业务代码,30路由模块,整体的代码量和复杂度还是比较高的。
  项目整体是基于VueTypeScirpt,而构建工具,由于最早项目是经由vuecli初始化而来,所以自然而然使用的是Webpack。
  我们知道,随着项目体量越来越大,我们在开发阶段将项目跑起来,也就是通过npmrunserve的单次冷启动时间,以及在项目发布时候的npmrunbuild的耗时都会越来越久。
  因此,打包构建优化也是伴随项目的成长需要持续不断去做的事情。在早期,项目体量比较小的时,构建优化的效果可能还不太明显,而随着项目体量的增大,构建耗时逐渐增加,如何尽可能的降低构建时间,则显得越来越重要:大项目通常是团队内多人协同开发,单次开发时的冷启动时间的降低,乘上人数及天数,经年累月节省下来的时间非常可观,能较大程度的提升开发效率、提升开发体验大项目的发布构建的效率提升,能更好地保证项目发布、回滚等一系列操作的准确性、及时性
  本文,就将详细介绍整个我们项目,在随着项目体量不断增大的过程中,对整体的打包构建效率的优化之路。瓶颈分析
  再更具体一点,我们的项目最初是基于vuecli4,当时其基于的是webpack4版本。如无特殊说明,下文的一些配置会基于webpack4展开。
  工欲善其事必先利其器,解决问题前需要分析问题,要优化构建速度,首先得分析出Webpack构建编译我们的项目过程中,耗时所在,侧重点分布。
  这里,我们使用的是SMP插件,统计各模块耗时数据。
  speedmeasurewebpackplugin是一款统计webpack打包时间的插件,不仅可以分析总的打包时间,还能分析各阶段loader的耗时,并且可以输出一个文件用于永久化存储数据。安装npminstallsavedevspeedmeasurewebpackplugin使用方式constSpeedMeasurePluginrequire(speedmeasurewebpackplugin);constsmpnewSpeedMeasurePlugin();config。plugins。push(smp());开发阶段构建耗时
  对于npmrunserve,也就是开发阶段而言,在没有任何缓存的前提下,单次冷启动整个项目的时间达到了惊人的4min。
  生产阶段构建耗时
  而对于npmrunbuild,也就是实际线上生产环境的构建,看看总体的耗时:
  因此,对于构建效率的优化可谓是势在必行。首先,我们需要明确,优化分为两个方向:基于开发阶段npmrunserve的优化
  在开发阶段,我们的核心目标是在保有项目所有功能的前提下,尽可能提高构建速度,保证开发时的效率,所以对于Live才需要的一些功能,譬如代码混淆压缩、图片压缩等功能是可以不开启的,并且在开发阶段,我们需要热更新。基于生产阶段npmrunbuild的优化
  而在生产打包阶段,尽管构建速度也非常重要,但是一些在开发时可有可无的功能必须加上,譬如代码压缩、图片压缩。因此,生产构建的目标是在于保证最终项目打包体积尽可能小,所需要的相关功能尽可能完善的前提下,同时保有较快的构建速度。
  两者的目的不尽相同,因此一些构建优化手段可能仅在其中一个环节有效。
  基于上述的一些分析,本文将从如下几个方面探讨对构建效率优化的探索:基于Webpack的一些常见传统优化方式分模块构建基于Vite的构建工具切换基于Esbuild插件的构建效率优化为什么这么慢?
  那么,为什么随着项目的增大,构建的效率变得越来越慢了呢?
  从上面两张截图不难看出,对于我们这样一个单页应用,构建过程中的大部分时间都消耗在编译JavaScript文件及CSS文件的各类Loader上。
  本文不会详细描述Webpack的构建原理,我们只需要大致知道,Webpack的构建流程,主要时间花费在递归遍历各个入口文件,并基于入口文件不断寻找依赖逐个编译再递归处理的过程,每次递归都需要经历StringASTString的流程,然后通过不同的loader处理一些字符串或者执行一些JavaScript脚本,由于NodeJS单线程的特性以及语言本身的效率限制,Webpack构建慢一直成为它饱受诟病的原因。
  因此,基于上述Webpack构建的流程及提到的一些问题,整体的优化方向就变成了:缓存多进程寻路优化抽离拆分构建工具替换基于Webpack的传统优化方式
  上面也说了,构建过程中的大部分时间都消耗在递归地去编译JavaScript及CSS的各类Loader上,并且会受限于NodeJS单线程的特性以及语言本身的效率限制。
  如果不替换掉Webpack本身,语言本身(NodeJS)的执行效率是没法优化的,只能在其他几个点做文章。
  因此在最早期,我们所做的都是一些比较常规的优化手段,这里简单介绍最为核心的几个:缓存多进程寻址优化缓存优化
  其实对于vuecli4而言,已经内置了一些缓存操作,譬如上图可见到loader的过程中,有使用cacheloader,所以我们并不需要再次添加到项目之中。cacheloader:在一些性能开销较大的loader之前添加cacheloader,以便将结果缓存到磁盘里
  那还有没有一些其他的缓存操作呢用上的呢?我们使用了一个HardSourceWebpackPlugin。HardSourceWebpackPluginHardSourceWebpackPlugin:HardSourceWebpackPlugin为模块提供中间缓存,缓存默认存放的路径是nodemodules。cachehardsource,配置了HardSourceWebpackPlugin之后,首次构建时间并没有太大的变化,但是第二次开始,构建时间将会大大的加快。
  首先安装依赖:npminstallhardsourcewebpackpluginD
  修改vue。config。js配置文件:constHardSourceWebpackPluginrequire(hardsourcewebpackplugin);module。exports{。。。configureWebpack:(config){。。。config。plugins。push(newHardSourceWebpackPlugin());},。。。}
  配置了HardSourceWebpackPlugin的首次构建时间,和预期的一样,并没有太大的变化,但是第二次构建从平均4min左右降到了平均20s左右,提升的幅度非常的夸张,当然,这个也因项目而异,但是整体而言,在不同项目中实测发现它都能比较大的提升开发时二次编译的效率。设置babelloader的cacheDirectory以及DLL
  另外,在缓存方面我们的尝试有:设置babelloader的cacheDirectoryDLL
  但是整体收效都不太大,可以简单讲讲。
  打开babelloader的cacheDirectory的配置,当有设置时,指定的目录将用来缓存loader的执行结果。之后的webpack构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的Babel重新编译过程。实际的操作步骤,你可以看看Webpackbabelloader。
  那么DLL又是什么呢?
  DLL文件为动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据。
  为什么要用DLL?
  原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。
  由于动态链接库中大多数包含的是常用的第三方模块,例如Vue、React、Reactdom,只要不升级这些模块的版本,动态链接库就不用重新编译。
  DLL的配置非常繁琐,并且最终收效甚微,我们在过程中借助了autodllwebpackplugin,感兴趣的可以自行尝试。值得一提的是,Vuecli已经剔除了这个功能。多进程
  基于NodeJS单线程的特性,当有多个任务同时存在,它们也只能排队串行执行。
  而如今大多数CPU都是多核的,因此我们可以借助一些工具,充分释放CPU在多核并发方面的优势,利用多核优势,多进程同时处理任务。
  从上图中可以看到,VueCLi4中,其实已经内置了threadloader。threadloader:把threadloader放置在其它loader之前,那么放置在这个loader之后的loader就会在一个单独的worker池中运行。这样做的好处是把原本需要串行执行的任务并行执行。
  那么,除了threadloader,还有哪些可以考虑的方案呢?HappyPack
  HappyPack与threadloader类似。
  HappyPack可利用多进程对文件进行打包,将任务分解给多个子进程去并行执行,子进程处理完后,再把结果发送给主进程,达到并行打包的效、HappyPack并是所有的loader都支持,比如vueloader就不支持。
  可以通过LoaderCompatibilityList来查看支持的loaders。需要注意的是,创建子进程和主进程之间的通信是有开销的,当你的loader很慢的时候,可以加上happypack。否则,可能会编译的更慢。
  当然,由于HappyPack作者对JavaScript的兴趣逐步丢失,维护变少,webpack4及之后都更推荐使用threadloader。因此,这里没有实际结论给出。
  上一次HappyPack更新已经是3年前寻址优化
  对于寻址优化,总体而言提升并不是很大。
  它的核心即在于,合理设置loader的exclude和include属性。通过配置loader的exclude选项,告诉对应的loader可以忽略某个目录通过配置loader的include选项,告诉loader只需要处理指定的目录,loader处理的文件越少,执行速度就会更快
  这肯定是有用的优化手段,只是对于一些大型项目而言,这类优化对整体构建时间的优化不会特别明显。分模块构建
  在上述的一些常规优化完成后。整体效果仍旧不是特别明显,因此,我们开始思考一些其它方向。
  我们再来看看Webpack构建的整体流程:
  上图是大致的webpack构建流程,简单介绍一下:entryoption:读取webpack配置,调用newCompile(config)函数准备编译run:开始编译make:从入口开始分析依赖,对依赖模块进行buildbeforeresolve:对位置模块进行解析buildmodule:开始构建模块normalmoduleloader:生成AST树program:遍历AST树,遇到require语句收集依赖seal:build完成开始优化emit:输出dist目录
  随着项目体量地不断增大,耗时大头消耗在第7步,递归遍历AST,解析require,如此反复直到遍历完整个项目。
  而有意思的是,对于单次单个开发而言,极大概率只是基于这整个大项目的某一小个模块进行开发即可。
  所以,如果我们可以在收集依赖的时候,跳过我们本次不需要的模块,或者可以自行选择,只构建必要的模块,那么整体的构建时间就可以大大减少。
  这也就是我们要做的分模块构建。
  什么意思呢?举个栗子,假设我们的项目一共有6个大的路由模块A、B、C、D、E、F,当新需求只需要在A模块范围内进行优化新增,那么我们在开发阶段启动整个项目的时候,可以跳过B、C、D、E、F这5个模块,只构建A模块即可:
  假设原本每个模块的构建平均耗时3s,原本18s的整体冷启动构建耗时就能下降到3s。分模块构建打包的原理
  Webpack是静态编译打包的,Webpack在收集依赖时会去分析代码中的require(import会被bebel编译成require)语句,然后递归的去收集依赖进行打包构建。
  我们要做的,就是通过增加一些配置,简单改造下我们的现有代码,使得Webpack在初始化遍历整个路由模块收集依赖的时候,可以跳过我们不需要的模块。
  再说得详细点,假设我们的路由大致代码如下:importVimportVueRouter,{Route}1。定义路由组件。这里简化下模型,实际项目中肯定是一个一个的大路由模块,从其他文件导入constmoduleA{template:AAAA}constmoduleB{template:BBBB}constmoduleC{template:CCCC}constmoduleD{template:DDDD}constmoduleE{template:EEEE}constmoduleF{template:FFFF}2。定义一些路由每个路由都需要映射到一个组件。我们后面再讨论嵌套路由。constroutesConfig〔{path:A,component:moduleA},{path:B,component:moduleB},{path:C,component:moduleC},{path:D,component:moduleD},{path:E,component:moduleE},{path:F,component:moduleF}〕constrouternewVueRouter({mode:history,routes:routesConfig,});让路由生效。。。constappVue。createApp({})app。use(router)
  我们要做的,就是每次启动项目时,可以通过一个前置命令行脚本,收集本次需要启动的模块,按需生成需要的routesConfig即可。
  我们尝试了:IgnorePlugin插件webpackvirtualmodules配合require。contextNormalModuleReplacementPlugin插件进行文件替换
  最终选择了使用NormalModuleReplacementPlugin插件进行文件替换的方式,原因在于它对整个项目的侵入性非常小,只需要添加前置脚本及修改Webpack配置,无需改变任何路由文件代码。总结而言,该方案的两点优势在于:无需改动上层代码通过生成临时路由文件的方式,替换原路由文件,对项目无任何影响使用NormalModuleReplacementPlugin生成新的路由配置文件
  利用NormalModuleReplacementPlugin插件,可以不修改原来的路由配置文件,在编译阶段根据配置生成一个新的路由配置文件然后去使用它,这样做的好处在于对整个源码没有侵入性。
  NormalModuleReplacementPlugin插件的作用在于,将目标源文件的内容替换为我们自己的内容。
  我们简单修改Webpack配置,如果当前是开发环境,利用该插件,将原本的config。ts文件,替换为另外一份,代码如下:vue。config。jsif(process。env。NODEENVdevelopment){config。plugins。push(newwebpack。NormalModuleReplacementPlugin(srcrouterconfig。ts,。。。。dev。routerConfig。ts))}
  上面的代码功能是将实际使用的config。ts替换为自定义配置的dev。routerConfig。ts文件,那么dev。routerConfig。ts文件的内容又是如何产生的呢,其实就是借助了inquirer与EJS模板引擎,通过一个交互式的命令行问答,选取需要的模块,基于选择的内容,动态的生成新的dev。routerConfig。ts代码,这里直接上代码。
  改造一下我们的启动脚本,在执行vuecliserviceserve前,先跑一段我们的前置脚本:{。。。scripts:{dev:vuecliserviceserve,dev:node。scriptdevserver。jsvuecliserviceserve,},。。。}
  而devserver。js所需要做的事,就是通过inquirer实现一个交互式命令,用户选择本次需要启动的模块列表,通过ejs生成一份新的dev。routerConfig。ts文件。devserver。jsconstejsrequire(ejs);constfsrequire(fs);constchildprocessrequire(childprocess);constinquirerrequire(inquirer);constpathrequire(path);constmoduleConfig〔moduleA,moduleB,moduleC,实际业务中的所有模块〕选中的模块constchooseModules〔home〕functiondeelRouteName(name){constindexname。search(〔AZ〕g);constpreRoutepath。resolve(dirname,。。srcroutermodules);if(!〔0,1〕。includes(index)){returnpreRoute(name。slice(0,index)name。slice(index))。toLowerCase();}returnpreRoutename。toLowerCase();;}functioninit(){letentryDirprocess。argv。slice(2);entryDir〔。。。newSet(entryDir)〕;if(entryDirentryDir。length0){for(constitemofentryDir){if(moduleConfig。includes(item)){chooseModules。push(item);}}console。log(output:,chooseModules);runDEV();}else{promptModule();}}constgetContenTemplateasync(){consthtmlawaitejs。renderFile(path。resolve(dirname,router。config。template。ejs),{chooseModules,deelRouteName},{async:true});fs。writeFileSync(path。resolve(dirname,。。dev。routerConfig。ts),html);};functionpromptModule(){inquirer。prompt({type:checkbox,name:modules,message:请选择启动的模块,点击上下键选择,按空格键确认(可以多选),回车运行。注意:直接敲击回车会全量编译,速度较慢。,pageSize:15,choices:moduleConfig。map((item){return{name:item,value:item,}})})。then((answers){if(answers。modules。length0){chooseModules。push(。。。moduleConfig)}else{chooseModules。push(。。。answers。modules)}runDEV();});}init();
  模板代码的简单示意:模板代码示意,router。config。template。ejsimport{RouteConfig}chooseModules。forEach(function(item){deelRouteName(item);})letroutesConfig:ArrayRouteConfig〔〕;eslintdisableroutesConfig〔chooseModules。forEach(function(item){item,})〕exportdefaultroutesC
  devserver。js的核心在于启动一个inquirer交互命令行服务,让用户选择需要构建的模块,类似于这样:
  模板代码示意router。config。template。ejs是EJS模板文件,chooseModules是我们在终端输入时,获取到的用户选择的模块集合数组,根据这个列表,我们去生成新的routesConfig文件。
  这样,我们就实现了分模块构建,按需进行依赖收集。以我们的项目为例,我们的整个项目大概有20个不同的模块,几十万行代码:
  构建模块数耗时冷启动全量构建20个模块4。5MIN冷启动只构建1个模块18s有缓存状态下二次构建1个模块4。5s
  实际效果大致如下,无需启动所有模块,只启动我们选中的模块进行对应的开发即可:
  这样,如果单次开发只涉及固定的模块,单次项目冷启动的时间,可以从原本的4min下降到18s左右,而有缓存状态下二次构建1个模块,仅仅需要4。5s,属于一个比较大的提升。
  受限于Webpack所使用的语言的性能瓶颈,要追求更快的构建性能,我们不可避免的需要把目光放在其他构建工具上。这里,我们的目光聚焦在了Vite与esbuild上。使用Vite优化开发时构建
  Vite,一个基于浏览器原生ES模块的开发服务器。利用浏览器去解析imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有Vue文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。
  当然,由于Vite本身特性的限制,目前只适用于在开发阶段替代Webpack。
  我们都知道Vite非常快,它主要快在什么地方?项目冷启动更快热更新更快
  那么是什么让它这么快?Webpack与Vite冷启动的区别
  我们先来看看Webpack与Vite的在构建上的区别。下图是Webpack的遍历递归收集依赖的过程:
  上文我们也讲了,Webpack启动时,从入口文件出发,调用所有配置的Loader对模块进行编译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  这一过程是非常非常耗时的,再看看Vite:
  Vite通过在一开始将应用中的模块区分为依赖和源码两类,改进了开发服务器启动时间。它快的核心在于两点:使用Go语言的依赖预构建:Vite将会使用esbuild进行预构建依赖。esbuild使用Go编写,并且比以JavaScript编写的打包器预构建依赖快10100倍。依赖预构建主要做了什么呢?开发阶段中,Vite的开发服务器将所有代码视为原生ES模块。因此,Vite必须先将作为CommonJS或UMD发布的依赖项转换为ESMVite将有许多内部模块的ESM依赖关系转换为单个模块,以提高后续页面加载性能。如果不编译,每个依赖包里面都可能含有多个其他的依赖,每个引入的依赖都会又一个请求,请求多了耗时就多按需编译返回:Vite以原生ESM方式提供源码。这实际上是让浏览器接管了打包程序的部分工作:Vite只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。Webpack与Vite热更新的区别
  使用Vite的另外一个大的好处在于,它的热更新也是非常迅速的。
  我们首先来看看Webpack的热更新机制:
  一些名词解释:Webpackcomplier:Webpack的编译器,将Javascript编译成bundle(就是最终的输出文件)HMRServer:将热更新的文件输出给HMRRuntimeBunbleServer:提供文件在浏览器的访问,也就是我们平时能够正常通过localhost访问我们本地网站的原因HMRRuntime:开启了热更新的话,在打包阶段会被注入到浏览器中的bundle。js,这样bundle。js就可以跟服务器建立连接,通常是使用Websocket,当收到服务器的更新指令的时候,就去更新文件的变化bundle。js:构建输出的文件
  Webpack热更新的大致原理是,文件经过Webpackcomplier编译好后传输给HMRServer,HMRServer知道哪个资源(模块)发生了改变,并通知HMRRuntime有哪些变化,HMRRuntime就会更新我们的代码,这样浏览器就会更新并且不需要刷新。
  而Webpack热更新机制主要耗时点在于,Webpack的热更新会以当前修改的文件为入口重新build打包,所有涉及到的依赖也都会被重新加载一次。
  而Vite号称热更新的速度不会随着模块增多而变慢。它的主要优化点在哪呢?
  Vite实现热更新的方式与Webpack大同小异,也通过创建WebSocket建立浏览器与服务器建立通信,通过监听文件的改变向客户端发出消息,客户端对应不同的文件进行不同的操作的更新。
  Vite通过chokidar来监听文件系统的变更,只用对发生变更的模块重新加载,只需要精确的使相关模块与其临近的HMR边界连接失效即可,这样HMR更新速度就不会因为应用体积的增加而变慢而Webpack还要经历一次打包构建。所以HMR场景下,Vite表现也要好于Webpack。
  通过不同的消息触发一些事件。做到浏览器端的即时热模块更换(热更新)。通过不同事件,触发更细粒度的更新(目前只有Vue和JS,Vue文件又包含了template、script、style的改动),做到只更新必须的文件,而不是全量进行更新。在些事件分别是:connected:WebSocket连接成功vuereload:Vue组件重新加载(当修改了script里的内容时)vuererender:Vue组件重新渲染(当修改了template里的内容时)styleupdate:样式更新styleremove:样式移除jsupdate:js文件更新fullreload:fallback机制,网页重刷新
  本文不会在Vite原理上做太多深入,感兴趣的可以通过官方文档了解更多Vite官方文档为什么选Vite
  基于Vite的改造,相当于在开发阶段替换掉Webpack,下文主要讲讲我们在替换过程中遇到的一些问题。
  基于Vuecli4的Vue2项目改造,大致只需要:安装Vite配置index。html(Vite解析
投诉 评论 转载

杀人诛心!华为余承东建议淘汰纯燃油车,网友建议淘汰4G手机大家都知道,最近几年,国内新能源汽车虽然持续发展,表现出了很强势的进击态势,但是总体来说,传统燃油车的势力依然非常强势,2022年国内新能源汽车发展如此迅猛,渗透率最高也没有超……印度外长印俄合作关系对我们有利,会继续购买俄罗斯石油据今日俄罗斯电视台(RT)网站8日报道,周二在与俄罗斯外长拉夫罗夫举行的联合新闻发布会上,印度外交部长苏杰生表示,印度正在寻求与俄罗斯建立稳定的能源合作关系,并将继续购买俄罗斯……咽喉肿痛如何快速解决?中医有方法,别再硬抗咽喉肿痛是生活中的常见的症状,虽不是大病,但却让人十分痛苦,影响日常生活。下面就讲讲缓解咽喉肿痛以及辨清症状虚实的方法。咽喉肿痛的病因本病以咽喉红肿疼痛、吞咽……中超4消息!中超积分榜更新,武磊首秀低迷,深圳队换帅如换刀第1个消息来自于中超积分榜。刚刚结束的中超第16轮补赛,有着多场焦点大战,其中武汉三镇以21惊险击败长春亚泰,上海申花则是10小胜梅州客家,河南嵩山龙门12不敌海港,深圳队30……英媒评7大退役后发财的球星传奇入选,孙继海和李铁前队友在列北京时间7月16日,英媒《星报》评选出7名退役之后,通过其他方式最终发财的前英超球员。这其中包括了孙继海以及李铁的前队友格拉维森,此外还有在利物浦效力过的福勒和迈克尔欧文,海港……从一夜爆火到无戏可拍,演员张一山到底经历了什么?头条创作挑战赛说起张一山,相信大家首先想起的是2005年爆火的国产情景《家有儿女》,该剧一经播出,瞬间打破了电视台的收视记录,而剧中的一众主演,也全都一夜爆红,成为了国民级的演……曾经火爆的上下九,你多少年没去了如果不是看到上下九最新的改造方案,阿拆几乎都快忘记了这个地方。这条充满了西关风情,曾经是来广州游玩必去的地方之一的岭南第一街,不仅游客越来越少,连被遗忘的速度,也越来越快……待就业!盘点自由市场的全明星球员,三名湖人旧将上榜NBA季前赛已经开始两天了,不少球队也已经打了季前赛的比赛,这也就意味着距离新赛季开启的时间越来越近,早些时候凯尔特人被曝出与格里芬达成一年合同。只不过目前球队方面还没有官宣签……仅因小行星撞击地球所致?地大等单位研究发现,恐龙灭绝可能还另湖北日报讯(记者张歆、通讯员王俊芳、陈华文)恐龙是地球上最广为人知的古生物之一。恐龙为何在6600万年前白垩纪末期突然消失,小行星撞击假说一直是主流观点。近日,我国科学家根据研……欧元的昨天今天和明天2022年,距离2002年7月欧元正式成为欧元区唯一合法货币,已经过去二十年了。在过去的二十年中,欧元对美元汇率基本上维持在1以上。但是,今年以来,美元走强,欧元开始大幅……经常刷头条的苹果用户注意了手机发热耗电元凶找到了引言经常喜欢刷《今日头条》的用户,对头条简直爱不释手,头条的算法太厉害了,知道你喜欢什么,然后投你所好,会让你一直阅读下去,小编比较闲,平均每天花在头条上的时间差不多有3……冷启动4minampampgt2s的构建优化,怎么做到的?大家好,我是Echa哥。项目背景我们的系统(一个ToB的Web单页应用)经过多年的迭代,目前已经累积有大几十万行的业务代码,30路由模块,整体的代码量和复杂度还是比较高的……
突发!韩国队遭制裁!无缘奥运会,中国男篮这次稳了姚明笑了不走了!曝CBA传奇外援要再打一年,能否在辽宁男篮退役?上线仅4天,已定上网飞,杨洋首演古偶剧,凭啥这么横?张柏芝偶遇王菲,大儿子急中生智化解尴尬的气氛?太尴尬了咩咩启示录评测救世,唯有真主K50至尊版对比K50Pro,起售价相同,配置差在哪里,一眼德普在法庭上说人无完人,但我肯定没做过希尔德所说的虐待之事杨幂又带火了一件衣服叫方块衫,时髦显瘦,随意搭都好看DNF110机械战神版本可以淘汰神话了?初次入园不哭不闹,很快就适应的孩子,父母3点入园准备功不可没全新神秘皮肤来袭!一勇者一史诗,还能免费获取?天游良心要打美网?德约科维奇回应克耶高斯晚餐邀约在纽约实现它

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利