精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

淺析 Parcel 的 Rust 打包算法 Demo

開(kāi)發(fā) 開(kāi)發(fā)工具
Parcel 在 2.8.0 的更新中提到使用了一個(gè)新的打包算法,相比較于之前速度提升了 2.7 倍,并且體積還減小了 2.5 倍。同時(shí)還有其他的比較夸張的性能提升,例如 6 倍的熱更新速度,增量構(gòu)建的再次構(gòu)建性能提升了10倍。

Parcel 是一個(gè)類似于 Webpack 、Rollup 的構(gòu)建工具,相較于這一類構(gòu)建工具,Parcel 主打的賣點(diǎn)是零配置并開(kāi)箱即用,雖然某種程度上這種零配置的方式會(huì)使得項(xiàng)目定制化變得很困難,但 Parcel 盡量提供了一套自身的構(gòu)建最佳實(shí)踐,以后有機(jī)會(huì)去單獨(dú)介紹一下 Parcel 的整體構(gòu)造,這里不展開(kāi)講解了。

Parcel 在 2.8.0 的更新中提到使用了一個(gè)新的打包算法,相比較于之前速度提升了 2.7 倍,并且體積還減小了 2.5 倍。同時(shí)還有其他的比較夸張的性能提升,例如 6 倍的熱更新速度,增量構(gòu)建的再次構(gòu)建性能提升了10倍。

圖片

同時(shí)作者強(qiáng)調(diào)該算法是由來(lái)自 Atlassian 的團(tuán)隊(duì)貢獻(xiàn)的,他們?yōu)榇嘶舜蠹s一年的時(shí)間使得其在 parcel v2.8.0 中成為默認(rèn)的打包算法,該算法帶來(lái)了巨大的性能提升,并且通過(guò)更少的重復(fù)包以及更好的瀏覽器緩存來(lái)有效減少了包產(chǎn)物的體積:

This results in both smaller bundles and much faster builds. *For a very large real-world project with over 60,000 assets, overall build time was reduced from over 25 minutes to 9 minutes (2.7x faster). The total bundle size for the whole project went from 952 MB *to 370 MB (2.5x smaller). For comparison, building the same app with webpack takes over 45 minutes.

此處測(cè)試項(xiàng)目根據(jù) twitter 信息為 jira。

實(shí)際上這個(gè)算法在 landing 過(guò)程中,它是基于 Parcel 作者 Devon Govett 本身寫的一個(gè)打包算法原型,并對(duì)這個(gè)算法本身在 Parcel 的實(shí)際場(chǎng)景中做了一些優(yōu)化。具體可以參考這個(gè) PR: https://github.com/parcel-bundler/parcel/pull/6975 的一些內(nèi)容:

圖片

在這篇文章中,我們先暫時(shí)不分析 Parcel 目前的具體的打包策略以及代碼邏輯,而是結(jié)合這個(gè)原型倉(cāng)庫(kù)來(lái)了解一下 Parcel 最原始的打包算法的運(yùn)行思路:

圖片圖片

相比較于 Parcel 本身的打包算法,這個(gè)原型 Demo 可以要更簡(jiǎn)單一點(diǎn)(不過(guò)由于代碼是 Rust,理解難度對(duì)筆者來(lái)說(shuō)也沒(méi)有很簡(jiǎn)單),在之后的文章中,我會(huì)單獨(dú)結(jié)合 Parcel 目前本身的打包算法(Default Bundlers)來(lái)做一下講解。

Rust 算法 demo

我們可以根據(jù)上面 PR 中找到對(duì)應(yīng)的算法原型倉(cāng)庫(kù),這個(gè)倉(cāng)庫(kù)的地址是: https://github.com/devongovett/bundler-algorithm, 不過(guò)由于倉(cāng)庫(kù)的一些內(nèi)容已經(jīng)實(shí)際落地了,因此作者后面將這個(gè)倉(cāng)庫(kù)給歸檔了。

這個(gè)倉(cāng)庫(kù)是基于 rust 寫的,不過(guò)整體流程上而言并不是特別復(fù)雜,我們可以簡(jiǎn)單調(diào)試一下(本地需要有 rust 環(huán)境):

git clone https://github.com/devongovett/bundler-algorithm
cd bundler-algorithm
# 使用 nightly 版本的 cargo
rustup install nightly && rustup default nightly

# 運(yùn)行 demo
cargo run

根據(jù)該倉(cāng)庫(kù)源碼,我們可以將一次打包算法執(zhí)行流程分為下面幾個(gè)步驟,注意以下步驟中的具體代碼存在一些細(xì)節(jié)省略的情況,如果你想了解更多的算法細(xì)節(jié),可以參考倉(cāng)庫(kù)源碼來(lái)進(jìn)行閱讀。

構(gòu)建依賴圖

首先在 main.rs 文件中的第一步,會(huì)先根據(jù)項(xiàng)目各文件之間的引用關(guān)系構(gòu)建一個(gè)依賴圖出來(lái)。

這里直接參考 build_graph 這個(gè)方法,例如這里項(xiàng)目中存在 html 、js 等不同類型的文件,首先會(huì)將這些資源文件作為節(jié)點(diǎn)添加到一個(gè)圖中,然后根據(jù)這些文件之間的引用關(guān)系去給對(duì)應(yīng)的節(jié)點(diǎn)添加對(duì)應(yīng)的邊,這樣就會(huì)形成一個(gè)比較初步完善的依賴圖。

// 創(chuàng)建 graph 并獲取對(duì)應(yīng)的 entries 對(duì)象
let (g, entries) = build_graph();

