Skip to content

Commit 5361b44

Browse files
committed
feat: support use data uri with inline loaders
1 parent 19aa2e3 commit 5361b44

4 files changed

Lines changed: 142 additions & 138 deletions

File tree

crates/rspack_core/src/normal_module_factory.rs

Lines changed: 138 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{borrow::Cow, path::Path, sync::Arc};
1+
use std::{borrow::Cow, sync::Arc};
22

33
use once_cell::sync::Lazy;
44
use regex::Regex;
@@ -73,6 +73,9 @@ static ELEMENT_SPLIT_REGEX: Lazy<Regex> =
7373

7474
const HYPHEN: char = '-';
7575
const EXCLAMATION: char = '!';
76+
const DOT: char = '.';
77+
const SLASH: char = '/';
78+
const QUESTION_MARK: char = '?';
7679

7780
impl NormalModuleFactory {
7881
pub fn new(
@@ -126,93 +129,83 @@ impl NormalModuleFactory {
126129
.expect("should be module dependency");
127130
let importer = data.issuer_identifier.as_ref();
128131
let raw_request = dependency.request().to_owned();
129-
let mut request_without_match_resource = dependency.request();
130132

131133
let mut file_dependencies = Default::default();
132134
let mut missing_dependencies = Default::default();
133135

134136
let plugin_driver = &self.plugin_driver;
135137
let loader_resolver = self.get_loader_resolver();
136138

137-
let mut match_resource_data: Option<ResourceData> = None;
139+
let mut match_resource_data = None;
138140
let mut match_module_type = None;
139-
let mut inline_loaders: Vec<ModuleRuleUseLoader> = vec![];
141+
let mut inline_loaders = vec![];
140142
let mut no_pre_auto_loaders = false;
141143
let mut no_auto_loaders = false;
142144
let mut no_pre_post_auto_loaders = false;
143145

144-
request_without_match_resource = {
145-
let match_resource_match = MATCH_RESOURCE_REGEX.captures(request_without_match_resource);
146-
if let Some(m) = match_resource_match {
147-
let mut match_resource: String = m
148-
.get(1)
149-
.expect("Should have match resource")
150-
.as_str()
151-
.to_owned();
152-
let mut chars = match_resource.chars();
153-
let first_char = chars.next();
154-
let second_char = chars.next();
155-
156-
if matches!(first_char, Some('.'))
157-
&& (matches!(second_char, Some('/'))
158-
|| (matches!(second_char, Some('.')) && matches!(chars.next(), Some('/'))))
159-
{
160-
// if matchResources startsWith ../ or ./
161-
match_resource = data
162-
.context
163-
.as_path()
164-
.join(match_resource)
165-
.absolutize()
166-
.to_string_lossy()
167-
.to_string();
168-
}
146+
let mut scheme = get_scheme(dependency.request());
147+
let context_scheme = get_scheme(data.context.as_ref());
148+
let mut unresolved_resource = dependency.request();
149+
if scheme.is_none() {
150+
let mut request_without_match_resource = dependency.request();
151+
request_without_match_resource = {
152+
if let Some(m) = MATCH_RESOURCE_REGEX.captures(request_without_match_resource) {
153+
let match_resource = {
154+
let resource = m.get(1).expect("Should have match resource").as_str();
155+
let mut chars = resource.chars();
156+
let first_char = chars.next();
157+
let second_char = chars.next();
158+
159+
if matches!(first_char, Some(DOT))
160+
&& (matches!(second_char, Some(SLASH))
161+
|| (matches!(second_char, Some(DOT)) && matches!(chars.next(), Some(SLASH))))
162+
{
163+
// if matchResources startsWith ../ or ./
164+
data
165+
.context
166+
.as_path()
167+
.join(resource)
168+
.absolutize()
169+
.to_string_lossy()
170+
.to_string()
171+
} else {
172+
resource.to_owned()
173+
}
174+
};
169175

170-
let ResourceParsedData {
171-
path,
172-
query,
173-
fragment,
174-
} = parse_resource(&match_resource).expect("Should parse resource");
175-
match_resource_data = Some(
176-
ResourceData::new(match_resource, path)
177-
.query_optional(query)
178-
.fragment_optional(fragment),
179-
);
180-
181-
// e.g. ./index.js!=!
182-
let whole_matched = m.get(0).expect("Whole matched").as_str();
183-
184-
match request_without_match_resource
185-
.char_indices()
186-
.nth(whole_matched.chars().count())
187-
{
188-
Some((pos, _)) => &request_without_match_resource[pos..],
189-
None => {
190-
unreachable!("Invalid dependency: {:?}", &data.dependency)
176+
let ResourceParsedData {
177+
path,
178+
query,
179+
fragment,
180+
} = parse_resource(&match_resource).expect("Should parse resource");
181+
match_resource_data = Some(
182+
ResourceData::new(match_resource, path)
183+
.query_optional(query)
184+
.fragment_optional(fragment),
185+
);
186+
187+
// e.g. ./index.js!=!
188+
let whole_matched = m
189+
.get(0)
190+
.expect("should guaranteed to return a non-None value.")
191+
.as_str();
192+
193+
match request_without_match_resource
194+
.char_indices()
195+
.nth(whole_matched.chars().count())
196+
{
197+
Some((pos, _)) => &request_without_match_resource[pos..],
198+
None => {
199+
unreachable!("Invalid dependency: {:?}", &data.dependency)
200+
}
191201
}
202+
} else {
203+
request_without_match_resource
192204
}
193-
} else {
194-
request_without_match_resource
195-
}
196-
};
197-
198-
let scheme = get_scheme(request_without_match_resource);
199-
let context_scheme = get_scheme(data.context.as_ref());
205+
};
200206

201-
// with scheme, windows absolute path is considered scheme by `url`
202-
let resource_data =
203-
if scheme != Scheme::None && !Path::is_absolute(Path::new(request_without_match_resource)) {
204-
let mut resource_data =
205-
ResourceData::new(request_without_match_resource.to_string(), "".into());
206-
// resource with scheme
207-
plugin_driver
208-
.normal_module_factory_hooks
209-
.resolve_for_scheme
210-
.call(data, &mut resource_data)
211-
.await?;
212-
resource_data
213-
}
214-
// TODO: resource within scheme, call resolveInScheme hook
215-
else {
207+
scheme = get_scheme(request_without_match_resource);
208+
if scheme.is_none() && context_scheme.is_none() {
216209
let mut request = request_without_match_resource.chars();
217210
let first_char = request.next();
218211
let second_char = request.next();
@@ -245,7 +238,7 @@ impl NormalModuleFactory {
245238
ELEMENT_SPLIT_REGEX.split(s).collect::<Vec<_>>()
246239
};
247240

248-
request_without_match_resource = raw_elements
241+
unresolved_resource = raw_elements
249242
.pop()
250243
.ok_or_else(|| error!("Invalid request: {request_without_match_resource}"))?;
251244

@@ -267,75 +260,84 @@ impl NormalModuleFactory {
267260
}),
268261
}
269262
}));
263+
scheme = get_scheme(unresolved_resource);
264+
} else {
265+
unresolved_resource = request_without_match_resource;
266+
}
267+
}
270268

271-
if request_without_match_resource.is_empty()
272-
|| request_without_match_resource.starts_with('?')
273-
{
274-
let ResourceParsedData {
275-
path,
276-
query,
277-
fragment,
278-
} = parse_resource(request_without_match_resource).expect("Should parse resource");
279-
ResourceData::new(request_without_match_resource.to_string(), path)
280-
.query_optional(query)
281-
.fragment_optional(fragment)
282-
} else {
283-
let optional = dependency.get_optional();
269+
let resource_data = if scheme.is_some() {
270+
// resource with scheme
271+
let mut resource_data = ResourceData::new(unresolved_resource.to_owned(), "".into());
272+
// resource with scheme
273+
plugin_driver
274+
.normal_module_factory_hooks
275+
.resolve_for_scheme
276+
.call(data, &mut resource_data)
277+
.await?;
278+
resource_data
279+
} else {
280+
// TODO: resource within scheme
284281

285-
let resolve_args = ResolveArgs {
286-
importer,
287-
issuer: data.issuer.as_deref(),
288-
context: if context_scheme != Scheme::None {
289-
self.options.context.clone()
290-
} else {
291-
data.context.clone()
292-
},
293-
specifier: request_without_match_resource,
294-
dependency_type: dependency.dependency_type(),
295-
dependency_category: dependency.category(),
296-
span: dependency.source_span(),
297-
// take the options is safe here, because it
298-
// is not used in after_resolve hooks
299-
resolve_options: data.resolve_options.take(),
300-
resolve_to_context: false,
301-
optional,
302-
file_dependencies: &mut file_dependencies,
303-
missing_dependencies: &mut missing_dependencies,
304-
};
282+
// default resolve
283+
// resource without scheme and with path
284+
if unresolved_resource.is_empty() || unresolved_resource.starts_with(QUESTION_MARK) {
285+
ResourceData::new(unresolved_resource.to_owned(), "".into())
286+
} else {
287+
let resolve_args = ResolveArgs {
288+
importer,
289+
issuer: data.issuer.as_deref(),
290+
context: if context_scheme != Scheme::None {
291+
self.options.context.clone()
292+
} else {
293+
data.context.clone()
294+
},
295+
specifier: unresolved_resource,
296+
dependency_type: dependency.dependency_type(),
297+
dependency_category: dependency.category(),
298+
span: dependency.source_span(),
299+
// take the options is safe here, because it
300+
// is not used in after_resolve hooks
301+
resolve_options: data.resolve_options.take(),
302+
resolve_to_context: false,
303+
optional: dependency.get_optional(),
304+
file_dependencies: &mut file_dependencies,
305+
missing_dependencies: &mut missing_dependencies,
306+
};
305307

306-
// default resolve
307-
let resource_data = resolve(resolve_args, plugin_driver).await;
308+
// default resolve
309+
let resource_data = resolve(resolve_args, plugin_driver).await;
308310

309-
match resource_data {
310-
Ok(ResolveResult::Resource(resource)) => {
311-
let uri = resource.full_path().display().to_string();
312-
ResourceData::new(uri, resource.path)
313-
.query(resource.query)
314-
.fragment(resource.fragment)
315-
.description_optional(resource.description_data)
316-
}
317-
Ok(ResolveResult::Ignored) => {
318-
let ident = format!("{}/{}", &data.context, request_without_match_resource);
319-
let module_identifier = ModuleIdentifier::from(format!("ignored|{ident}"));
320-
321-
let raw_module = RawModule::new(
322-
"/* (ignored) */".to_owned(),
323-
module_identifier,
324-
format!("{ident} (ignored)"),
325-
Default::default(),
326-
)
327-
.boxed();
328-
329-
return Ok(Some(ModuleFactoryResult::new_with_module(raw_module)));
330-
}
331-
Err(err) => {
332-
data.add_file_dependencies(file_dependencies);
333-
data.add_missing_dependencies(missing_dependencies);
334-
return Err(err);
335-
}
311+
match resource_data {
312+
Ok(ResolveResult::Resource(resource)) => {
313+
let uri = resource.full_path().display().to_string();
314+
ResourceData::new(uri, resource.path)
315+
.query(resource.query)
316+
.fragment(resource.fragment)
317+
.description_optional(resource.description_data)
318+
}
319+
Ok(ResolveResult::Ignored) => {
320+
let ident = format!("{}/{}", &data.context, unresolved_resource);
321+
let module_identifier = ModuleIdentifier::from(format!("ignored|{ident}"));
322+
323+
let raw_module = RawModule::new(
324+
"/* (ignored) */".to_owned(),
325+
module_identifier,
326+
format!("{ident} (ignored)"),
327+
Default::default(),
328+
)
329+
.boxed();
330+
331+
return Ok(Some(ModuleFactoryResult::new_with_module(raw_module)));
332+
}
333+
Err(err) => {
334+
data.add_file_dependencies(file_dependencies);
335+
data.add_missing_dependencies(missing_dependencies);
336+
return Err(err);
336337
}
337338
}
338-
};
339+
}
340+
};
339341

340342
let resolved_module_rules = if let Some(match_resource_data) = &mut match_resource_data
341343
&& let Some(captures) = MATCH_WEBPACK_EXT_REGEX.captures(&match_resource_data.resource)

crates/rspack_loader_runner/src/scheme.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ impl Scheme {
2121
pub fn is_none(&self) -> bool {
2222
matches!(self, Self::None)
2323
}
24+
25+
pub fn is_some(&self) -> bool {
26+
!self.is_none()
27+
}
2428
}
2529

2630
impl From<&str> for Scheme {

webpack-test/cases/resolving/data-uri/test.filter.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

webpack-test/configCases/asset-modules/resource-from-data-uri/test.filter.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)