<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          埋點(diǎn)自動(dòng)收集方案-路由依賴分析

          共 10873字,需瀏覽 22分鐘

           ·

          2020-12-26 23:10

          1.一個(gè)項(xiàng)目總共有多少組件?每個(gè)頁(yè)面又有多少組件構(gòu)成?

          2.有哪些組件是公共組件,它們分別被哪些頁(yè)面引用?

          對(duì)于這兩個(gè)問題,我們先思考一會(huì)。sleep……

          跟隨這篇文章我們一起探討下,希望能幫你找到答案。

          • 背景

          隨著組件化思想深入人心,開發(fā)中遇到特定的功能模塊或UI模塊,我們便會(huì)想到抽成組件,高級(jí)一點(diǎn)的做法就是把多個(gè)頁(yè)面相似的部分抽成公共的組件。

          組件化的“詛咒”

          但是往往對(duì)一件事物依賴越強(qiáng),越容易陷入它的“詛咒”當(dāng)中。當(dāng)項(xiàng)目有越多的組件時(shí),開發(fā)者越不容易建立它們之間的關(guān)系,特別當(dāng)改動(dòng)了某個(gè)組件的一行代碼,甚至不能準(zhǔn)確的判斷由于這行代碼變動(dòng),都影響了哪些頁(yè)面。我暫且稱之為“組件化的詛咒”。如果我們有個(gè)完整的組件依賴關(guān)系,就可以很好的解決這個(gè)問題。

          我們以下面的場(chǎng)景為例,看一看依賴分析的重要性和必要性。

          通過前一篇文章,想必大家對(duì)埋點(diǎn)自動(dòng)收集方案有了宏觀且全面的了解。在這里再簡(jiǎn)單概述下:

          埋點(diǎn)自動(dòng)收集方案是基于jsdoc對(duì)注釋信息的搜集能力,通過給路由頁(yè)面中所有埋點(diǎn)增加注釋的方式,在編譯時(shí)建立起頁(yè)面和埋點(diǎn)信息的對(duì)應(yīng)關(guān)系。

          點(diǎn)擊查看《埋點(diǎn)自動(dòng)收集方案-概述》

          在整個(gè)方案中,埋點(diǎn)的數(shù)據(jù)源很重要,而數(shù)據(jù)源與頁(yè)面的對(duì)應(yīng)關(guān)系又是保證數(shù)據(jù)源完整性的關(guān)鍵。比如:首頁(yè)和個(gè)人主頁(yè)的商品流都采用相同的商品卡片,開發(fā)者自然會(huì)將商品卡片抽離為一個(gè)公共組件。如下:

          //Index.vue?首頁(yè)
          import?Card?from?'./common/Card.vue'?//依賴商品卡片組件

          //Home.vue?個(gè)人主頁(yè)
          import?Card?from?'./common/Card.vue'?//依賴商品卡片組件

          //Card.vue?商品卡片組件
          goDetail(item)?{
          ????/**
          ????*?@mylog?商品卡片點(diǎn)擊
          ????*/
          ????this.$log('card-click')?//?埋點(diǎn)發(fā)送
          }

          這就帶來一個(gè)問題:商品卡片的點(diǎn)擊信息(埋點(diǎn)的數(shù)據(jù)源),既可能是首頁(yè)的,也可能是個(gè)人主頁(yè)的,而jsdoc搜集埋點(diǎn)注釋時(shí),對(duì)這種歸屬情況的判斷無能為力。所以必須找到一種方法可以拿到組件和頁(yè)面的映射關(guān)系。

          • 期望效果

          項(xiàng)目中的實(shí)際依賴關(guān)系:


          對(duì)應(yīng)的依賴分析關(guān)系:(每個(gè)組件,與引用它的頁(yè)面路由的映射)


          • 方案思考

          那么,怎么做依賴分析?在思考這個(gè)問題之前,我們先看一看有哪些常見的建立依賴的語(yǔ)法。

          //a.ts
          import?B?from?'./b.ts'
          import?getCookie?from?'@/libs/cookie.ts'

          //c.ts
          const?C?=?require('./b.ts')

          //b.ts
          div?{
          ????background:?url('./assets/icon.png')?no-repeat;
          }
          import?'./style.css'
          //?c.vue
          import?Vue?from?Vue
          import?Card?from?'@/component/Card.vue'

          這里給出三種依賴分析的思路:

          1 遞歸解析

          從項(xiàng)目的路由配置文件開始,分別對(duì)每個(gè)路由頁(yè)面,進(jìn)行依賴的遞歸解析。這種思路想法簡(jiǎn)單直接,但實(shí)現(xiàn)起來可能較為繁瑣,需要解析頁(yè)面中所有形式的依賴關(guān)系。

          2 借助webpack工具的統(tǒng)計(jì)分析數(shù)據(jù),進(jìn)行二次加工

          實(shí)際項(xiàng)目中我們都是采用webpack打包工具,而它的一大特點(diǎn)就是會(huì)自動(dòng)幫開發(fā)者做依賴分析(獨(dú)立的enhanced-resolve庫(kù))。相較于第一種重寫解析的方法,為何不站在webpack的肩膀上解決問題呢。

          先來看下webpack的整體編譯流程:


          可以看到,每一個(gè)文件都會(huì)經(jīng)過resolve階段,最終在編譯結(jié)束后,得到本次編譯的統(tǒng)計(jì)分析信息。

          //done是compiler的鉤子,在完成一次編譯結(jié)束后的會(huì)執(zhí)行
          compiler.hooks.done.tapAsync("demoPlugin",(stats,cb)=>{
          ??fs.writeFile(appRoot+'/stats.json',?JSON.stringify(stats.toJson(),'','\t'),?(err)?=>?{
          ??????if?(err)?{
          ??????????throw?err;
          ??????}
          ??})
          ??cb()
          })

          詳細(xì)的編譯數(shù)據(jù),就是done事件中的回調(diào)參數(shù)stats,經(jīng)過處理后,大致如下:


          通過對(duì)這份統(tǒng)計(jì)分析信息的二次加工和分析,也可以得到預(yù)期的依賴關(guān)系(插件webpack-bundle-analyzer也是基于這份數(shù)據(jù)生成的分析圖表)。這份數(shù)據(jù)看上去更像基本chunk和module的依賴分析,對(duì)于組件或公共組件的依賴關(guān)系問題,需要對(duì)chunks和modules綜合分析才能解決。同時(shí)我們還發(fā)現(xiàn),這份數(shù)據(jù)的數(shù)據(jù)量相當(dāng)大,且有大量開發(fā)者不關(guān)心的數(shù)據(jù)(截圖是只有兩個(gè)路由頁(yè)面的情況下的數(shù)據(jù)量)。接下來討論的方案是作者實(shí)際采用的方案,也是基于webpack,不同之處在于分析和收集依賴關(guān)系的時(shí)機(jī)

          3 在webpack的解析階段,分析并收集依賴

          我們看到雖然webpack的分析數(shù)據(jù)非常臃腫,但是它確實(shí)幫助開發(fā)者做了這份繁重的工作。只是我們希望能定制數(shù)據(jù)的范圍主動(dòng)收集期望數(shù)據(jù),所以推想,可否在每個(gè)文件解析階段進(jìn)行一定的“干預(yù)”,即通過條件判斷或過濾篩選達(dá)成目的。那么問題來了,應(yīng)該在resolve的哪個(gè)階段進(jìn)行“干預(yù)”,如何“干預(yù)”?

          好,我們先要總覽下webpack事件流過程:


          很顯然,afterResolve是每個(gè)文件解析階段的最后,應(yīng)該就從這里下手啦。

          • 具體實(shí)現(xiàn)

          先奉上流程圖


          1 初始化

          首先這是一個(gè)webpack插件,在初始化階段,指定解析的路由文件地址(比如src/route)以及排除解析的文件地址(比如src/lib、src/util),原因是這些排除的文件不會(huì)存在埋點(diǎn)數(shù)據(jù)。

          2 收集依賴關(guān)系

          在afterResolve鉤子函數(shù)中,獲取當(dāng)前被解析文件的路徑及其父級(jí)文件路徑。

          apply(compiler)?{
          ??compiler.hooks.normalModuleFactory.tap(
          ????"demoPlugin",
          ????nmf?=>?{
          ??????nmf.hooks.afterResolve.tapAsync(
          ????????"demoPlugin",
          ????????(result,?callback)?=>?{
          ??????????const?{?resourceResolveData?}?=?result;
          ??????????//?當(dāng)前文件的路徑
          ??????????let?path?=?resourceResolveData.path;?
          ??????????//?父級(jí)文件路徑
          ??????????let?fatherPath?=?resourceResolveData.context.issuer;?
          ??????????callback(null,result)
          ????????}
          ??????);
          ????}
          ??)
          }

          3 建立依賴樹

          根據(jù)上一步獲取的引用關(guān)系,生成依賴樹

          //?不是nodemodule中的文件,不是exclude中的文件,且為.js/.jsx/.ts/.tsx/.vue
          if(!skip(this.ignoreDependenciesArr,this.excludeRegArr,path,?fatherPath)?&&?matchFileType(path)){?
          ??if(fatherPath?&&?fatherPath?!=?path){?//?父子路徑相同的排除
          ????if(!(fatherPath.endsWith('js')?||?fatherPath.endsWith('ts'))?||?!(path.endsWith('js')?||?path.endsWith('ts'))){?
          ??????//?父子同為js文件,認(rèn)為是路由文件的父子關(guān)系,而非組件,故排除
          ??????let?sonObj?=?{};
          ??????sonObj.type?=?'module';
          ??????sonObj.path?=?path;
          ??????sonObj.deps?=?[]
          ??????//?如果本次parser中的path,解析過,那么把過去的解析結(jié)果copy過來。
          ??????sonObj?=?copyAheadDep(this.dependenciesArray,sonObj);
          ??????let?obj?=?checkExist(this.dependenciesArray,fatherPath,sonObj);
          ??????this.dependenciesArray?=?obj.arr;
          ??????if(!obj.fileExist){
          ????????let?entryObj?=?{type:'module',path:fatherPath,deps:[sonObj]};
          ????????this.dependenciesArray.push(entryObj);
          ??????}
          ????}
          }?else?if(!this.dependenciesArray.some(it?=>?it.path?==?path))?{
          //?父子路徑相同,且在this.dependenciesArray不存在,認(rèn)為此文件為依賴樹的根文件
          ????let?entryObj?=?{type:'entry',path:path,deps:[]};
          ????this.dependenciesArray.push(entryObj);
          ??}
          }

          那么這時(shí)生成的依賴樹如下:


          4 解析路由信息

          通過上一步基本上得到組件的依賴樹,但我們發(fā)現(xiàn)對(duì)于公共組件Card,它只存在首頁(yè)的依賴中,卻不見在個(gè)人主頁(yè)的依賴中,這顯然不符合預(yù)期(在第6步中專門解釋)。那么接下來就要找尋,這個(gè)依賴樹與路由信息的關(guān)系。

          compiler.hooks.done.tapAsync("RoutePathWebpackPlugin",(stats,cb)=>{
          ??this.handleCompilerDone()
          ??cb()
          })
          //?ast解析路由文件
          handleCompilerDone(){
          ??if(this.dependenciesArray.length){
          ????let?tempRouteDeps?=?{};
          ????//?routePaths是項(xiàng)目的路由文件數(shù)組
          ????for(let?i?=?0;?i?????????let?code?=?fs.readFileSync(this.routePaths[i],'utf-8');
          ????????const?tsParsedScript?=?ts.transpileModule(code,?{?compilerOptions:?{target:?'ES6'?}});
          ????????code?=?tsParsedScript.outputText;
          ????????let?ast?=?Parser.parse(code,{'sourceType':'module',ecmaVersion:11});
          ????????const?walk?=?inject(acornWalk);
          ????????let?that?=?this;
          ????????walk.ancestor(ast,{
          ????????????Literal(_,?ancestors)?{
          ????????????????//?以下操作為獲取單獨(dú)的route配置文件中,name和頁(yè)面的映射關(guān)系
          ????????????????……
          ????????????????}
          ????????????}
          ????????})
          ????}
          ????//?合并多個(gè)路由文件的映射關(guān)系
          ????let?tempDeps?=?[]
          ????for(let?arr?of?Object.values(tempRouteDeps)){
          ????????tempDeps?=?tempDeps.concat(arr)
          ????}
          ????this.routeDeps?=?tempDeps.filter(it=>it?&&?Object.prototype.toString.call(it)?==?"[object?Object]"?&&?it.components);
          ????//?獲取真實(shí)插件傳入的router配置文件的依賴,除去main.js、filter.js、store.js等文件的依賴
          ????this.dependenciesArray?=?
          ????getRealRoutePathDependenciesArr(this.dependenciesArray,this.routePaths);
          ??}
          }

          通過這一步ast解析,可以得到如下路由信息:

          [
          ??{
          ????"name":?"index",
          ????"route":?"/index",
          ????"title":?"首頁(yè)",
          ????"components":?["../view/newCycle/index.vue"]
          ??},
          ??{
          ????"name":?"home",
          ????"route":?"/home",
          ????"title":?"個(gè)人主頁(yè)",
          ????"components":?["../view/newCycle/home.vue"]
          ??}
          ]

          5 對(duì)依賴樹和路由信息進(jìn)行整合分析

          //?將路由頁(yè)面的所有依賴組件deps,都存放在路由信息的components數(shù)組中
          const?getEndPathComponentsArr?=?function(routeDeps,dependenciesArray)?{
          ??for(let?i?=?0;?i?????let?pageArr?=?dependenciesArray[i].deps;
          ????pageArr.forEach(page=>{
          ??????routeDeps?=?routeDeps.map(routeObj=>{
          ????????if(routeObj?&&?routeObj.components){
          ??????????let?relativePath?=?
          ??????????routeObj.components[0].slice(routeObj.components[0].indexOf('/')+1);
          ??????????if(page.path.includes(relativePath.split('/').join(path.sep))){
          ????????????//?鋪平依賴樹的層級(jí)
          ????????????routeObj?=?flapAllComponents(routeObj,page);
          ????????????//?去重操作
          ????????????routeObj.components?=?dedupe(routeObj.components);
          ??????????}
          ????????}
          ????????return?routeObj;
          ??????})
          ????})
          ??}
          ??return?routeDeps;
          }
          //建立一個(gè)map數(shù)據(jù)結(jié)構(gòu),以每個(gè)組件為key,以對(duì)應(yīng)的路由信息為value
          //??{
          //????'path1'?=>?Set?{?'/index'?},
          //????'path2'?=>?Set?{?'/index',?'/home'?},
          //????'path3'?=>?Set?{?'/home'?}
          //??}
          const?convertDeps?=?function(deps)?{
          ????let?map?=?new?Map();
          ????......
          ????return?map;
          }

          整合分析后依賴關(guān)系如下:

          {
          ????A:?["index&_&首頁(yè)&_&index"],//?A代表組件A的路徑
          ????B:?["index&_&首頁(yè)&_&index"],//?B代表組件B的路徑
          ????Card:?["index&_&首頁(yè)&_&index"],
          ????//?映射中只有和首頁(yè)的映射
          ????D:?["index&_&首頁(yè)&_&index"],//?D代表組件D的路徑
          ????E:?["home&_&個(gè)人主頁(yè)&_&home"],//?E代表組件E的路徑
          }

          因?yàn)樯弦徊揭蕾囀占糠?,Card組件并沒有成功收集到個(gè)人主頁(yè)的依賴中,所以這步整合分析也無法建立準(zhǔn)確的映射關(guān)系。且看下面的解決。

          6 修改unsafeCache配置

          為什么公共組件Card在收集依賴的時(shí)候,只收集到一次?這個(gè)問題如果不解決,意味著只有首頁(yè)的商品點(diǎn)擊埋點(diǎn)被收集到,其他引用這個(gè)組件的頁(yè)面商品點(diǎn)擊就會(huì)丟失。有問題,就有機(jī)會(huì),機(jī)會(huì)意味著解決問題的可能性。

          webpack4提供了resolve的配置入口,開發(fā)者可以通過幾項(xiàng)設(shè)置決定如何解析文件,比如extensions、alias等,其中有一個(gè)屬性——unsafeCache成功引起了作者的注意,它正是問題的根結(jié)。

          6.1 unsafeCache是webpack提高編譯性能的優(yōu)化措施。

          unsafeCache默認(rèn)為true,表示webpack會(huì)緩存已經(jīng)解析過的文件依賴,待再次需要解析此文件時(shí),直接從緩存中返回結(jié)果,避免重復(fù)解析。

          我們看下源碼:

          //webpack/lib/WebpackOptionsDefaulter.js
          this.set("resolveLoader.unsafeCache",?true);
          //這是webpack初始化配置參數(shù)時(shí)對(duì)unsafeCache的默認(rèn)設(shè)置

          //enhanced-resolve/lib/Resolverfatory.js
          if?(unsafeCache)?{
          ?plugins.push(
          ??new?UnsafeCachePlugin(
          ???"resolve",
          ???cachePredicate,
          ???unsafeCache,
          ???cacheWithContext,
          ???"new-resolve"
          ??)
          ?);
          ?plugins.push(new?ParsePlugin("new-resolve",?"parsed-resolve"));
          }?else?{
          ?plugins.push(new?ParsePlugin("resolve",?"parsed-resolve"));
          }
          //前面已經(jīng)提到,webpack將文件的解析獨(dú)立為一個(gè)單獨(dú)的庫(kù)去做,那就是enhanced-resolve。
          //緩存的工作是由UnsafeCachePlugin完成,代碼如下:
          //enhanced-resolve/lib/UnsafeCachePlugin.js
          apply(resolver)?{
          ?const?target?=?resolver.ensureHook(this.target);
          ?resolver
          ??.getHook(this.source)
          ??.tapAsync("UnsafeCachePlugin",?(request,?resolveContext,?callback)?=>?{
          ???if?(!this.filterPredicate(request))?return?callback();
          ???const?cacheId?=?getCacheId(request,?this.withContext);
          ???//?!!劃重點(diǎn),當(dāng)緩存中存在解析過的文件結(jié)果,直接callback
          ???const?cacheEntry?=?this.cache[cacheId];
          ???if?(cacheEntry)?{
          ????return?callback(null,?cacheEntry);
          ???}
          ???resolver.doResolve(
          ????target,
          ????request,
          ????null,
          ????resolveContext,
          ????(err,?result)?=>?{
          ?????if?(err)?return?callback(err);
          ?????if?(result)?return?callback(null,?(this.cache[cacheId]?=?result));
          ?????callback();
          ????}
          ???);
          ??});
          }

          在UnsafeCachePlugin的apply方法中,當(dāng)判斷有緩存過的文件結(jié)果,直接callback,沒有繼續(xù)后面的解析動(dòng)作。

          6.2 這對(duì)我們收集依賴有什么影響?

          緩存了解析過的文件,意味著與這個(gè)文件再次相遇時(shí),事件流將被提前終止,afterResolve的鉤子自然也就不會(huì)執(zhí)行到,那么我們的依賴關(guān)系就無從談起。

          其實(shí)webpack的resolve 過程可以看成事件的串聯(lián),當(dāng)所有串聯(lián)在一起的事件執(zhí)行完之后,resolve 就結(jié)束了。我們看下原理:

          用來解析文件的庫(kù)是enhanced-resolve,在Resolverfatory生成resolver解析對(duì)象時(shí),進(jìn)行了大量plugins的注冊(cè),正是這些plugins形成一系列的解析事件。

          //enhanced-resolve/lib/Resolverfatory.js
          exports.createResolver?=?function(options)?{
          ????......
          ?let?unsafeCache?=?options.unsafeCache?||?false;
          ?if?(unsafeCache)?{
          ??plugins.push(
          ???new?UnsafeCachePlugin(
          ????"resolve",
          ????cachePredicate,
          ????unsafeCache,
          ????cacheWithContext,
          ????"new-resolve"
          ???)
          ??);
          ??plugins.push(new?ParsePlugin("new-resolve",?"parsed-resolve"));
          ??//?這里的事件流大致是:UnsafeCachePlugin的事件源(source)是resolve,
          ??//執(zhí)行結(jié)束后的目標(biāo)事件(target)是new-resolve。
          ??//而ParsePlugin的事件源為new-resolve,所以事件流機(jī)制剛好把這兩個(gè)插件串聯(lián)起來。
          ?}?else?{
          ??plugins.push(new?ParsePlugin("resolve",?"parsed-resolve"));
          ?}
          ?......?//?各種plugin
          ?plugins.push(new?ResultPlugin(resolver.hooks.resolved));

          ?plugins.forEach(plugin?=>?{
          ??plugin.apply(resolver);
          ?});

          ?return?resolver;
          }

          每個(gè)插件在執(zhí)行自己的邏輯后,都會(huì)調(diào)用resolver.doResolve(target, ...),其中的target是觸發(fā)下一個(gè)插件的事件名稱,如此往復(fù),直到遇到事件源為result,遞歸終止,解析結(jié)束。

          resolve的事件串聯(lián)流程圖大致如下:


          UnsafeCachePlugin插件在第一次解析文件時(shí),因?yàn)闆]有緩存,就會(huì)觸發(fā)target為new-resolve的事件,也就是ParsePlugin,同時(shí)將解析結(jié)果記入緩存。當(dāng)判斷該文件有緩存結(jié)果,UnsafeCachePlugin的apply方法會(huì)直接callback,而沒有繼續(xù)執(zhí)行resolver.doResolve(),意味著整個(gè)resolve事件流在UnsafeCachePlugin就終止了。這就解釋了,為什么只建立了首頁(yè)與Card組件的映射,而無法拿到個(gè)人主頁(yè)與Card組件的映射。

          6.3 解決辦法

          分析了原因后,就好辦了,將unsafeCache設(shè)置為false(嗯,就這么簡(jiǎn)單)。這時(shí)你可能擔(dān)心會(huì)降低工程編譯速度,但深入一步想想,依賴分析這件事完全可以獨(dú)立于開發(fā)階段,只要在我們需要它的時(shí)候執(zhí)行這個(gè)能力,比如由開發(fā)者通過命令行參數(shù)來控制。

          //package.json
          "analyse":?"cross-env?LEGO_ENV=analyse?vue-cli-service?build"

          //vue.config.js
          chainWebpack(config)?{
          ????//?這一步解決webpack對(duì)組件緩存,影響最終映射關(guān)系的處理
          ????config.resolve.unsafeCache?=?process.env.LEGO_ENV?!=?'analyse'
          }

          7 最終依賴關(guān)系

          {
          ????A:?["index&_&首頁(yè)&_&index"],//?A代表組件A的路徑
          ????B:?["index&_&首頁(yè)&_&index"],//?B代表組件B的路徑
          ????Card:?["index&_&首頁(yè)&_&index",
          ????"home&_&個(gè)人主頁(yè)&_&home"],
          ????//?Card組件與多個(gè)頁(yè)面有映射關(guān)系
          ????D:?["index&_&首頁(yè)&_&index"],//?D代表組件D的路徑
          ????E:?["home&_&個(gè)人主頁(yè)&_&home"],//?E代表組件E的路徑
          }

          可以看到,與公共組件Card關(guān)聯(lián)的映射頁(yè)面中,多了個(gè)人主頁(yè)的路由信息,這才是準(zhǔn)確的依賴數(shù)據(jù)。在埋點(diǎn)自動(dòng)收集項(xiàng)目中,這份依賴關(guān)系數(shù)據(jù)交由jsdoc處理,就可以完成所有埋點(diǎn)信息與頁(yè)面的映射關(guān)系。

          one more thing

          webpack5,它來了,它帶著持久化緩存策略來了。前面提到的unsafeCache雖然可以提升應(yīng)用構(gòu)建性能,但是它犧牲了一定的 resolving 準(zhǔn)確度,同時(shí)它意味著持續(xù)性構(gòu)建過程需要反復(fù)重新啟動(dòng)決斷策略,這就要收集文件的尋找策略(resolutions)的變化,要識(shí)別判斷文件 resolutions 是否變化,這一系列過程也是有成本的,這就是為什么叫unsafeCache,而不是safeCache(安全的)。

          webpack5規(guī)定在配置信息的cache對(duì)象的type,可以設(shè)置為memoryfileSystem兩種方式。memory是指之前的unsafeCache緩存,fileSystem是指相對(duì)安全的磁盤持久化緩存。

          module.exports?=?{
          ??cache:?{
          ????//?1.?Set?cache?type?to?filesystem
          ????type:?'filesystem',

          ????buildDependencies:?{
          ??????//?2.?Add?your?config?as?buildDependency?to?get?cache?invalidation?on?config?change
          ??????config:?[__filename]

          ??????//?3.?If?you?have?other?things?the?build?depends?on?you?can?add?them?here
          ??????//?Note?that?webpack,?loaders?and?all?modules?referenced?from?your?config?are?automatically?added
          ????}
          ??}
          };

          所以針對(duì)webpack5,如果需要做完整的依賴分析,只需將cache.type動(dòng)態(tài)設(shè)置為memory,resolve.unsafeCache設(shè)置為false即可。(感興趣的童鞋可以試一試)

          • 總結(jié)

          以上,我們解釋了組件化可能帶來的隱患,提到了路由依賴分析的重要性,給出三種依賴分析的思路,并基于埋點(diǎn)自動(dòng)收集項(xiàng)目重點(diǎn)闡述了其中一種方案的具體實(shí)現(xiàn)。在此與你分享,期待共同成長(zhǎng)~

          ??愛心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長(zhǎng)指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端?開發(fā)者,會(huì)討論?前端 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長(zhǎng)。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  首页AV天堂 | 亚洲黄片免费 | 91在线超碰国产97 | 蜜桃视频网站免费观看 | 扒开屁日本网视频 |