fn build_graph<'a>() -> (Graph<Asset<'a>, Dependency>, Vec<NodeIndex>) {
  let mut g = Graph::new();
  let mut entries = Vec::new();

  let html = g.add_node(Asset {
    name: "a.html",
    asset_type: AssetType::HTML,
    size: 10
  });
  
  let js = g.add_node(Asset {
    name: "a.js",
    asset_type: AssetType::JavaScript,
    size: 10
  });

  // ...一些資源的初始化過(guò)程,這里節(jié)省篇幅跳過(guò)...
  g.add_edge(html, js, Dependency {
    is_async: false
  });
  
  entries.push(html);
  entries.push(html2);

  return (g, entries);
}

由于 build_graph 方法中代碼邏輯都比較單一,因此上面貼的源碼中有一些重復(fù)邏輯例如資源的初始化等的省略。

最后這個(gè)方法會(huì)構(gòu)建出一個(gè)如下圖所示的依賴圖出來(lái),注意下面還有一些依賴之間的引用關(guān)系是異步的(這里可以理解為動(dòng)態(tài)導(dǎo)入),同時(shí)這里我們給每個(gè)靜態(tài)資源都標(biāo)注一個(gè)唯一的 asset_id,下文圖中標(biāo)注的序號(hào)均與此處對(duì)應(yīng):

圖片圖片

其中 Parcel 會(huì)默認(rèn)將 Html 資源作為依賴圖的入口文件,因此我們可以看到這張圖是存在兩個(gè)入口的,分別為 a.html 和 b.html 。

遍歷圖創(chuàng)建獨(dú)立的 bundle

這里的 bundle 可以簡(jiǎn)單理解為最終 打包 輸出的文件,這個(gè)可以結(jié)合 Webpack 里面的一些概念來(lái)理解。

在圖創(chuàng)建完成之后,這一步主要目的則是根據(jù) graph 提供的一些信息(例如 entry、資源之間的依賴關(guān)系等)去創(chuàng)建出最后打包出來(lái)的 bundle 類型,這里首先會(huì)對(duì)下面三種情況創(chuàng)建一個(gè)單獨(dú)的 bundle:

  • 入口文件,例如這里的 html 入口文件
  • 不同類型資源之間的引用,例如當(dāng) js 文件中引用到圖片資源的時(shí)候
  • 資源之間存在 異步 引用時(shí),例如某個(gè) js 動(dòng)態(tài)導(dǎo)入另外的 js 文件

下面我將根據(jù)這幾種情況來(lái)展開(kāi)講講:

首先是根據(jù)入口文件去創(chuàng)建 bundle,這里邏輯很簡(jiǎn)單,遍歷一下前面 build_graph 方法生成的 entries 數(shù)組(實(shí)際上這里是個(gè) Rust 的 Vec ),然后往 bundle_roots 中插入對(duì)應(yīng)的 entry 以及 bundle_id 。

這里的 bundle_id 對(duì)應(yīng)前面 graph 圖中標(biāo)記的序列號(hào),例如 a.html 是 0,b.html 是 1。具體的實(shí)現(xiàn)參考以下邏輯:

let mut bundle_roots = HashMap:new();
let mut reacheable_bundles = HashSet::new();
let mut bundle_graph = Graph::new();

// 遍歷 entries,往 bundle_roots 中插入對(duì)應(yīng)的 entry 信息以及對(duì)應(yīng)的 bundle_id
for entry in &entries {
  let bundle_id = bundle_graph.add_node(Bundle::from_asset(*entry, &g[*entry]));
  bundle_roots.insert(*entry, (bundle_id, bundle_id));
}

這里應(yīng)用到實(shí)際開(kāi)發(fā)中的場(chǎng)景可以聯(lián)想到我們開(kāi)發(fā)一個(gè)單頁(yè)(SPA) 或者多頁(yè)應(yīng)用(MPA)時(shí),在打包產(chǎn)物中通常會(huì)出現(xiàn)一個(gè)或者多個(gè) html 入口文件,這里的情況也是類似的。

添加完 html 入口文件之后,接下來(lái)就會(huì)用深度優(yōu)先搜索算法(DFS)去遍歷整個(gè)圖,對(duì)以下兩種情況再生成單獨(dú)的 bundle:

  • 不同類型資源之間的引用,對(duì)被引用的資源創(chuàng)建一個(gè) bundle,例如 a.js 中引用到了 style.css 那么 style.css 會(huì)被處理成一個(gè)單獨(dú)的 bundle
  • 資源之間存在 異步 引用時(shí),對(duì)被引用的資源創(chuàng)建一個(gè) bundle,例如 a.js 是異步引用的 async.js ,那么 async.js 會(huì)被處理成一個(gè)單獨(dú)的 bundle

以下為遍歷圖的主要代碼邏輯,具體可以參考 DfsEvent::TreeEdge(u, v) ,這里是遍歷圖中的各個(gè)相連子節(jié)點(diǎn):

let mut stack = LinkedList::new();

