Skip to content

Commit

Permalink
refactor: css loading (#8534)
Browse files Browse the repository at this point in the history
  • Loading branch information
JSerFeng authored Nov 27, 2024
1 parent deed009 commit a8fc586
Show file tree
Hide file tree
Showing 59 changed files with 259 additions and 390 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/rspack_plugin_css/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#![feature(option_get_or_insert_default)]

pub mod dependency;
mod parser_and_generator;
pub mod parser_and_generator;
pub mod plugin;
pub mod runtime;
mod utils;
Expand Down
133 changes: 52 additions & 81 deletions crates/rspack_plugin_css/src/parser_and_generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use rustc_hash::FxHashSet;

use crate::{
dependency::CssSelfReferenceLocalIdentDependency,
utils::{css_modules_exports_to_string, escape_css, LocalIdentOptions},
utils::{css_modules_exports_to_string, LocalIdentOptions},
};
use crate::{
dependency::CssSelfReferenceLocalIdentReplacement,
Expand All @@ -47,7 +47,8 @@ static REGEX_IS_MODULES: LazyLock<Regex> =
static REGEX_IS_COMMENTS: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"/\*[\s\S]*?\*/").expect("Invalid regex"));

pub(crate) static CSS_MODULE_SOURCE_TYPE_LIST: &[SourceType; 1] = &[SourceType::Css];
pub(crate) static CSS_MODULE_SOURCE_TYPE_LIST: &[SourceType; 2] =
&[SourceType::Css, SourceType::JavaScript];

pub(crate) static CSS_MODULE_EXPORTS_ONLY_SOURCE_TYPE_LIST: &[SourceType; 1] =
&[SourceType::JavaScript];
Expand Down Expand Up @@ -79,6 +80,7 @@ pub struct CssParserAndGenerator {
pub named_exports: bool,
pub es_module: bool,
pub exports: Option<CssExports>,
pub hot: bool,
}

