Skip to content

Commit

Permalink
Selectively load runtime CSS modules to avoid rule duplication (#4695)
Browse files Browse the repository at this point in the history
### Description

Implementing our style deduplication RFC.

Next.js PR: vercel/next.js#48866

### Testing Instructions

Working on a test case now.

link WEB-951

---------

Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com>
  • Loading branch information
alexkirsz and sokra committed Apr 27, 2023
1 parent f6082b4 commit af5fbbb
Show file tree
Hide file tree
Showing 59 changed files with 2,725 additions and 581 deletions.
4 changes: 4 additions & 0 deletions crates/turbopack-core/src/chunk/mod.rs
Expand Up @@ -133,6 +133,10 @@ pub trait Chunk: Asset {
pub struct OutputChunkRuntimeInfo {
pub included_ids: Option<ModuleIdsVc>,
pub excluded_ids: Option<ModuleIdsVc>,
/// List of paths of chunks containing individual modules that are part of
/// this chunk. This is useful for selectively loading modules from a chunk
/// without loading the whole chunk.
pub module_chunks: Option<AssetsVc>,
pub placeholder_for_future_extensions: (),
}

Expand Down
58 changes: 52 additions & 6 deletions crates/turbopack-css/src/chunk/mod.rs
@@ -1,14 +1,15 @@
pub(crate) mod single_item_chunk;
pub mod source_map;
pub(crate) mod writer;

use std::fmt::Write;

use anyhow::{anyhow, Result};
use indexmap::IndexSet;
use turbo_tasks::{primitives::StringVc, Value, ValueToString};
use turbo_tasks::{primitives::StringVc, TryJoinIterExt, Value, ValueToString};
use turbo_tasks_fs::{rope::Rope, File, FileSystemPathOptionVc};
use turbopack_core::{
asset::{Asset, AssetContentVc, AssetVc},
asset::{Asset, AssetContentVc, AssetVc, AssetsVc},
chunk::{
availability_info::AvailabilityInfo, chunk_content, chunk_content_split, Chunk,
ChunkContentResult, ChunkGroupReferenceVc, ChunkItem, ChunkItemVc, ChunkVc,
Expand All @@ -28,7 +29,10 @@ use turbopack_core::{
};
use writer::expand_imports;

use self::source_map::CssChunkSourceMapAssetReferenceVc;
use self::{
single_item_chunk::{chunk::SingleItemCssChunkVc, reference::SingleItemCssChunkReferenceVc},
source_map::CssChunkSourceMapAssetReferenceVc,
};
use crate::{
embed::{CssEmbed, CssEmbeddable, CssEmbeddableVc},
parse::ParseResultSourceMapVc,
Expand Down Expand Up @@ -302,14 +306,53 @@ impl Chunk for CssChunk {
impl OutputChunk for CssChunk {
#[turbo_tasks::function]
async fn runtime_info(&self) -> Result<OutputChunkRuntimeInfoVc> {
let entries = self
let content = css_chunk_content(
self.context,
self.main_entries,
Value::new(self.availability_info),
)
.await?;
let entries_chunk_items: Vec<_> = self
.main_entries
.await?
.iter()
.map(|&entry| entry.as_chunk_item(self.context).id())
.map(|&entry| entry.as_chunk_item(self.context))
.collect();
let included_ids = entries_chunk_items
.iter()
.map(|chunk_item| chunk_item.id())
.collect();
let imports_chunk_items: Vec<_> = entries_chunk_items
.iter()
.map(|&chunk_item| async move {
Ok(chunk_item
.content()
.await?
.imports
.iter()
.filter_map(|import| {
if let CssImport::Internal(_, item) = import {
Some(*item)
} else {
None
}
})
.collect::<Vec<_>>())
})
.try_join()
.await?
.into_iter()
.flatten()
.collect();
let module_chunks: Vec<_> = content
.chunk_items
.iter()
.chain(imports_chunk_items.iter())
.map(|item| SingleItemCssChunkVc::new(self.context, *item).into())
.collect();
Ok(OutputChunkRuntimeInfo {
included_ids: Some(ModuleIdsVc::cell(entries)),
included_ids: Some(ModuleIdsVc::cell(included_ids)),
module_chunks: Some(AssetsVc::cell(module_chunks)),
..Default::default()
}
.cell())
Expand Down Expand Up @@ -377,6 +420,9 @@ impl Asset for CssChunk {
for entry in content.async_chunk_group_entries.iter() {
references.push(ChunkGroupReferenceVc::new(this.context, *entry).into());
}
for item in content.chunk_items.iter() {
references.push(SingleItemCssChunkReferenceVc::new(this.context, *item).into());
}
if *this
.context
.reference_chunk_source_maps(self_vc.into())
Expand Down
163 changes: 163 additions & 0 deletions crates/turbopack-css/src/chunk/single_item_chunk/chunk.rs
@@ -0,0 +1,163 @@
use std::fmt::Write;

use anyhow::Result;
use turbo_tasks::{primitives::StringVc, ValueToString};
use turbo_tasks_fs::File;
use turbopack_core::{
asset::{Asset, AssetContentVc, AssetVc},
chunk::{Chunk, ChunkItem, ChunkVc, ChunkingContext, ChunkingContextVc},
code_builder::{CodeBuilder, CodeVc},
ident::AssetIdentVc,
introspect::{Introspectable, IntrospectableVc},
reference::AssetReferencesVc,
source_map::{GenerateSourceMap, GenerateSourceMapVc, OptionSourceMapVc},
};

use super::source_map::SingleItemCssChunkSourceMapAssetReferenceVc;
use crate::chunk::{CssChunkItem, CssChunkItemVc};

/// A CSS chunk that only contains a single item. This is used for selectively
/// loading CSS modules that are part of a larger chunk in development mode, and
/// avoiding rule duplication.
#[turbo_tasks::value]
pub struct SingleItemCssChunk {
context: ChunkingContextVc,
item: CssChunkItemVc,
}

#[turbo_tasks::value_impl]
impl SingleItemCssChunkVc {
/// Creates a new [`SingleItemCssChunkVc`].
#[turbo_tasks::function]
pub fn new(context: ChunkingContextVc, item: CssChunkItemVc) -> Self {
SingleItemCssChunk { context, item }.cell()
}
}

#[turbo_tasks::value_impl]
impl SingleItemCssChunkVc {
#[turbo_tasks::function]
async fn code(self) -> Result<CodeVc> {
use std::io::Write;

let this = self.await?;
let mut code = CodeBuilder::default();

let id = &*this.item.id().await?;

writeln!(code, "/* {} */", id)?;
let content = this.item.content().await?;
code.push_source(
&content.inner_code,
content.source_map.map(|sm| sm.as_generate_source_map()),
);

if *this
.context
.reference_chunk_source_maps(self.into())
.await?
&& code.has_source_map()
{
let chunk_path = self.path().await?;
write!(
code,
"\n/*# sourceMappingURL={}.map*/",
chunk_path.file_name()
)?;
}

let c = code.build().cell();
Ok(c)
}
}

#[turbo_tasks::value_impl]
impl Chunk for SingleItemCssChunk {
#[turbo_tasks::function]
fn chunking_context(&self) -> ChunkingContextVc {
self.context
}
}

#[turbo_tasks::function]
fn single_item_modifier() -> StringVc {
StringVc::cell("single item css chunk".to_string())
}

#[turbo_tasks::value_impl]
impl Asset for SingleItemCssChunk {
#[turbo_tasks::function]
async fn ident(&self) -> Result<AssetIdentVc> {
Ok(AssetIdentVc::from_path(
self.context.chunk_path(
self.item
.asset_ident()
.with_modifier(single_item_modifier()),
".css",
),
))
}

#[turbo_tasks::function]
async fn content(self_vc: SingleItemCssChunkVc) -> Result<AssetContentVc> {
let code = self_vc.code().await?;
Ok(File::from(code.source_code().clone()).into())
}

#[turbo_tasks::function]
async fn references(self_vc: SingleItemCssChunkVc) -> Result<AssetReferencesVc> {
let this = self_vc.await?;
let mut references = Vec::new();
if *this
.context
.reference_chunk_source_maps(self_vc.into())
.await?
{
references.push(SingleItemCssChunkSourceMapAssetReferenceVc::new(self_vc).into());
}
Ok(AssetReferencesVc::cell(references))
}
}

#[turbo_tasks::value_impl]
impl GenerateSourceMap for SingleItemCssChunk {
#[turbo_tasks::function]
fn generate_source_map(self_vc: SingleItemCssChunkVc) -> OptionSourceMapVc {
self_vc.code().generate_source_map()
}
}

#[turbo_tasks::function]
fn introspectable_type() -> StringVc {
StringVc::cell("single asset css chunk".to_string())
}

#[turbo_tasks::function]
fn entry_module_key() -> StringVc {
StringVc::cell("entry module".to_string())
}

#[turbo_tasks::value_impl]
impl Introspectable for SingleItemCssChunk {
#[turbo_tasks::function]
fn ty(&self) -> StringVc {
introspectable_type()
}

#[turbo_tasks::function]
fn title(self_vc: SingleItemCssChunkVc) -> StringVc {
self_vc.path().to_string()
}

#[turbo_tasks::function]
async fn details(self_vc: SingleItemCssChunkVc) -> Result<StringVc> {
let this = self_vc.await?;
let mut details = String::new();
write!(
details,
"Chunk item: {}",
this.item.asset_ident().to_string().await?
)?;
Ok(StringVc::cell(details))
}
}
3 changes: 3 additions & 0 deletions crates/turbopack-css/src/chunk/single_item_chunk/mod.rs
@@ -0,0 +1,3 @@
pub(crate) mod chunk;
pub(crate) mod reference;
pub(crate) mod source_map;
57 changes: 57 additions & 0 deletions crates/turbopack-css/src/chunk/single_item_chunk/reference.rs
@@ -0,0 +1,57 @@
use anyhow::Result;
use turbo_tasks::{primitives::StringVc, ValueToString, ValueToStringVc};
use turbopack_core::{
chunk::{
ChunkItem, ChunkableAssetReference, ChunkableAssetReferenceVc, ChunkingContextVc,
ChunkingType, ChunkingTypeOptionVc,
},
reference::{AssetReference, AssetReferenceVc},
resolve::{ResolveResult, ResolveResultVc},
};

use super::chunk::SingleItemCssChunkVc;
use crate::chunk::CssChunkItemVc;

/// A reference to a [`SingleItemCssChunkVc`].
#[turbo_tasks::value]
#[derive(Hash, Debug)]
pub struct SingleItemCssChunkReference {
context: ChunkingContextVc,
item: CssChunkItemVc,
}

#[turbo_tasks::value_impl]
impl SingleItemCssChunkReferenceVc {
/// Creates a new [`SingleItemCssChunkReferenceVc`].
#[turbo_tasks::function]
pub fn new(context: ChunkingContextVc, item: CssChunkItemVc) -> Self {
Self::cell(SingleItemCssChunkReference { context, item })
}
}

#[turbo_tasks::value_impl]
impl AssetReference for SingleItemCssChunkReference {
#[turbo_tasks::function]
fn resolve_reference(&self) -> ResolveResultVc {
ResolveResult::asset(SingleItemCssChunkVc::new(self.context, self.item).into()).cell()
}
}

#[turbo_tasks::value_impl]
impl ValueToString for SingleItemCssChunkReference {
#[turbo_tasks::function]
async fn to_string(&self) -> Result<StringVc> {
Ok(StringVc::cell(format!(
"css single item chunk {}",
self.item.asset_ident().to_string().await?
)))
}
}

#[turbo_tasks::value_impl]
impl ChunkableAssetReference for SingleItemCssChunkReference {
#[turbo_tasks::function]
fn chunking_type(&self) -> Result<ChunkingTypeOptionVc> {
Ok(ChunkingTypeOptionVc::cell(Some(ChunkingType::Separate)))
}
}

0 comments on commit af5fbbb

Please sign in to comment.