depth_first_search(&g, entries, |event| {
  match event {
    // ...
    DfsEvent::TreeEdge(u, v) => {
      let asset_a = &g[u];
      let asset_b = &g[v];
      // 當(dāng)資源類型發(fā)生變化的時(shí)候,創(chuàng)建一個(gè)新的 bundle
      if asset_a.asset_type != asset_b.asset_type {
        // 舉個(gè)例子,這里 a.js -> style.css
        // 這里 bundle_group_id 是 a.html,asset_b 是 style.css
        // style.css 是不同類型的資源被引用,會(huì)被拆單獨(dú)的 bundle 出來(lái)
        let (_, bundle_group_id) = stack.front().unwrap();
        let bundle_id = bundle_graph.add_node(Bundle::from_asset(v, asset_b));
        bundle_roots.insert(v, (bundle_id, *bundle_group_id));
        bundle_graph.add_edge(*bundle_group_id, bundle_id, 0);
        return
      }
      
      // 當(dāng)存在異步依賴的時(shí)候,創(chuàng)建一個(gè)新的 bundle
      let dependency = &g[g.find_edge(u, v).unwrap()];
      // 舉個(gè)例子,這里 a.js -> async.js 是異步依賴導(dǎo)入
      if dependency.is_async {
        // 因此這里 async.js(這里的 asset_b) 會(huì)被處理成一個(gè)單獨(dú)的 bundle
        let bundle_id = bundle_graph.add_node(Bundle::from_asset(v, asset_b));
        bundle_roots.insert(v, (bundle_id, bundle_id));
        for (b, _) in &stack {
            let a = &g[*b];
            if a.asset_type != asset_b.asset_type {
              break
            }
            reachable_bundles.insert((*b, v));
          }
      }
    }
    // ...
  }
});

在經(jīng)歷過(guò)上面兩次操作之后,我們最開(kāi)始的 Graph 就會(huì)創(chuàng)建以下幾個(gè)單獨(dú)的 bundle 出來(lái),對(duì)應(yīng)的文件分別為:

  • 作為入口文件的 a.html 和 b.html 各自形成一個(gè) bundle
  • 被 html 文件引用的 a.js 和 b.js 以及被 js 文件引用的 style.css 各自形成一個(gè) bundle
  • 被異步引用的 async.js 文件形成一個(gè) bundle

并且這些 bundle 之間還存在一定的依賴關(guān)系(這里的序號(hào)為最初始依賴圖中的序號(hào)):

圖片圖片

上面 async2.js 這個(gè)異步的 js 資源沒(méi)有形成單獨(dú)的 bundle 原因在于其是被其它 js 資源同步引入的。

處理所有資源到對(duì)應(yīng) bundle

在上一接中利用初始化的依賴圖分析出了最初的幾個(gè)獨(dú)立的 bundle,當(dāng)然還剩下一些其它的資源還沒(méi)處理,例如 async2.js 和 shared.js 這兩個(gè) asset。

因此這一步的操作就是將這些還沒(méi)有處理成 bundle 的 assets 全部都處理到 bundles 中去,同時(shí)整合一下上一步遍歷出來(lái)的獨(dú)立 bundle。

首先這一步會(huì)根據(jù)前面的到的獨(dú)立 bundle 去建立一個(gè)可訪問(wèn)的節(jié)點(diǎn),這里會(huì)用輪詢上一節(jié)生成的獨(dú)立 bundle,也就是這里的 bundle_roots ,同時(shí)根據(jù)每個(gè) bundle_root 在最原始的依賴圖中進(jìn)行一次 DFS 查找,然后剪枝掉一些同樣是 bundle_root 的 bundle,把剩余不是 asset 的添加到可訪問(wèn)節(jié)點(diǎn)中來(lái),實(shí)現(xiàn)參考如下:

let mut reachable_nodes = HashSet::new();
for (root, _) in &bundle_roots {
  depth_first_search(&g, Some(*root), |event| {
    if let DfsEvent::Discover(n, _) = &event {
      if n == root {
        return Control::Continue
      }

      // 如果命中了 bundle_root 就跳過(guò)
      if bundle_roots.contains_key(&n) {
        return Control::<()>::Prune;
      }
      // 否則就添加到可訪問(wèn)節(jié)點(diǎn)中來(lái)
      reachable_nodes.insert((*root, *n));
    }
      Control::Continue
    });
}

舉個(gè)例子來(lái)說(shuō),例如 a.js 是個(gè)單獨(dú)的 bundle,但他同步引用了一個(gè)同類型的 async2.js ,同時(shí) async2.js 又引用了一個(gè) shared.js 。

圖片圖片

那么這里就會(huì)形成一個(gè)以a.js為根節(jié)點(diǎn),分別到 async2.js 和 shared.js 的兩個(gè)可訪問(wèn)節(jié)點(diǎn)。

同理,在上圖中我們還能找到這樣的一些訪問(wèn)節(jié)點(diǎn):

圖片圖片

拿到這些可訪問(wèn)節(jié)點(diǎn)以后,這里會(huì)像最開(kāi)始的的時(shí)候,根據(jù)這些節(jié)點(diǎn)再去形成一個(gè)可訪問(wèn)節(jié)點(diǎn)的依賴圖,這個(gè)圖對(duì)比最初的依賴圖會(huì)小一點(diǎn),這個(gè)依賴圖的主要作用是為了幫助后續(xù)決定具體哪些 asset 被放到哪個(gè) bundle 中去,這個(gè)過(guò)程可以和 Webpack 的拆包策略一起理解。

// 根據(jù)拿到的可訪問(wèn)的節(jié)點(diǎn),構(gòu)造一個(gè) graph 出來(lái)
let reachable_graph = Graph::<(), ()>::from_edges(&reachable_nodes);

這里的依賴圖實(shí)際上就是個(gè)去除一些 root_bundle 后的縮減版的依賴圖:

圖片圖片

在完成可訪問(wèn)節(jié)點(diǎn)依賴圖的構(gòu)建之后,下一步開(kāi)始利用這個(gè)依賴圖將所有的 asset 都處理進(jìn) bundle 中去。其中這里每個(gè) asset 都會(huì)基于其和可訪問(wèn)的 bundle_roots 之間的關(guān)系被放到單獨(dú) bundle 中去,這么做的目的是為先創(chuàng)建盡可能沒(méi)有重復(fù)代碼的 bundle graph。

