mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
Turbopack: extract chunk list version/update into turbopack-ecmascript (#95661)
This moves modules out of `turbopack-browser` and into `turbopack-ecmascript` so it can be used in the server hmr implementation as well. Move the chunk-list version, update, and merged-update wire types out of `turbopack-browser` into a shared `turbopack-ecmascript::chunk_list` module so both the browser and node chunking contexts can build on them. Rename `EcmascriptDevChunkListVersion` to `ChunkListVersion` and make `update_chunk_list` runtime-agnostic (plain path->`VersionedContent` maps). Pure refactor: no behavior change. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Claude <[email protected]>
This commit is contained in:
co-authored by
Claude
parent
88155cd421
commit
8cfa551041
Generated
+1
@@ -10636,6 +10636,7 @@ dependencies = [
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"smallvec",
|
||||
"strsim 0.11.1",
|
||||
"swc_core",
|
||||
|
||||
@@ -148,6 +148,9 @@ impl VersionedContentMap {
|
||||
impl VersionedContentMap {
|
||||
/// Inserts output assets into the map and returns a completion that when
|
||||
/// awaited will emit the assets that were inserted.
|
||||
//
|
||||
// TODO: If `VersionedContentMap` becomes transient as described above, these methods should be
|
||||
// `#[turbo_tasks::function(session_dependent)]`
|
||||
#[turbo_tasks::function]
|
||||
pub async fn insert_output_assets(
|
||||
self: ResolvedVc<Self>,
|
||||
|
||||
@@ -13,17 +13,17 @@ use turbopack_core::{
|
||||
chunk::ChunkingContext,
|
||||
code_builder::{Code, CodeBuilder},
|
||||
output::OutputAsset,
|
||||
version::{
|
||||
MergeableVersionedContent, Update, Version, VersionedContent, VersionedContentMerger,
|
||||
version::{Update, Version, VersionedContent},
|
||||
};
|
||||
use turbopack_ecmascript::{
|
||||
chunk_list::{
|
||||
update::update_chunk_list,
|
||||
version::{ChunkListVersion, compute_chunk_list_version},
|
||||
},
|
||||
utils::StringifyJs,
|
||||
};
|
||||
use turbopack_ecmascript::utils::StringifyJs;
|
||||
|
||||
use super::{
|
||||
asset::{EcmascriptDevChunkList, EcmascriptDevChunkListSource},
|
||||
update::update_chunk_list,
|
||||
version::EcmascriptDevChunkListVersion,
|
||||
};
|
||||
use super::asset::{EcmascriptDevChunkList, EcmascriptDevChunkListSource};
|
||||
use crate::chunking_context::{
|
||||
CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR, CurrentChunkMethod,
|
||||
};
|
||||
@@ -102,39 +102,8 @@ impl EcmascriptDevChunkListContent {
|
||||
|
||||
/// Computes the version of this content.
|
||||
#[turbo_tasks::function]
|
||||
pub async fn version(&self) -> Result<Vc<EcmascriptDevChunkListVersion>> {
|
||||
let mut by_merger = FxIndexMap::<_, Vec<_>>::default();
|
||||
let mut by_path = FxIndexMap::<_, _>::default();
|
||||
|
||||
for (chunk_path, chunk_content) in &self.chunks_contents {
|
||||
if let Some(mergeable) =
|
||||
ResolvedVc::try_sidecast::<Box<dyn MergeableVersionedContent>>(*chunk_content)
|
||||
{
|
||||
let merger = mergeable.get_merger().to_resolved().await?;
|
||||
by_merger.entry(merger).or_default().push(*chunk_content);
|
||||
} else {
|
||||
by_path.insert(
|
||||
chunk_path.clone(),
|
||||
chunk_content.version().into_trait_ref().await?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let by_merger = by_merger
|
||||
.into_iter()
|
||||
.map(|(merger, contents)| (merger, Vc::cell(contents)))
|
||||
.map(async |(merger, contents)| {
|
||||
Ok((
|
||||
merger,
|
||||
merger.merge(contents).version().into_trait_ref().await?,
|
||||
))
|
||||
})
|
||||
.try_join()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
Ok(EcmascriptDevChunkListVersion { by_path, by_merger }.cell())
|
||||
pub async fn version(&self) -> Result<Vc<ChunkListVersion>> {
|
||||
compute_chunk_list_version(&self.chunks_contents).await
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
@@ -200,6 +169,7 @@ impl VersionedContent for EcmascriptDevChunkListContent {
|
||||
self: ResolvedVc<Self>,
|
||||
from_version: ResolvedVc<Box<dyn Version>>,
|
||||
) -> Result<Vc<Update>> {
|
||||
update_chunk_list(self, from_version).await
|
||||
let this = self.await?;
|
||||
update_chunk_list(&this.chunks_contents, self.version(), from_version).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
pub(crate) mod asset;
|
||||
pub(crate) mod content;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod version;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::Serialize;
|
||||
use turbo_tasks::{FxIndexMap, FxIndexSet, ReadRef, ResolvedVc, TryJoinIterExt, Vc};
|
||||
use turbo_tasks_fs::rope::Rope;
|
||||
use turbo_tasks::{FxIndexMap, ReadRef, ResolvedVc, TryJoinIterExt, Vc};
|
||||
use turbopack_core::{
|
||||
chunk::{ChunkingContext, ModuleId},
|
||||
code_builder::Code,
|
||||
output::OutputAsset,
|
||||
source_map::GenerateSourceMap,
|
||||
version::{PartialUpdate, TotalUpdate, Update, Version},
|
||||
};
|
||||
use turbopack_ecmascript::chunk_list::merged_update::{
|
||||
EcmascriptMergedChunkAdded, EcmascriptMergedChunkDeleted, EcmascriptMergedChunkPartial,
|
||||
EcmascriptMergedChunkUpdate, EcmascriptMergedUpdate, EcmascriptModuleEntry,
|
||||
};
|
||||
|
||||
use super::{
|
||||
super::{
|
||||
@@ -21,87 +21,6 @@ use super::{
|
||||
version::EcmascriptBrowserMergedChunkVersion,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
struct EcmascriptMergedUpdate<'a> {
|
||||
/// A map from module id to latest module entry.
|
||||
#[serde(skip_serializing_if = "FxIndexMap::is_empty")]
|
||||
entries: FxIndexMap<ModuleId, EcmascriptModuleEntry>,
|
||||
/// A map from chunk path to the chunk update.
|
||||
#[serde(skip_serializing_if = "FxIndexMap::is_empty")]
|
||||
chunks: FxIndexMap<&'a str, EcmascriptMergedChunkUpdate>,
|
||||
}
|
||||
|
||||
impl EcmascriptMergedUpdate<'_> {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty() && self.chunks.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
enum EcmascriptMergedChunkUpdate {
|
||||
Added(EcmascriptMergedChunkAdded),
|
||||
Deleted(EcmascriptMergedChunkDeleted),
|
||||
Partial(EcmascriptMergedChunkPartial),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct EcmascriptMergedChunkAdded {
|
||||
#[serde(skip_serializing_if = "FxIndexSet::is_empty")]
|
||||
modules: FxIndexSet<ModuleId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct EcmascriptMergedChunkDeleted {
|
||||
// Technically, this is redundant, since the client will already know all
|
||||
// modules in the chunk from the previous version. However, it's useful for
|
||||
// merging updates without access to an initial state.
|
||||
#[serde(skip_serializing_if = "FxIndexSet::is_empty")]
|
||||
modules: FxIndexSet<ModuleId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct EcmascriptMergedChunkPartial {
|
||||
#[serde(skip_serializing_if = "FxIndexSet::is_empty")]
|
||||
added: FxIndexSet<ModuleId>,
|
||||
#[serde(skip_serializing_if = "FxIndexSet::is_empty")]
|
||||
deleted: FxIndexSet<ModuleId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct EcmascriptModuleEntry {
|
||||
#[serde(with = "turbo_tasks_fs::rope::ser_as_string")]
|
||||
code: Rope,
|
||||
url: String,
|
||||
#[serde(with = "turbo_tasks_fs::rope::ser_option_as_string")]
|
||||
map: Option<Rope>,
|
||||
}
|
||||
|
||||
impl EcmascriptModuleEntry {
|
||||
async fn from_code(id: &ModuleId, code: Vc<Code>, chunk_path: &str) -> Result<Self> {
|
||||
let map = &*code.generate_source_map().await?;
|
||||
let map = map.as_content().map(|f| f.content().clone());
|
||||
|
||||
/// serde_qs can't serialize a lone enum when it's [serde::untagged].
|
||||
#[derive(Serialize)]
|
||||
struct Id<'a> {
|
||||
id: &'a ModuleId,
|
||||
}
|
||||
let id = serde_qs::to_string(&Id { id }).unwrap();
|
||||
|
||||
Ok(EcmascriptModuleEntry {
|
||||
// Cloning a rope is cheap.
|
||||
code: code.await?.source_code().clone(),
|
||||
url: format!("{}?{}", chunk_path, id),
|
||||
map,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper structure to get a module's hash from multiple different chunk
|
||||
/// versions, without having to actually merge the versions into a single
|
||||
/// hashmap, which would be expensive.
|
||||
|
||||
@@ -43,6 +43,7 @@ regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true, features = ["raw_value"] }
|
||||
serde_qs = { workspace = true }
|
||||
swc_ecma_react_compiler = { workspace = true }
|
||||
react_compiler = { workspace = true }
|
||||
next-custom-transforms = { workspace = true, default-features = false }
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
//! Shared wire-format types for merged ecmascript chunk updates.
|
||||
//!
|
||||
//! These serde structures are the protocol contract sent to the JS HMR client
|
||||
//! (see `applyEcmascriptMergedUpdateShared` in the ecmascript runtime). Both the
|
||||
//! browser and node chunking contexts serialize into exactly this shape, so the
|
||||
//! definitions live here to keep the two runtimes from drifting apart on the
|
||||
//! wire format.
|
||||
//!
|
||||
//! The turbo-tasks value types (`*MergedChunkContent`, `*MergedChunkVersion`,
|
||||
//! `*ChunkContentMerger`) cannot be generic and therefore remain per-runtime,
|
||||
//! but they all build and serialize these shared structs.
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::Serialize;
|
||||
use turbo_tasks::{FxIndexMap, FxIndexSet, Vc};
|
||||
use turbo_tasks_fs::rope::Rope;
|
||||
use turbopack_core::{chunk::ModuleId, code_builder::Code, source_map::GenerateSourceMap};
|
||||
|
||||
/// A merged update covering one or more ecmascript chunks that share a merger.
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(
|
||||
tag = "type",
|
||||
rename = "EcmascriptMergedUpdate",
|
||||
rename_all = "camelCase"
|
||||
)]
|
||||
pub struct EcmascriptMergedUpdate<'a> {
|
||||
/// A map from module id to its latest module entry (code + source map url).
|
||||
#[serde(skip_serializing_if = "FxIndexMap::is_empty")]
|
||||
pub entries: FxIndexMap<ModuleId, EcmascriptModuleEntry>,
|
||||
/// A map from chunk path to the update for that chunk.
|
||||
#[serde(skip_serializing_if = "FxIndexMap::is_empty")]
|
||||
pub chunks: FxIndexMap<&'a str, EcmascriptMergedChunkUpdate>,
|
||||
}
|
||||
|
||||
impl EcmascriptMergedUpdate<'_> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty() && self.chunks.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-chunk portion of an [`EcmascriptMergedUpdate`].
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum EcmascriptMergedChunkUpdate {
|
||||
Added(EcmascriptMergedChunkAdded),
|
||||
Deleted(EcmascriptMergedChunkDeleted),
|
||||
Partial(EcmascriptMergedChunkPartial),
|
||||
}
|
||||
|
||||
/// A chunk that was newly added in this version.
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EcmascriptMergedChunkAdded {
|
||||
#[serde(skip_serializing_if = "FxIndexSet::is_empty")]
|
||||
pub modules: FxIndexSet<ModuleId>,
|
||||
}
|
||||
|
||||
/// A chunk that was removed in this version.
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EcmascriptMergedChunkDeleted {
|
||||
// Technically, this is redundant, since the client will already know all
|
||||
// modules in the chunk from the previous version. However, it's useful for
|
||||
// merging updates without access to an initial state.
|
||||
#[serde(skip_serializing_if = "FxIndexSet::is_empty")]
|
||||
pub modules: FxIndexSet<ModuleId>,
|
||||
}
|
||||
|
||||
/// A chunk that was present in both versions and whose module membership
|
||||
/// changed.
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EcmascriptMergedChunkPartial {
|
||||
#[serde(skip_serializing_if = "FxIndexSet::is_empty")]
|
||||
pub added: FxIndexSet<ModuleId>,
|
||||
#[serde(skip_serializing_if = "FxIndexSet::is_empty")]
|
||||
pub deleted: FxIndexSet<ModuleId>,
|
||||
}
|
||||
|
||||
/// The code (and source map) for a single module in a merged update.
|
||||
#[derive(Serialize)]
|
||||
pub struct EcmascriptModuleEntry {
|
||||
#[serde(with = "turbo_tasks_fs::rope::ser_as_string")]
|
||||
pub code: Rope,
|
||||
pub url: String,
|
||||
#[serde(with = "turbo_tasks_fs::rope::ser_option_as_string")]
|
||||
pub map: Option<Rope>,
|
||||
}
|
||||
|
||||
impl EcmascriptModuleEntry {
|
||||
pub async fn from_code(id: &ModuleId, code: Vc<Code>, chunk_path: &str) -> Result<Self> {
|
||||
let map = &*code.generate_source_map().await?;
|
||||
let map = map.as_content().map(|f| f.content().clone());
|
||||
|
||||
/// serde_qs can't serialize a lone enum when it's [serde::untagged].
|
||||
#[derive(Serialize)]
|
||||
struct Id<'a> {
|
||||
id: &'a ModuleId,
|
||||
}
|
||||
let id = serde_qs::to_string(&Id { id }).unwrap();
|
||||
|
||||
Ok(EcmascriptModuleEntry {
|
||||
// Cloning a rope is cheap.
|
||||
code: code.await?.source_code().clone(),
|
||||
url: format!("{}?{}", chunk_path, id),
|
||||
map,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod merged_update;
|
||||
pub mod update;
|
||||
pub mod version;
|
||||
+23
-27
@@ -8,7 +8,7 @@ use turbopack_core::version::{
|
||||
VersionedContentMerger,
|
||||
};
|
||||
|
||||
use super::{content::EcmascriptDevChunkListContent, version::EcmascriptDevChunkListVersion};
|
||||
use super::version::ChunkListVersion;
|
||||
|
||||
/// Update of a chunk list from one version to another.
|
||||
#[derive(Serialize)]
|
||||
@@ -47,24 +47,27 @@ impl ChunkListUpdate<'_> {
|
||||
}
|
||||
|
||||
/// Computes the update of a chunk list from one version to another.
|
||||
pub(super) async fn update_chunk_list(
|
||||
content: ResolvedVc<EcmascriptDevChunkListContent>,
|
||||
///
|
||||
/// Runtime-agnostic (takes plain paths + [`VersionedContent`]) so the browser
|
||||
/// and node chunking contexts can share one implementation instead of each
|
||||
/// duplicating the merge-by-[`VersionedContentMerger`] logic.
|
||||
pub async fn update_chunk_list(
|
||||
chunks_contents: &FxIndexMap<String, ResolvedVc<Box<dyn VersionedContent>>>,
|
||||
to_version: Vc<ChunkListVersion>,
|
||||
from_version: ResolvedVc<Box<dyn Version>>,
|
||||
) -> Result<Vc<Update>> {
|
||||
let to_version = content.version();
|
||||
let from_version = if let Some(from) =
|
||||
ResolvedVc::try_downcast_type::<EcmascriptDevChunkListVersion>(from_version)
|
||||
{
|
||||
from
|
||||
} else {
|
||||
// It's likely `from_version` is `NotFoundVersion`.
|
||||
return Ok(Update::Total(TotalUpdate {
|
||||
to: Vc::upcast::<Box<dyn Version>>(to_version)
|
||||
.into_trait_ref()
|
||||
.await?,
|
||||
})
|
||||
.cell());
|
||||
};
|
||||
let from_version =
|
||||
if let Some(from) = ResolvedVc::try_downcast_type::<ChunkListVersion>(from_version) {
|
||||
from
|
||||
} else {
|
||||
// It's likely `from_version` is `NotFoundVersion`.
|
||||
return Ok(Update::Total(TotalUpdate {
|
||||
to: Vc::upcast::<Box<dyn Version>>(to_version)
|
||||
.into_trait_ref()
|
||||
.await?,
|
||||
})
|
||||
.cell());
|
||||
};
|
||||
|
||||
let to = to_version.await?;
|
||||
let from = from_version.await?;
|
||||
@@ -76,19 +79,12 @@ pub(super) async fn update_chunk_list(
|
||||
return Ok(Update::None.cell());
|
||||
}
|
||||
|
||||
let content = content.await?;
|
||||
|
||||
// There are two kind of updates nested within a chunk list update:
|
||||
// * merged updates; and
|
||||
// * single chunk updates.
|
||||
// In order to compute merged updates, we first need to group mergeable chunks
|
||||
// by common mergers. Then, we compute the update of each group separately.
|
||||
// Single chunk updates are computed separately and only require a stable chunk
|
||||
// path to identify the chunk across versions.
|
||||
// Group mergeable chunks by merger so their updates collapse into one
|
||||
// `EcmascriptMergedUpdate`; everything else is diffed individually by path.
|
||||
let mut by_merger = FxIndexMap::<_, Vec<_>>::default();
|
||||
let mut by_path = FxIndexMap::<_, _>::default();
|
||||
|
||||
for (chunk_path, chunk_content) in &content.chunks_contents {
|
||||
for (chunk_path, chunk_content) in chunks_contents {
|
||||
if let Some(mergeable) =
|
||||
ResolvedVc::try_sidecast::<Box<dyn MergeableVersionedContent>>(*chunk_content)
|
||||
{
|
||||
+56
-5
@@ -2,15 +2,21 @@ use anyhow::Result;
|
||||
use turbo_rcstr::RcStr;
|
||||
use turbo_tasks::{FxIndexMap, ResolvedVc, TraitRef, TryJoinIterExt, Vc};
|
||||
use turbo_tasks_hash::{Xxh3Hash64Hasher, encode_base64};
|
||||
use turbopack_core::version::{Version, VersionedContentMerger};
|
||||
use turbopack_core::version::{
|
||||
MergeableVersionedContent, Version, VersionedContent, VersionedContentMerger,
|
||||
};
|
||||
|
||||
type VersionTraitRef = TraitRef<Box<dyn Version>>;
|
||||
|
||||
/// The version of a [`EcmascriptDevChunkListContent`].
|
||||
/// The version of a chunk list content.
|
||||
///
|
||||
/// [`EcmascriptDevChunkListContent`]: super::content::EcmascriptDevChunkListContent
|
||||
/// Tracks versions of individual chunks by path and by merger. Chunks that
|
||||
/// implement [`MergeableVersionedContent`] are grouped by their merger and
|
||||
/// their versions are merged. Other chunks are tracked by path.
|
||||
///
|
||||
/// [`MergeableVersionedContent`]: turbopack_core::version::MergeableVersionedContent
|
||||
#[turbo_tasks::value(serialization = "skip", shared)]
|
||||
pub(super) struct EcmascriptDevChunkListVersion {
|
||||
pub struct ChunkListVersion {
|
||||
/// A map from chunk path to its version.
|
||||
#[turbo_tasks(trace_ignore)]
|
||||
pub by_path: FxIndexMap<String, VersionTraitRef>,
|
||||
@@ -24,7 +30,7 @@ pub(super) struct EcmascriptDevChunkListVersion {
|
||||
}
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
impl Version for EcmascriptDevChunkListVersion {
|
||||
impl Version for ChunkListVersion {
|
||||
#[turbo_tasks::function]
|
||||
async fn id(&self) -> Result<Vc<RcStr>> {
|
||||
let by_path = {
|
||||
@@ -66,3 +72,48 @@ impl Version for EcmascriptDevChunkListVersion {
|
||||
Ok(Vc::cell(hash.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes a [`ChunkListVersion`] from a map of chunk paths to their
|
||||
/// [`VersionedContent`].
|
||||
///
|
||||
/// Chunks that implement [`MergeableVersionedContent`] are grouped by their
|
||||
/// merger and their versions are merged. Other chunks are tracked by path.
|
||||
///
|
||||
/// [`VersionedContent`]: turbopack_core::version::VersionedContent
|
||||
/// [`MergeableVersionedContent`]: turbopack_core::version::MergeableVersionedContent
|
||||
pub async fn compute_chunk_list_version(
|
||||
chunks_contents: &FxIndexMap<String, ResolvedVc<Box<dyn VersionedContent>>>,
|
||||
) -> Result<Vc<ChunkListVersion>> {
|
||||
let mut by_merger = FxIndexMap::<_, Vec<_>>::default();
|
||||
let mut by_path = FxIndexMap::<_, _>::default();
|
||||
|
||||
for (chunk_path, chunk_content) in chunks_contents {
|
||||
if let Some(mergeable) =
|
||||
ResolvedVc::try_sidecast::<Box<dyn MergeableVersionedContent>>(*chunk_content)
|
||||
{
|
||||
let merger = mergeable.get_merger().to_resolved().await?;
|
||||
by_merger.entry(merger).or_default().push(*chunk_content);
|
||||
} else {
|
||||
by_path.insert(
|
||||
chunk_path.clone(),
|
||||
chunk_content.version().into_trait_ref().await?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let by_merger = by_merger
|
||||
.into_iter()
|
||||
.map(|(merger, contents)| (merger, Vc::cell(contents)))
|
||||
.map(async |(merger, contents)| {
|
||||
Ok((
|
||||
merger,
|
||||
merger.merge(contents).version().into_trait_ref().await?,
|
||||
))
|
||||
})
|
||||
.try_join()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
Ok(ChunkListVersion { by_path, by_merger }.cell())
|
||||
}
|
||||
@@ -12,6 +12,7 @@ pub mod annotations;
|
||||
pub mod async_chunk;
|
||||
pub mod bytes_source_transform;
|
||||
pub mod chunk;
|
||||
pub mod chunk_list;
|
||||
pub mod code_gen;
|
||||
pub mod embed_js;
|
||||
mod errors;
|
||||
|
||||
Reference in New Issue
Block a user