Skip to content

Commit

Permalink
feat(rspack_core): use simple string parsing instead of regex
Browse files Browse the repository at this point in the history
  • Loading branch information
shulaoda committed Oct 14, 2024
1 parent c3a09fd commit dc91206
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 144 deletions.
119 changes: 53 additions & 66 deletions crates/rspack_core/src/options/filename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,26 @@ use std::sync::Arc;
use std::sync::LazyLock;
use std::{borrow::Cow, convert::Infallible, ptr};

use regex::{NoExpand, Regex};
use regex::Regex;
use rspack_error::error;
use rspack_macros::MergeFrom;
use rspack_util::atom::Atom;
use rspack_util::ext::CowExt;
use rspack_util::MergeFrom;

use crate::replace_all_hash_pattern;
use crate::replace_all_placeholder;
use crate::{parse_resource, AssetInfo, PathData, ResourceParsedData};

pub static FILE_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[file\]").expect("Should generate regex"));
pub static BASE_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[base\]").expect("Should generate regex"));
pub static NAME_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[name\]").expect("Should generate regex"));
pub static PATH_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[path\]").expect("Should generate regex"));
pub static EXT_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[ext\]").expect("Should generate regex"));
pub static QUERY_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[query\]").expect("Should generate regex"));
pub static FRAGMENT_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[fragment\]").expect("Should generate regex"));
pub static ID_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[id\]").expect("Should generate regex"));
pub static RUNTIME_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[runtime\]").expect("Should generate regex"));
pub static URL_PLACEHOLDER: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[url\]").expect("Should generate regex"));
static FILE_PLACEHOLDER: &str = "[file]";
static BASE_PLACEHOLDER: &str = "[base]";
static NAME_PLACEHOLDER: &str = "[name]";
static PATH_PLACEHOLDER: &str = "[path]";
static EXT_PLACEHOLDER: &str = "[ext]";
static QUERY_PLACEHOLDER: &str = "[query]";
static FRAGMENT_PLACEHOLDER: &str = "[fragment]";
static ID_PLACEHOLDER: &str = "[id]";
static RUNTIME_PLACEHOLDER: &str = "[runtime]";
static URL_PLACEHOLDER: &str = "[url]";