// 用于存儲(chǔ) entry asset id 到 bundle id 的映射
let mut bundles: HashMap<Vec<NodeIndex>, NodeIndex> = HashMap::new();

for asset_id in g.node_indices() {
  let reachable: Vec<NodeIndex> = reachable_graph.neighbors_directed(asset_id, Incoming).collect();
  let reachable: Vec<NodeIndex> = reachable.iter().cloned().filter(|b| {
      (&reachable).into_iter().all(|a| !reachable_bundles.contains(&(*a, *b)))
  }).collect();
  
  // 根據(jù)上面的每個(gè) asset 對(duì)應(yīng)的可訪問(wèn)的 bundle 去生成一個(gè) bundle_graph
  if let Some((bundle_id, _)) = bundle_roots.get(&asset_id) {
    // 如果 asset 是個(gè) bundle_root,那么會(huì)給這個(gè) bundle_root 在 bundle_graph 添加上對(duì)應(yīng)的節(jié)點(diǎn)
    bundles.entry(vec![asset_id]).or_insert(*bundle_id);
    for a in &reachable {
      if *a != asset_id {
          bundle_graph.add_edge(bundle_roots[a].1, *bundle_id, 0);
       }
    }
  } else if reachable.len() > 0 {
    // 如果 asset 對(duì)于多個(gè) bundle 都是可訪問(wèn)的
    // 例如 shared.js(6) 就同時(shí)被 a.js(2) 和 b.js(5) 可訪問(wèn)
    // 這里可以根據(jù)這些 asset 組合為該 asset 創(chuàng)建一個(gè)新的 bundle 
  }
}

這一步的代碼邏輯會(huì)很復(fù)雜(從 rust 代碼的角度來(lái)看,畢竟筆者不是很懂 rust QAQ),因此這里筆者并沒(méi)有貼完所有的代碼。但我這里會(huì)結(jié)合具體的代碼來(lái)舉例講解一下這一步的具體操作:

首先這一步會(huì)遍歷所有的 asset ,找到那些目前還沒(méi)有形成 bundle 的 asset,如下圖所示,經(jīng)歷過(guò)前一個(gè)步驟之后,沒(méi)有形成 root bundle 的 asset 的只有 async2.js 以及 shared.js 。

圖片圖片

這里針對(duì) async2.js 這個(gè) asset 來(lái)講解一下,對(duì)于 async2.js 來(lái)說(shuō),對(duì)它可訪問(wèn)的 bundle 有 a.js (2)、async.js (3)。這里由于 async2.js 對(duì)于 async.js 的父 bundle a.js 同樣是可訪問(wèn)的,這里在處理可訪問(wèn)節(jié)點(diǎn)的時(shí)候會(huì)把 async.js 這個(gè) asset 給移除掉,相當(dāng)于 async2.js 實(shí)際可訪問(wèn)的 bundle 只有 a.js ,這里這樣處理的作用是為了后續(xù) asset 的 bundle 合并操作,可以參考如下代碼:

for asset_id in g.node_indices() {
  let reachable: Vec<NodeIndex> = reachable_graph.neighbors_directed(asset_id, Incoming).collect();
  // 這一步篩掉 async2.js
  let reachable: Vec<NodeIndex> = reachable.iter().cloned().filter(|b| {
      (&reachable).into_iter().all(|a| !reachable_bundles.contains(&(*a, *b)))
  }).collect();
}

在這一步篩出對(duì)應(yīng)可訪問(wèn)的 bundle 之后(reachable),接下來(lái)就開(kāi)始去繼續(xù)構(gòu)造 bundle_graph ,這上一步中 bundle_graph 中先暫時(shí)放置了一些 root_bundle 以及他們之間的引用關(guān)系。在這一步,對(duì)于沒(méi)有形成 bundle 的 asset 進(jìn)行繼續(xù)的完善。

這一步代碼比較復(fù)雜,筆者并沒(méi)有完全貼出來(lái),大概處理過(guò)程分成了兩個(gè)分支:

  • 處理成 root_bundle 的 bundle,需要將他們有依賴關(guān)系的 bundle_graph 添加上對(duì)應(yīng)的邊
  • 處理 root_bundle 之外的 asset 為 bundle(async2.js 和 shared.js)
  • 如果這個(gè) asset 被多個(gè) root_bundle 依賴(可訪問(wèn)),那么會(huì)給這個(gè) asset 創(chuàng)建一個(gè)單獨(dú)的 bundle 并給它在 bundle_graph 加上對(duì)應(yīng)的邊,例如這里的 shared.js
  • 如果這個(gè) asset 只被一個(gè) root_bundle 依賴(可訪問(wèn)),那么會(huì)直接把這個(gè) asset 添加到 root_bundle 的 bundle 中組成一個(gè)新的 bundle,例如這里的 async2.js