impl ParserAndGenerator for CssParserAndGenerator {
Expand Down Expand Up @@ -443,71 +445,6 @@ impl ParserAndGenerator for CssParserAndGenerator {
data: generate_context.data,
};

let identifier = module.identifier();
let module_id = compilation
.chunk_graph
.get_module_id(identifier)
.unwrap_or_default();

if let Some(exports) = &self.exports {
let mg = compilation.get_module_graph();
let unused = get_unused_local_ident(exports, identifier, generate_context.runtime, &mg);
context.data.insert(unused);

let used = get_used_exports(exports, identifier, generate_context.runtime, &mg);

static RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"\\"#).expect("should compile"));
let module_id = RE.replace_all(module_id, "/");

let meta_data = used
.iter()
.map(|(n, v)| {
let escaped = escape_css(n, false);
v.iter()
.map(|v| {
let composed = &v.id;

if let Some(composed) = composed {
let mg = compilation.get_module_graph();
let module = mg
.get_module_by_dependency_id(composed)
.expect("should have from dependency");
let module_id = compilation
.chunk_graph
.get_module_id(module.identifier())
.expect("should have module id");

format!(
"{}:{}@{}/",
escaped,
escape_css(module_id, false),
escape_css(&v.ident, false)
)
} else {
format!("{}:{}/", escaped, escape_css(&v.ident, false))
}
})
.collect::<Vec<_>>()
.join("")
})
.collect::<Vec<_>>()
.join("");

context.data.insert(CssUsedExports(format!(
"{}{}{}",
meta_data,
if self.es_module { "&" } else { "" },
escape_css(&module_id, false)
)));
} else {
context.data.insert(CssUsedExports(format!(
"{}{}",
if self.es_module { "&" } else { "" },
escape_css(module_id, false)
)));
}

module.get_dependencies().iter().for_each(|id| {
if let Some(dependency) = compilation
.get_module_graph()
Expand All @@ -530,11 +467,15 @@ impl ParserAndGenerator for CssParserAndGenerator {
Ok(source.boxed())
}
SourceType::JavaScript => {
let with_hmr = self.hot;
let exports = if generate_context.concatenation_scope.is_some() {
// currently this is dead branch, as css module will never be concatenated expect exportsOnly
let mut concate_source = ConcatSource::default();
if let Some(ref exports) = self.exports {
let mg = generate_context.compilation.get_module_graph();

let unused_exports =
get_unused_local_ident(exports, module.identifier(), generate_context.runtime, &mg);
generate_context.data.insert(unused_exports);
let exports =
get_used_exports(exports, module.identifier(), generate_context.runtime, &mg);

Expand All @@ -560,20 +501,47 @@ impl ParserAndGenerator for CssParserAndGenerator {
("", "", "")
};
if let Some(exports) = &self.exports {
let unused_exports =
get_unused_local_ident(exports, module.identifier(), generate_context.runtime, &mg);
generate_context.data.insert(unused_exports);

let exports =
get_used_exports(exports, module.identifier(), generate_context.runtime, &mg);

css_modules_exports_to_string(
exports,
module,
generate_context.compilation,
generate_context.runtime_requirements,
if with_hmr {
format!(
"{}\nmodule.hot.accept();\n",
css_modules_exports_to_string(
exports,
module,
generate_context.compilation,
generate_context.runtime_requirements,
ns_obj,
left,
right,
)?
)
} else {
css_modules_exports_to_string(
exports,
module,
generate_context.compilation,
generate_context.runtime_requirements,
ns_obj,
left,
right,
)?
}
} else {
format!(
"{}{}module.exports = {{}}{};\n{}",
ns_obj,
left,
right,
)?
} else {
format!("{}{}module.exports = {{}}{};\n", ns_obj, left, right)
with_hmr
.then_some("module.hot.accept();\n")
.unwrap_or_default()
)
}
};
generate_context
Expand Down Expand Up @@ -601,7 +569,13 @@ impl ParserAndGenerator for CssParserAndGenerator {
_mg: &ModuleGraph,
_cg: &ChunkGraph,
) -> Option<Cow<'static, str>> {
Some("Module Concatenation is not implemented for CssParserAndGenerator".into())
if self.exports_only {
None
} else {
// CSS Module cannot be concatenated as it must appear in css chunk, if it's
// concatenated, it will be removed from module graph
Some("Module Concatenation is not implemented for CssParserAndGenerator".into())
}
}

fn update_hash(
Expand All @@ -628,7 +602,7 @@ fn get_used_exports<'a>(
let export_info = mg.get_read_only_export_info(&identifier, name.as_str().into());

if let Some(export_info) = export_info {
!matches!(export_info.get_used(mg, runtime), UsageState::Unused)
export_info.get_used(mg, runtime) != UsageState::Unused
} else {
true
}
Expand Down Expand Up @@ -668,6 +642,3 @@ fn get_unused_local_ident(
.collect(),
}
}

#[derive(Debug, Clone)]
pub struct CssUsedExports(pub String);
77 changes: 6 additions & 71 deletions crates/rspack_plugin_css/src/plugin/impl_plugin_for_css_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ use rspack_hook::plugin_hook;
use rspack_plugin_runtime::is_enabled_for_chunk;
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};

use crate::parser_and_generator::{
CodeGenerationDataUnusedLocalIdent, CssParserAndGenerator, CssUsedExports,
};
use crate::parser_and_generator::{CodeGenerationDataUnusedLocalIdent, CssParserAndGenerator};
use crate::runtime::CssLoadingRuntimeModule;
use crate::utils::{escape_css, AUTO_PUBLIC_PATH_PLACEHOLDER};
use crate::utils::AUTO_PUBLIC_PATH_PLACEHOLDER;
use crate::{plugin::CssPluginInner, CssPlugin};

struct CssModuleDebugInfo<'a> {
Expand Down Expand Up @@ -122,18 +120,13 @@ impl CssPlugin {
chunk: &Chunk,
ordered_css_modules: &[&dyn Module],
) -> rspack_error::Result<ConcatSource> {
let mut meta_data = vec![];
let with_compression = compilation.options.output.css_head_data_compression;
let module_sources = ordered_css_modules
.iter()
.map(|module| {
let module_id = &module.identifier();
let code_gen_result = compilation
.code_generation_results
.get(module_id, Some(chunk.runtime()));
if let Some(meta_data_str) = code_gen_result.data.get::<CssUsedExports>() {
meta_data.push(meta_data_str.0.as_str());
}

Ok(
code_gen_result
Expand All @@ -143,7 +136,7 @@ impl CssPlugin {
})
.collect::<Result<Vec<_>>>()?;

let mut source = module_sources
let source = module_sources
.into_par_iter()
// TODO(hyf0): I couldn't think of a situation where a module doesn't have `Source`.
// Should we return a Error if there is a `None` in `module_sources`?
Expand All @@ -165,23 +158,6 @@ impl CssPlugin {
acc
});

let name_with_id = format!(
"{}-{}",
&compilation.options.output.unique_name,
chunk.id().unwrap_or_default()
);
let meta_data_str = format!(
"head{{--webpack-{}:{};}}",
escape_css(&name_with_id, true),
if with_compression {
lzw_encode(&meta_data.join(","))
} else {
meta_data.join(",")
}
);

source.add(RawSource::from(meta_data_str));

Ok(source)
}

Expand Down Expand Up @@ -321,50 +297,6 @@ async fn content_hash(
Ok(())
}

fn lzw_encode(input: &str) -> String {
if input.is_empty() {
return input.into();
}
let mut map: HashMap<String, char> = HashMap::default();
let mut encoded = String::new();
let mut phrase = input.chars().next().expect("should have value").to_string();
let mut code = 256u16;
let max_code = 0xFFFF;

for c in input.chars().skip(1) {
let next_phrase = format!("{}{}", phrase, c);
if map.contains_key(&next_phrase) {
phrase = next_phrase;
} else {
if phrase.len() > 1 {
encoded.push(*map.get(&phrase).expect("should convert to u32 correctly"));
} else {
encoded += &phrase;
}
if code <= max_code {
map.insert(
next_phrase,
std::char::from_u32(code as u32).expect("should convert to u32 correctly"),
);
code += 1;
}
if code > max_code {
code = 256;
map.clear();
}
phrase = c.to_string();
}
}

if phrase.len() > 1 {
encoded.push(*map.get(&phrase).expect("should have phrase"));
} else {
encoded += &phrase;
}

encoded
}

#[plugin_hook(CompilationRenderManifest for CssPlugin)]
async fn render_manifest(
&self,
Expand Down Expand Up @@ -491,6 +423,7 @@ impl Plugin for CssPlugin {
exports_only: g.exports_only.expect("should have exports_only"),
named_exports: p.named_exports.expect("should have named_exports"),
es_module: g.es_module.expect("should have es_module"),
hot: false,
}) as Box<dyn ParserAndGenerator>
}),
);
Expand All @@ -517,6 +450,7 @@ impl Plugin for CssPlugin {
exports_only: g.exports_only.expect("should have exports_only"),
named_exports: p.named_exports.expect("should have named_exports"),
es_module: g.es_module.expect("should have es_module"),
hot: false,
}) as Box<dyn ParserAndGenerator>
}),
);
Expand All @@ -543,6 +477,7 @@ impl Plugin for CssPlugin {
exports_only: g.exports_only.expect("should have exports_only"),
named_exports: p.named_exports.expect("should have named_exports"),
es_module: g.es_module.expect("should have es_module"),
hot: false,
}) as Box<dyn ParserAndGenerator>
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var applyHandler = function (options) {
}
while (newTags.length) {
var info = newTags.pop();
var chunkModuleIds = loadCssChunkData(__webpack_require__.m, info[1], info[0]);
var chunkModuleIds = loadCssChunkData(__webpack_require__.m, info[0]);
chunkModuleIds.forEach(function(id) {
moduleIds.push(id)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ __webpack_require__.f.css = function (chunkId, promises, fetchPriority) {
error.request = realSrc;
installedChunkData[1](error);
} else {
loadCssChunkData(__webpack_require__.m, link, chunkId);
loadCssChunkData(__webpack_require__.m, chunkId);
installedChunkData[0]();
}
}
Expand Down
Loading

2 comments on commit a8fc586

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2024-11-27 c1e8f11) Current Change
10000_big_production-mode_disable-minimize + exec 40.2 s ± 1.43 s 42.7 s ± 535 ms +6.20 %
10000_development-mode + exec 1.82 s ± 32 ms 1.78 s ± 28 ms -2.13 %
10000_development-mode_hmr + exec 644 ms ± 15 ms 644 ms ± 6.4 ms 0.00 %
10000_production-mode + exec 2.41 s ± 42 ms 2.41 s ± 57 ms +0.07 %
arco-pro_development-mode + exec 1.79 s ± 61 ms 1.76 s ± 74 ms -1.56 %
arco-pro_development-mode_hmr + exec 429 ms ± 2.1 ms 425 ms ± 1.5 ms -0.82 %
arco-pro_production-mode + exec 3.1 s ± 93 ms 3.09 s ± 84 ms -0.35 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.17 s ± 67 ms 3.17 s ± 62 ms -0.15 %
threejs_development-mode_10x + exec 1.62 s ± 16 ms 1.63 s ± 12 ms +0.15 %
threejs_development-mode_10x_hmr + exec 811 ms ± 12 ms 814 ms ± 8.2 ms +0.36 %
threejs_production-mode_10x + exec 4.9 s ± 36 ms 4.94 s ± 59 ms +0.67 %
10000_big_production-mode_disable-minimize + rss memory 12451 MiB ± 95.7 MiB 13707 MiB ± 304 MiB +10.08 %
10000_development-mode + rss memory 742 MiB ± 18.2 MiB 776 MiB ± 15 MiB +4.57 %
10000_development-mode_hmr + rss memory 1578 MiB ± 403 MiB 2095 MiB ± 360 MiB +32.81 %
10000_production-mode + rss memory 679 MiB ± 64.6 MiB 670 MiB ± 40.3 MiB -1.37 %
arco-pro_development-mode + rss memory 701 MiB ± 43.4 MiB 719 MiB ± 36.1 MiB +2.59 %
arco-pro_development-mode_hmr + rss memory 871 MiB ± 88.7 MiB 921 MiB ± 85.6 MiB +5.70 %
arco-pro_production-mode + rss memory 876 MiB ± 56.8 MiB 857 MiB ± 61.5 MiB -2.14 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 859 MiB ± 52.9 MiB 878 MiB ± 68.8 MiB +2.21 %
threejs_development-mode_10x + rss memory 785 MiB ± 49.8 MiB 816 MiB ± 43.7 MiB +3.98 %
threejs_development-mode_10x_hmr + rss memory 1478 MiB ± 164 MiB 1869 MiB ± 400 MiB +26.42 %
threejs_production-mode_10x + rss memory 1044 MiB ± 66.8 MiB 1049 MiB ± 95 MiB +0.50 %

Threshold exceeded: ["10000_big_production-mode_disable-minimize + exec","10000_development-mode_hmr + rss memory"]

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ran ecosystem CI: Open

suite result
modernjs ✅ success
_selftest ✅ success
rsdoctor ✅ success
rspress ✅ success
rslib ❌ failure
rsbuild ✅ success
examples ✅ success
devserver ✅ success
nuxt ✅ success

Please sign in to comment.