pub static HASH_PLACEHOLDER: &str = "[hash]";
pub static FULL_HASH_PLACEHOLDER: &str = "[fullhash]";
Expand Down Expand Up @@ -247,14 +237,18 @@ fn render_template(
)
.map(|exts| exts[0]);
t = t
.map(|t| FILE_PLACEHOLDER.replace_all(t, ""))
.map(|t| QUERY_PLACEHOLDER.replace_all(t, ""))
.map(|t| FRAGMENT_PLACEHOLDER.replace_all(t, ""))
.map(|t| PATH_PLACEHOLDER.replace_all(t, ""))
.map(|t| BASE_PLACEHOLDER.replace_all(t, ""))
.map(|t| NAME_PLACEHOLDER.replace_all(t, ""))
.map(|t| replace_all_placeholder(t, FILE_PLACEHOLDER, ""))
.map(|t| replace_all_placeholder(t, QUERY_PLACEHOLDER, ""))
.map(|t| replace_all_placeholder(t, FRAGMENT_PLACEHOLDER, ""))
.map(|t| replace_all_placeholder(t, PATH_PLACEHOLDER, ""))
.map(|t| replace_all_placeholder(t, BASE_PLACEHOLDER, ""))
.map(|t| replace_all_placeholder(t, NAME_PLACEHOLDER, ""))
.map(|t| {
EXT_PLACEHOLDER.replace_all(t, &ext.map(|ext| format!(".{}", ext)).unwrap_or_default())
replace_all_placeholder(
t,
EXT_PLACEHOLDER,
&ext.map(|ext| format!(".{}", ext)).unwrap_or_default(),
)
});
} else if let Some(ResourceParsedData {
path: file,
Expand All @@ -263,41 +257,39 @@ fn render_template(
}) = parse_resource(filename)
{
t = t
.map(|t| FILE_PLACEHOLDER.replace_all(t, NoExpand(file.as_str())))
.map(|t| replace_all_placeholder(t, FILE_PLACEHOLDER, file.as_str()))
.map(|t| {
EXT_PLACEHOLDER.replace_all(
replace_all_placeholder(
t,
NoExpand(
&file
.extension()
.map(|p| format!(".{p}"))
.unwrap_or_default(),
),
EXT_PLACEHOLDER,
&file
.extension()
.map(|p| format!(".{p}"))
.unwrap_or_default(),
)
});

if let Some(base) = file.file_name() {
t = t.map(|t| BASE_PLACEHOLDER.replace_all(t, NoExpand(base)));
t = t.map(|t| replace_all_placeholder(t, BASE_PLACEHOLDER, base));
}
if let Some(name) = file.file_stem() {
t = t.map(|t| NAME_PLACEHOLDER.replace_all(t, NoExpand(name)));
t = t.map(|t| replace_all_placeholder(t, NAME_PLACEHOLDER, name));
}
t = t
.map(|t| {
PATH_PLACEHOLDER.replace_all(
replace_all_placeholder(
t,
NoExpand(
&file
.parent()
// "" -> "", "folder" -> "folder/"
.filter(|p| !p.as_str().is_empty())
.map(|p| p.as_str().to_owned() + "/")
.unwrap_or_default(),
),
PATH_PLACEHOLDER,
&file
.parent()
// "" -> "", "folder" -> "folder/"
.filter(|p| !p.as_str().is_empty())
.map(|p| p.as_str().to_owned() + "/")
.unwrap_or_default(),
)
})
.map(|t| QUERY_PLACEHOLDER.replace_all(t, NoExpand(&query.unwrap_or_default())))
.map(|t| FRAGMENT_PLACEHOLDER.replace_all(t, NoExpand(&fragment.unwrap_or_default())));
.map(|t| replace_all_placeholder(t, QUERY_PLACEHOLDER, &query.unwrap_or_default()))
.map(|t| replace_all_placeholder(t, FRAGMENT_PLACEHOLDER, &fragment.unwrap_or_default()));
}
}
if let Some(content_hash) = options.content_hash.or_else(|| {
Expand All @@ -313,69 +305,64 @@ fn render_template(
asset_info.version = content_hash.to_string();
}
t = t.map(|t| {
replace_all_hash_pattern(t, CONTENT_HASH_PLACEHOLDER, |len| {
replace_all_placeholder(t, CONTENT_HASH_PLACEHOLDER, |len| {
let hash: &str = &content_hash[..hash_len(content_hash, len)];
if let Some(asset_info) = asset_info.as_mut() {
asset_info.set_immutable(Some(true));
asset_info.set_content_hash(hash.to_owned());
}
hash
})
.map_or(Cow::Borrowed(t), Cow::Owned)
});
}
if let Some(hash) = options.hash {
for key in [HASH_PLACEHOLDER, FULL_HASH_PLACEHOLDER] {
t = t.map(|t| {
replace_all_hash_pattern(t, key, |len| {
replace_all_placeholder(t, key, |len| {
let hash = &hash[..hash_len(hash, len)];
if let Some(asset_info) = asset_info.as_mut() {
asset_info.set_immutable(Some(true));
asset_info.set_full_hash(hash.to_owned());
}
hash
})
.map_or(Cow::Borrowed(t), Cow::Owned)
});
}
}
if let Some(chunk) = options.chunk {
if let Some(id) = &options.id {
t = t.map(|t| ID_PLACEHOLDER.replace_all(t, NoExpand(id)));
} else if let Some(id) = &chunk.id {
t = t.map(|t| ID_PLACEHOLDER.replace_all(t, NoExpand(id)));
if let Some(id) = options.id.or(chunk.id.as_deref()) {
t = t.map(|t| replace_all_placeholder(t, ID_PLACEHOLDER, id));
}
if let Some(name) = chunk.name_for_filename_template() {
t = t.map(|t| NAME_PLACEHOLDER.replace_all(t, NoExpand(name)));
t = t.map(|t| replace_all_placeholder(t, NAME_PLACEHOLDER, name));
}
if let Some(d) = chunk.rendered_hash.as_ref() {
t = t.map(|t| {
let hash = &**d;
replace_all_hash_pattern(t, CHUNK_HASH_PLACEHOLDER, |len| {
replace_all_placeholder(t, CHUNK_HASH_PLACEHOLDER, |len| {
let hash: &str = &hash[..hash_len(hash, len)];
if let Some(asset_info) = asset_info.as_mut() {
asset_info.set_immutable(Some(true));
asset_info.set_chunk_hash(hash.to_owned());
}
hash
})
.map_or(Cow::Borrowed(t), Cow::Owned)
});
}
}

if let Some(id) = &options.id {
t = t.map(|t| ID_PLACEHOLDER.replace_all(t, NoExpand(id)));
if let Some(id) = options.id {
t = t.map(|t| replace_all_placeholder(t, ID_PLACEHOLDER, id));
} else if let Some(module) = options.module {
if let Some(chunk_graph) = options.chunk_graph {
if let Some(id) = chunk_graph.get_module_id(module.identifier()) {
t = t.map(|t| ID_PLACEHOLDER.replace_all(t, NoExpand(id)));
t = t.map(|t| replace_all_placeholder(t, ID_PLACEHOLDER, id));
}
}
}
t = t.map(|t| RUNTIME_PLACEHOLDER.replace_all(t, NoExpand(options.runtime.unwrap_or("_"))));
t = t.map(|t| replace_all_placeholder(t, RUNTIME_PLACEHOLDER, options.runtime.unwrap_or("_")));
if let Some(url) = options.url {
t = t.map(|t| URL_PLACEHOLDER.replace_all(t, NoExpand(url)));
t = t.map(|t| replace_all_placeholder(t, URL_PLACEHOLDER, url));
}
t.into_owned()
}
106 changes: 106 additions & 0 deletions crates/rspack_core/src/utils/hash.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

Expand All @@ -6,3 +7,108 @@ pub fn calc_hash<T: Hash>(t: &T) -> u64 {
t.hash(&mut s);
s.finish()
}

pub struct ExtractedHashPattern {
pub pattern: String,
pub len: Option<usize>,
}

pub trait Replacer {
fn get_replacer(&mut self, hash_len: Option<usize>) -> Cow<'_, str>;
}

impl<'a> Replacer for &'a str {
#[inline]
fn get_replacer(&mut self, _: Option<usize>) -> Cow<'_, str> {
Cow::Borrowed(self)
}
}

impl<'a> Replacer for &'a String {
#[inline]
fn get_replacer(&mut self, _: Option<usize>) -> Cow<'_, str> {
Cow::Borrowed(self.as_str())
}
}

impl<F, S> Replacer for F
where
F: FnMut(Option<usize>) -> S,
S: AsRef<str>,
{
#[inline]
fn get_replacer(&mut self, hash_len: Option<usize>) -> Cow<'_, str> {
Cow::Owned((*self)(hash_len).as_ref().to_string())
}
}

/// Extract `[hash]` or `[hash:8]` in the template
pub fn extract_hash_pattern(pattern: &str, key: &str) -> Option<ExtractedHashPattern> {
let key_offset = key.len() - 1;
let start = pattern.find(&key[..key_offset])?;
let end = pattern[start + key_offset..].find(']')?;
let len = pattern[start + key_offset..start + key_offset + end]
.strip_prefix(':')
.and_then(|n| n.parse::<usize>().ok());

let pattern = &pattern[start..=start + key_offset + end];
Some(ExtractedHashPattern {
pattern: pattern.to_string(),
len,
})
}

/// Replace all `[placeholder]` or `[placeholder:8]` in the pattern
pub fn replace_all_placeholder<'a>(
pattern: &'a str,
placeholder: &'a str,
mut replacer: impl Replacer,
) -> Cow<'a, str> {
let offset = placeholder.len() - 1;
let mut iter = pattern.match_indices(&placeholder[..offset]).peekable();

if iter.peek().is_none() {
return Cow::Borrowed(pattern);
}

let mut ending = 0;
let mut result = String::with_capacity(pattern.len());

for (start, _) in iter {
if start < ending {
continue;
}

let start_offset = start + offset;
if let Some(end) = pattern[start_offset..].find(']') {
let end = start_offset + end;

let replacer = replacer.get_replacer(
pattern[start_offset..end]
.strip_prefix(':')
.and_then(|n| n.parse::<usize>().ok()),
);

result.push_str(&pattern[ending..start]);
result.push_str(replacer.as_ref());

ending = end + 1;
}
}

if ending < pattern.len() {
result.push_str(&pattern[ending..]);
}

Cow::Owned(result)
}

#[test]
fn test_replace_all_placeholder() {
let result = replace_all_placeholder("hello-[hash].js", "[hash]", "abc");
assert_eq!(result, "hello-abc.js");
let result = replace_all_placeholder("hello-[hash]-[hash:5].js", "[hash]", |n: Option<usize>| {
&"abcdefgh"[..n.unwrap_or(8)]
});
assert_eq!(result, "hello-abcdefgh-abcde.js");
}
Loading

0 comments on commit dc91206

Please sign in to comment.