for asset_id in g.node_indices() {
   // ... 省略掉獲取 reacheable 這個(gè)變量過(guò)程
   if let Some((bundle_id, _)) = bundle_roots.get(&asset_id) {
     // 1. 處理 root_bundle,這一步可以理解為給 bundle_graph 添加邊
     bundles.entry(vec![asset_id]).or_insert(*bundle_id);
      for a in &reachable {
        if *a != asset_id {
          bundle_graph.add_edge(bundle_roots[a].1, *bundle_id, 0);
        }
      }
   } else if reachable.len() > 0 {
     // 2. 處理 root_bundle 之外的 asset 為 bundle
     let source_bundles = reachable.iter().map(|a| bundles[&vec![*a]]).collect();
      
      let bundle_id = bundles.entry(reachable.clone()).or_insert_with(|| {
        let mut bundle = Bundle::default();
        bundle.source_bundles = source_bundles;
        bundle_graph.add_node(bundle)
      });

      let bundle = &mut bundle_graph[*bundle_id];
      bundle.asset_ids.push(asset_id);
      bundle.size += g[asset_id].size;

      // 處理完 asset 為 bundle 之后,同樣要添加邊
      for a in reachable {
        if a != *bundle_id {
          bundle_graph.add_edge(bundle_roots[&a].1, *bundle_id, 0);
        }
      }
    }
   }
}

這一步最后會(huì)形成一個(gè)這樣的 bundle_graph ,這里對(duì)每個(gè) bundle 進(jìn)行了重新標(biāo)號(hào),不同于之前的 asset_id

圖片圖片

這里我們可以看到, 其中 async2.js 和 root_bundle a.js 形成了一個(gè)新的 bundle,而另外一個(gè) asset shared.js 也形成了一個(gè)單獨(dú)的 bundle,它們之間的依賴關(guān)系(graph 的邊)也能比較清晰的看到。

合并小的公共 bundle

上一步我們基本上將所有的 asset 都全處理成了 bundle 并成功構(gòu)建出了一個(gè) bundle_graph ,實(shí)際上現(xiàn)在構(gòu)建大頭基本上已經(jīng)完成了。

下面的步驟基本就是對(duì) bundle_graph 做一個(gè)優(yōu)化,這一步的處理對(duì)比前面的步驟就很簡(jiǎn)單了。

在開(kāi)始介紹代碼之前,這一步我們可以理解為 webpack 的 chunkSplit 配置中的 splitChunks.minSize 。在 Parcel 也有對(duì)應(yīng)的配置可以參考,這里的 minBundleSize:

{
  "@parcel/bundler-default": {
    "minBundles": 1,
    "minBundleSize": 3000,
    "maxParallelRequests": 20
  }
}

大致就是對(duì)小于 minBundleSize 的 shared_bundle 合并到對(duì)應(yīng)的可訪問(wèn) bundle 中去,這里的副作用是可能會(huì)導(dǎo)致在多個(gè) bundle 中存在一定體積的重復(fù) asset 體積(重復(fù)代碼),但帶來(lái)的好處則是可以減少請(qǐng)求數(shù)量,這里具體的取舍開(kāi)始看用戶自身的配置。

這一步的代碼處理如下:

for bundle_id in bundle_graph.node_indices() {
    let bundle = &bundle_graph[bundle_id];
    // 當(dāng)這個(gè) bundle 本身為 commen bundle 且他本身的體積小于某個(gè)值,這里的 demo 默認(rèn)寫為了 10
    // 那么則可以合并掉這個(gè) bundle
    if bundle.source_bundles.len() > 0 && bundle.size < 10 {
      remove_bundle(&g, &mut bundle_graph, bundle_id);
    }
  }
  
  fn remove_bundle(
    asset_graph: &Graph<Asset, Dependency>,
    bundle_graph: &mut Graph<Bundle, i32>,
    bundle_id: NodeIndex
  ) {
    // 在 bundle_graph 中刪除掉對(duì)應(yīng)的 bundle
    let bundle = bundle_graph.remove_node(bundle_id).unwrap();
    // 并將該 bundle 合并對(duì)其有引用關(guān)系的 bundle 中去
    for asset_id in &bundle.asset_ids {
      for source_bundle_id in &bundle.source_bundles {
        let bundle = &mut bundle_graph[*source_bundle_id];
        bundle.asset_ids.push(*asset_id);
        bundle.size += asset_graph[*asset_id].size;
    }
  }

合并超出并行請(qǐng)求限制的公共 bundle

在上一步處理完了最小體積的公共 bundle,這一步要處理的則是最大并行請(qǐng)求的 chunk,舉個(gè)例子,例如這里拆出來(lái)了 a.js 、common-a.js 、common-b.js 這三個(gè) bundle,并且 a.js 同時(shí)依賴 common-a.js 和 common-b.js,但現(xiàn)在用戶配置允許的最大 bundle 并行請(qǐng)求數(shù)目是 2,那么這里就會(huì)合并掉這兩個(gè) common bundle 中的其中一個(gè)到 a.js 中去。

這里在 Parcel 中也有對(duì)應(yīng)的配置項(xiàng),參考下面的 maxParallelRequests 這個(gè)參數(shù):

{
  "@parcel/bundler-default": {
    "minBundles": 1,
    "minBundleSize": 3000,
    "maxParallelRequests": 20
  }
}

這一步的代碼處理可以參考如下:

// demo 這里默認(rèn)的限制請(qǐng)求數(shù)量為 3
let limit = 3;
for (_, (bundle_id, bundle_group_id)) in bundle_roots {
    if bundle_id != bundle_group_id {
      continue;
    }
    let mut neighbors: Vec<NodeIndex> = bundle_graph.neighbors(bundle_group_id).collect();
    if neighbors.len() > limit {
      neighbors.sort_by(|a, b| bundle_graph[*a].size.cmp(&bundle_graph[*b].size));
      // Remove bundles until the bundle group is within the parallel request limit.
      for bundle_id in &neighbors[0..neighbors.len() - limit] {
        // Add all assets in the shared bundle into the source bundles that are within this bundle group.
        let source_bundles: Vec<NodeIndex> = bundle_graph[*bundle_id].source_bundles.drain_filter(|s| neighbors.contains(s)).collect();
        for source in source_bundles {
          for asset_id in bundle_graph[*bundle_id].asset_ids.clone() {
            let bundle_id = bundles[&vec![source]];
            let bundle = &mut bundle_graph[bundle_id];
            bundle.asset_ids.push(asset_id);
            bundle.size += g[asset_id].size;
          }
        }

        // Remove the edge from this bundle group to the shared bundle.
        bundle_graph.remove_edge(bundle_graph.find_edge(bundle_group_id, *bundle_id).unwrap());

        // If there is now only a single bundle group that contains this bundle,
        // merge it into the remaining source bundles. If it is orphaned entirely, remove it.
        let count = bundle_graph.neighbors_directed(*bundle_id, Incoming).count();
        if count == 1 {
          remove_bundle(&g, &mut bundle_graph, *bundle_id);
        } else if count == 0 {
          bundle_graph.remove_node(*bundle_id);
        }
      }
    }
  }

這里簡(jiǎn)單對(duì)代碼邏輯做個(gè)講解,其實(shí)這里的邏輯也比較好理解:

  • 先找到對(duì)應(yīng)的 root_bundle(即這里的 bundle_group_id ),因?yàn)橐话阒挥?nbsp;root_bundle 會(huì)有多個(gè) bundle 并行請(qǐng)求。
  • 找出包括 root_bundle (不包括 root_bundle)依賴的所有 bundle(即 neighbors 變量),如果這個(gè)數(shù)目大于這里設(shè)置的限制(即 limit),按照neighbors 中的所有 bundle 體積大小,把體積小的 bundle 都合并到其對(duì)應(yīng)的 source_bundles 中去(這里不一定只有 root_bundle,還可能會(huì)有其他的 bundle)。一直合并到這些 bundle 的數(shù)目小于請(qǐng)求限制即可。
  • 合并到最后,如果只剩下唯一一個(gè) bundle 了,那么直接把這個(gè) bundle 也給合并進(jìn)剩下的 source_bundle 中去。

