Skip to content

Commit 5f0f57c

Browse files
committed
Optimize
1 parent 6d9aadb commit 5f0f57c

6 files changed

Lines changed: 140 additions & 82 deletions

File tree

bindings/devup-ui-wasm/src/lib.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use css::class_map::{get_class_map, set_class_map};
2-
use css::file_map::{get_file_map, get_filename_by_file_num, set_file_map};
1+
use css::class_map::{set_class_map, with_class_map};
2+
use css::file_map::{set_file_map, with_file_map};
33
use extractor::extract_style::extract_style_value::ExtractStyleValue;
44
use extractor::{ExtractOption, ImportAlias, extract, has_devup_ui};
55
use rustc_hash::FxHashSet;
@@ -212,7 +212,7 @@ pub fn export_sheet() -> Result<String, JsValue> {
212212

213213
/// Internal function to export class map as JSON string (testable without `JsValue`)
214214
pub fn export_class_map_internal() -> Result<String, String> {
215-
serde_json::to_string(&get_class_map()).map_err(|e| e.to_string())
215+
with_class_map(serde_json::to_string).map_err(|e| e.to_string())
216216
}
217217

218218
#[wasm_bindgen(js_name = "importClassMap")]
@@ -230,7 +230,7 @@ pub fn export_class_map() -> Result<String, JsValue> {
230230

231231
/// Internal function to export file map as JSON string (testable without `JsValue`)
232232
pub fn export_file_map_internal() -> Result<String, String> {
233-
serde_json::to_string(&get_file_map()).map_err(|e| e.to_string())
233+
with_file_map(serde_json::to_string).map_err(|e| e.to_string())
234234
}
235235

236236
#[wasm_bindgen(js_name = "importFileMap")]
@@ -349,10 +349,16 @@ pub fn get_default_theme() -> Result<Option<String>, JsValue> {
349349
#[cfg(not(tarpaulin_include))]
350350
pub fn get_css(file_num: Option<usize>, import_main_css: bool) -> Result<String, JsValue> {
351351
Ok(with_style_sheet(|sheet| {
352-
sheet.create_css(
353-
file_num.map(get_filename_by_file_num).as_deref(),
354-
import_main_css,
355-
)
352+
if let Some(file_num) = file_num {
353+
with_file_map(|map| {
354+
sheet.create_css(
355+
map.get_by_right(&file_num).map(String::as_str),
356+
import_main_css,
357+
)
358+
})
359+
} else {
360+
sheet.create_css(None, import_main_css)
361+
}
356362
}))
357363
}
358364

libs/css/src/file_map.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ static GLOBAL_FILE_MAP: LazyLock<Mutex<BiHashMap<String, usize>>> =
1919
LazyLock::new(|| Mutex::new(BiHashMap::new()));
2020

2121
#[inline]
22-
fn with_file_map<F, R>(f: F) -> R
22+
pub fn with_file_map<F, R>(f: F) -> R
2323
where
2424
F: FnOnce(&BiHashMap<String, usize>) -> R,
2525
{

libs/extractor/src/gen_class_name.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,24 @@ pub fn merge_expression_for_class_name<'a>(
162162
ast_builder: &AstBuilder<'a>,
163163
expressions: Vec<Expression<'a>>,
164164
) -> Option<Expression<'a>> {
165-
let mut class_names = vec![];
166165
let mut unknown_expr = vec![];
166+
let mut class_name = String::new();
167167
for expr in expressions {
168168
if let Expression::StringLiteral(str) = &expr {
169-
class_names.push(str.value.trim().to_string());
169+
let value = str.value.trim();
170+
if !value.is_empty() {
171+
if !class_name.is_empty() {
172+
class_name.push(' ');
173+
}
174+
class_name.push_str(value);
175+
}
170176
} else {
171177
unknown_expr.push(expr);
172178
}
173179
}
174-
if unknown_expr.is_empty() && class_names.is_empty() {
180+
if unknown_expr.is_empty() && class_name.is_empty() {
175181
return None;
176182
}
177-
let mut class_name = class_names.join(" ");
178183
if !unknown_expr.is_empty() {
179184
if class_name.is_empty() && unknown_expr.len() == 1 {
180185
Some(unknown_expr.remove(0))

libs/extractor/src/prop_modify_utils.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -531,33 +531,39 @@ fn rebuild_expression_with_mapping<'a>(
531531

532532
/// Extract all class name strings from a template literal, including from conditional expressions
533533
fn extract_all_classes_from_template_literal(template: &oxc_ast::ast::TemplateLiteral) -> String {
534-
let mut classes = Vec::new();
534+
let mut classes = String::new();
535535

536536
// Extract from quasis (static parts of template literal)
537537
for quasi in &template.quasis {
538538
let raw = quasi.value.raw.as_str();
539-
if !raw.trim().is_empty() {
540-
classes.push(raw.trim().to_string());
541-
}
539+
push_class_segment(&mut classes, raw.trim());
542540
}
543541

544542
// Extract from expressions (dynamic parts)
545543
for expr in &template.expressions {
546544
extract_classes_from_expression(expr, &mut classes);
547545
}
548546

549-
classes.join(" ")
547+
classes
548+
}
549+
550+
fn push_class_segment(classes: &mut String, value: &str) {
551+
if value.is_empty() {
552+
return;
553+
}
554+
if !classes.is_empty() {
555+
classes.push(' ');
556+
}
557+
classes.push_str(value);
550558
}
551559

552560
/// Recursively extract class name strings from an expression
553-
fn extract_classes_from_expression(expr: &Expression, classes: &mut Vec<String>) {
561+
fn extract_classes_from_expression(expr: &Expression, classes: &mut String) {
554562
match expr {
555563
// Direct string literal: 'text-red-500'
556564
Expression::StringLiteral(lit) => {
557565
let value = lit.value.as_str().trim();
558-
if !value.is_empty() {
559-
classes.push(value.to_string());
560-
}
566+
push_class_segment(classes, value);
561567
}
562568
// Ternary/conditional: cond ? 'text-red' : 'text-blue'
563569
Expression::ConditionalExpression(cond) => {
@@ -576,9 +582,7 @@ fn extract_classes_from_expression(expr: &Expression, classes: &mut Vec<String>)
576582
// Template literal inside expression
577583
Expression::TemplateLiteral(inner_template) => {
578584
let inner_classes = extract_all_classes_from_template_literal(inner_template);
579-
if !inner_classes.is_empty() {
580-
classes.push(inner_classes);
581-
}
585+
push_class_segment(classes, &inner_classes);
582586
}
583587
// Other expressions (variables, function calls, etc.) - skip, can't extract statically
584588
_ => {}

libs/extractor/src/vanilla_extract.rs

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -687,23 +687,10 @@ fn remap_style_names(
687687
})
688688
.collect();
689689

690-
// Replace __font_N__ placeholders in style JSON with font-family names
690+
// Replace placeholders in a single pass per style JSON.
691+
// This is needed for font-family references and selectors like `${parent}:hover &`.
691692
for entry in new_styles.values_mut() {
692-
for (placeholder, font_family) in &font_family_map {
693-
if entry.json.contains(placeholder) {
694-
entry.json = entry.json.replace(placeholder, font_family);
695-
}
696-
}
697-
}
698-
699-
// Replace __style_N__ placeholders in style JSON with variable names
700-
// This is needed for selectors that reference other styles like `${parent}:hover &`
701-
for entry in new_styles.values_mut() {
702-
for (placeholder, var_name) in &placeholder_to_name {
703-
if entry.json.contains(placeholder) {
704-
entry.json = entry.json.replace(placeholder, var_name);
705-
}
706-
}
693+
replace_placeholders_in_json(&mut entry.json, &font_family_map, &placeholder_to_name);
707694
}
708695

709696
collected.styles = new_styles;
@@ -717,6 +704,45 @@ fn remap_style_names(
717704
collected.themes = new_themes;
718705
}
719706

707+
fn replace_placeholders_in_json(
708+
json: &mut String,
709+
font_family_map: &FxHashMap<&str, &str>,
710+
placeholder_to_name: &FxHashMap<String, String>,
711+
) {
712+
let source = json.as_str();
713+
let mut search_start = 0;
714+
let mut last_copied = 0;
715+
let mut output = None::<String>;
716+
717+
while let Some(relative_start) = source[search_start..].find("__") {
718+
let start = search_start + relative_start;
719+
let Some(relative_end) = source[start + 2..].find("__") else {
720+
break;
721+
};
722+
let end = start + 2 + relative_end + 2;
723+
let placeholder = &source[start..end];
724+
let replacement = font_family_map
725+
.get(placeholder)
726+
.copied()
727+
.or_else(|| placeholder_to_name.get(placeholder).map(String::as_str));
728+
729+
if let Some(replacement) = replacement {
730+
let output = output.get_or_insert_with(|| String::with_capacity(source.len()));
731+
output.push_str(&source[last_copied..start]);
732+
output.push_str(replacement);
733+
last_copied = end;
734+
search_start = end;
735+
} else {
736+
search_start = start + 2;
737+
}
738+
}
739+
740+
if let Some(mut output) = output {
741+
output.push_str(&source[last_copied..]);
742+
*json = output;
743+
}
744+
}
745+
720746
/// Convert TypeScript to JavaScript using Oxc Transformer and replace imports
721747
fn preprocess_typescript(code: &str, package: &str) -> String {
722748
let allocator = Allocator::default();

0 commit comments

Comments
 (0)