總結(jié)

在走完最后兩步的 bundle 合并之后,那么整個(gè) Rust Demo 的打包算法流程就結(jié)束了,實(shí)際上這個(gè) Demo 給的資源樣例并沒(méi)有走最后的兩步:

圖片圖片

按照最后的 bundle_graph 生成的圖我們可以看到:

  • 最小的 common_bundle 體積也要大于 10
  • 最大的并行請(qǐng)求數(shù)目也并沒(méi)有超過(guò) 3 個(gè)(圖中的幾個(gè) bundle 之間還有依賴關(guān)系,這里并沒(méi)有標(biāo)注出來(lái))

實(shí)際上最后生成的整體 bundle 也大概如圖所示。

不過(guò)這整個(gè)流程由于筆者對(duì)于 Rust 代碼并不是很熟悉,因此中間可能會(huì)有些疏漏,不過(guò)整體流程來(lái)看應(yīng)該并沒(méi)有特別大的出入。

責(zé)任編輯:武曉燕 來(lái)源: zoomdong
相關(guān)推薦

2024-02-28 08:38:07

Rust前端效率

2009-07-10 17:47:47

MyEclipse打包

2009-07-22 15:01:01

iBATIS SQLM

2022-08-28 20:50:29

算法模型機(jī)器學(xué)習(xí)

2018-02-09 11:08:49

區(qū)塊鏈算法主流

2020-12-16 05:46:58

算法加密算法MD5

2009-08-11 13:54:54

約瑟夫環(huán)算法C#算法

2018-01-09 13:42:37

集成學(xué)習(xí)算法

2017-12-29 15:16:49

Parcel集成方式

2020-12-11 06:41:15

AES加密

2025-03-03 01:00:00

DeepSeekGRPO算法

2009-08-11 09:19:52

C#選擇排序C#算法

2021-01-19 07:02:26

算法數(shù)據(jù)結(jié)構(gòu)堆排序

2009-01-16 09:42:19

SQL Server算法IO成本

2023-05-29 16:25:59

Rust函數(shù)

2024-11-08 09:19:28

2024-03-11 00:07:00

VueRustGo

2009-09-04 16:37:37

C# DES算法

2022-12-30 11:05:40

Rust代碼

2009-08-11 09:16:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

国产精品久久国产| 国产精品久久久久国产a级| 久久黄色一级视频| 国产精品69xx| 久久美女高清视频| 成人性生交xxxxx网站| 免费网站看av| 欧美日韩国产在线观看网站 | 一区二区三区四区不卡| 亚洲av无码一区二区三区dv | 亚洲免费成人av| 国内精品久久国产| 国产又粗又猛又黄又爽无遮挡| 亚洲视频免费| 精品国产欧美一区二区五十路| 国产伦精品一区二区三区精品| 69堂精品视频在线播放| 亚洲国产一区二区视频| 亚洲一区二区在线看| 无码精品一区二区三区在线| 国产一区二区三区观看| 国产成人精品av在线| 久久免费视频99| 99久久夜色精品国产亚洲狼| 精品亚洲aⅴ在线观看| 亚洲国产欧美日韩在线| 成人在线免费av| 欧美午夜激情视频| 国产高清av在线播放| 精品视频在线一区二区| 国产日韩欧美精品一区| 久久精品国产理论片免费| 国产伦精品一区二区三区四区| 亚洲综合社区| 国语自产偷拍精品视频偷| 性欧美videos| 999精品一区| 在线观看国产精品淫| 少妇光屁股影院| 国产精品毛片av| 日韩视频免费观看高清完整版 | 农村少妇一区二区三区四区五区| 欧美久久久久久久久| 91最新在线观看| 婷婷电影在线观看| 精品二区三区线观看| 日韩av新片网| 黄色在线观看视频网站| 亚洲综合视频在线| 天堂а√在线中文在线| av在线免费观看网址| 亚洲免费看黄网站| 99re99热| av网站在线免费看推荐| 亚洲免费伊人电影| 丁香色欲久久久久久综合网| 午夜影院免费在线| 亚洲精品菠萝久久久久久久| 日本精品免费视频| 高清免费电影在线观看| 亚洲精选免费视频| 国产激情在线看| 男女在线观看视频| 亚洲va国产天堂va久久en| 亚洲精品蜜桃久久久久久| caoporn视频在线| 五月激情六月综合| 久久久久久久久久久视频| 在线免费三级电影网站| 色狠狠一区二区三区香蕉| 少妇一级淫免费放| 95精品视频| 日韩三级视频在线看| 国产十八熟妇av成人一区| 欧美美女啪啪| 亚洲视频网站在线观看| 国产日产在线观看| 欧美激情在线| 欧洲亚洲女同hd| 波多野结衣二区三区| 久久99蜜桃精品| 超碰97人人人人人蜜桃| 人妻91麻豆一区二区三区| 2024国产精品| 中文字幕剧情在线观看一区| 欧美精品videossex少妇| 欧美性感美女h网站在线观看免费| 欧美黄网站在线观看| 国外成人福利视频| 欧美精品一区二区三区久久久| 免费日本黄色网址| 精品一区二区三| 欧美久久精品一级黑人c片| 日本少妇在线观看| 另类成人小视频在线| 国产亚洲欧美一区二区三区| 成人网视频在线观看| 亚洲精品菠萝久久久久久久| 日本www高清视频| 日本亚州欧洲精品不卡| 精品亚洲一区二区三区在线播放 | 天堂av一区二区三区| 国产欧美日韩久久| 97中文字幕在线| 精品久久99| 亚洲精品国产欧美| 天天看天天摸天天操| 午夜一区在线| 99在线观看视频网站| 国产三级在线看| 图片区日韩欧美亚洲| 久久久久久久久久毛片| 免费视频一区三区| 欧美激情区在线播放| 伊人久久国产精品| 26uuu亚洲综合色| 久久久久久av无码免费网站下载| 成人毛片免费| 亚洲毛片在线观看.| 久久一级黄色片| 狠狠色综合播放一区二区| 欧美亚洲另类久久综合| 大香伊人久久| 日韩三级在线免费观看| 性色国产成人久久久精品| 久久久久网站| 久久久久久国产精品免费免费| 色婷婷av在线| 日韩一区二区影院| 小早川怜子一区二区的演员表| 日韩国产在线一| 久久影视中文粉嫩av| h片在线观看下载| 欧美一区二区三区精品| 日韩在线一卡二卡| 美女精品自拍一二三四| 欧美日韩国产精品一卡| 亚洲人体视频| 日韩的一区二区| av中文在线播放| 菠萝蜜视频在线观看一区| 丰满少妇大力进入| 91成人在线精品视频| 欧美高清自拍一区| 丰满肉嫩西川结衣av| 一区二区三区在线免费播放| 一级黄色高清视频| 欧美激情1区2区| 亚洲精品欧美日韩专区| 91精品久久久| 欧美大胆人体bbbb| 精品少妇久久久| 成人精品电影在线观看| 蜜臀av色欲a片无码精品一区| 99国产精品免费网站| 久久久久国产精品免费| 日韩一区二区三区不卡| 天天色 色综合| 精品少妇人妻一区二区黑料社区| 丝袜亚洲另类丝袜在线| 色大师av一区二区三区| 成人全视频免费观看在线看| 色偷偷av亚洲男人的天堂| 国产又粗又猛又黄又爽| 亚洲欧美欧美一区二区三区| 日本人妻一区二区三区| 国产欧美精品| 色一情一乱一伦一区二区三区丨| 久久婷婷五月综合色丁香| 精品国产一区二区三区久久| 性一交一乱一伧老太| 午夜电影一区二区三区| 在线 丝袜 欧美 日韩 制服| 奇米影视7777精品一区二区| 欧美h视频在线观看| 波多野结衣欧美| 日本精品视频网站| 日本激情视频在线观看| 日韩三级免费观看| √资源天堂中文在线| 国产精品欧美综合在线| 97中文字幕在线观看| 校园激情久久| 国产高清精品软男同| 欧美交a欧美精品喷水| 国产欧美在线观看| www.综合| 久久精品成人动漫| 色网站免费观看| 欧美亚男人的天堂| 久久久综合久久久| 欧美国产精品一区| 麻豆短视频在线观看| 丝袜美腿亚洲一区| 69精品丰满人妻无码视频a片| 综合综合综合综合综合网| 亚洲自拍偷拍福利| 欧美va在线观看| 欧美极品少妇全裸体| 国产1区2区3区在线| 欧美xxxxxxxxx| 中文字幕+乱码+中文乱码www| 一区二区三区在线视频免费| 成年人在线免费看片| 国产91在线观看| 午夜免费高清视频| 亚洲日产国产精品| 日本黄色a视频| 啪啪亚洲精品| 国产欧美日韩综合一区在线观看| 欧洲午夜精品| 国产成人拍精品视频午夜网站| 欧美野外wwwxxx| 最新国产精品拍自在线播放| 欧美挠脚心网站| 亚洲大胆人体视频| 国产高清在线免费| 欧美片网站yy| 这里只有精品国产| 欧美日韩精品国产| 日本天堂在线视频| 亚洲五码中文字幕| 欧美成人精品欧美一级| 国产精品九色蝌蚪自拍| 一道本在线观看| 26uuu精品一区二区三区四区在线| 午夜诱惑痒痒网| 精品一区二区在线免费观看| 爱情岛论坛vip永久入口| 国产日韩欧美| 黄色一级在线视频| 亚洲国产清纯| 五十路熟女丰满大屁股| 影音国产精品| 国产高清av在线播放| 亚洲无线一线二线三线区别av| 日本久久高清视频| 66视频精品| 女女同性女同一区二区三区按摩| 久久激情电影| 一区二区在线观看网站| 99精品一区| 亚洲一区在线直播| 婷婷精品进入| 中国一级大黄大黄大色毛片| 无码一区二区三区视频| 欧美aaa在线观看| 羞羞答答成人影院www| 一区二区三区欧美在线| 亚洲经典一区| 日韩在线视频在线| 伊人久久大香线蕉综合热线 | 日韩影院在线观看| 天天碰免费视频| 免费成人在线影院| 亚洲欧美日韩三级| 国产乱人伦偷精品视频不卡| 亚洲国产欧美日韩在线| 成人av资源网站| 国产ts在线播放| 国产精品色噜噜| 澳门黄色一级片| 五月天精品一区二区三区| 区一区二在线观看| 欧美日韩在线直播| 国产精品一区二区三区在线免费观看| 制服.丝袜.亚洲.另类.中文| 午夜久久久久久久久久| 日韩av在线不卡| 成人高清在线| 欧美成人在线影院| 精品三级久久| 国产精品女主播| 中文字幕av一区二区三区四区| 精品日产一区2区三区黄免费| 国产成人一区| 99中文字幕在线观看| 亚洲一卡久久| 天天干天天操天天玩| 国产99久久久精品| 一级特级黄色片| 中文字幕在线免费不卡| 国产一级在线免费观看| 一本大道综合伊人精品热热| 国产精品一区二区黑人巨大| 日韩av一卡二卡| 成人三级网址| 欧美在线免费观看| www.成人| 黄色一区三区| 欧美日韩在线播放视频| 日韩黄色片在线| 久久久777| 杨幂一区二区国产精品| 久久伊人中文字幕| 国产a免费视频| 欧美中文一区二区三区| 丰满人妻一区二区三区免费| 色偷偷综合社区| 中文在线免费二区三区| 亚洲自拍偷拍网址| 精品免费在线| 日韩国产一级片| 精品一区二区三区日韩| 亚洲自拍偷拍一区二区| 亚洲综合激情另类小说区| 最近中文字幕免费观看| 亚洲精品美女在线观看| 欧美bbbxxxxx| 成人啪啪免费看| 国产日产一区| 免费看日本毛片| 国产成人精品一区二区三区四区| 长河落日免费高清观看| 色婷婷av一区二区| 天天干天天草天天射| 九九热精品视频国产| 亚洲aⅴ网站| 水蜜桃一区二区| 久久精品123| 日本黄色片在线播放| 亚洲一区二区视频在线| 99久久久国产精品无码免费| 日韩一中文字幕| 国产精成人品2018| 日本一区美女| 久久xxxx| 国产艳俗歌舞表演hd| 精品久久久久久久久久久久| 人人妻人人澡人人爽人人欧美一区| 久久精品视频亚洲| 日韩欧美专区| 亚洲草草视频| 日本最新不卡在线| 亚洲自拍偷拍图| 91久久精品网| 东凛在线观看| 国产精品免费电影| 日韩在线观看一区| jizzzz日本| 中文字幕一区不卡| 亚洲综合精品视频| 精品国产欧美一区二区五十路| 国产一区二区三区四区五区3d | 欧美精品午夜| a级大片免费看| 夜夜嗨av一区二区三区四季av| 国产福利第一页| 久久久久久综合网天天| 久久99精品久久久久久欧洲站| 久激情内射婷内射蜜桃| 99riav一区二区三区| 日本韩国欧美中文字幕| 亚洲免费精彩视频| 日日av拍夜夜添久久免费| 日韩欧美电影一区二区| 久久国产精品99久久人人澡| 国产美女福利视频| 精品国产免费一区二区三区四区 | 91成人在线观看喷潮| av小片在线| 91免费在线视频| 欧美成人亚洲| 国产不卡一二三| 一道本成人在线| 在线观看av黄网站永久| 91牛牛免费视频| 日韩天堂av| 中文天堂资源在线| 欧美一三区三区四区免费在线看| 男女视频在线| 日本不卡二区高清三区| 韩日精品视频一区| 国产一级特黄视频| 亚洲视频在线观看| 豆花视频一区| 黄页网站大全在线观看| 国产欧美一区二区精品婷婷| 国产精品爽爽久久| 韩国视频理论视频久久| 欧美在线电影| 性猛交╳xxx乱大交| 色婷婷久久久亚洲一区二区三区| 精品麻豆一区二区三区| 久久精品二区| 国产在线麻豆精品观看| 可以免费在线观看的av| 久久久精品国产网站| 色综合www| 91网址在线观看精品| 五月开心婷婷久久| 看女生喷水的网站在线观看| 精品视频第一区| 黄网站免费久久| 日韩手机在线视频| 欧美xxxx14xxxxx性爽| 国产精品一区2区3区| 超碰人人cao| 欧亚洲嫩模精品一区三区| 黄页网站大全在线免费观看|