use std::{borrow::Cow, sync::Arc};
use rspack_error::{Result, error};
use rspack_hook::define_hook;
use rspack_loader_runner::{Loader, Scheme, get_scheme};
use rspack_util::MergeFrom;
use sugar_path::SugarPath;
use winnow::prelude::*;
use crate::{
AssetInlineGeneratorOptions, AssetResourceGeneratorOptions, BoxLoader, BoxModule,
CompilerOptions, Context, CssAutoGeneratorOptions, CssAutoParserOptions,
CssModuleGeneratorOptions, CssModuleParserOptions, Dependency, DependencyCategory,
DependencyType, FactoryMeta, FuncUseCtx, GeneratorOptions, ModuleExt, ModuleFactory,
ModuleFactoryCreateData, ModuleFactoryResult, ModuleIdentifier, ModuleLayer, ModuleRuleEffect,
ModuleRuleEnforce, ModuleRuleUse, ModuleRuleUseLoader, ModuleType, NormalModule,
ParserAndGenerator, ParserOptions, RawModule, Resolve, ResolveArgs,
ResolveOptionsWithDependencyType, ResolveResult, Resolver, ResolverFactory, ResourceData,
ResourceParsedData, RunnerContext, RuntimeGlobals, SharedPluginDriver,
diagnostics::EmptyDependency, module_rules_matcher, parse_resource, resolve,
stringify_loaders_and_resource,
};
define_hook!(NormalModuleFactoryBeforeResolve: SeriesBail(data: &mut ModuleFactoryCreateData) -> bool,tracing=false);
define_hook!(NormalModuleFactoryFactorize: SeriesBail(data: &mut ModuleFactoryCreateData) -> BoxModule,tracing=false);
define_hook!(NormalModuleFactoryResolve: SeriesBail(data: &mut ModuleFactoryCreateData) -> NormalModuleFactoryResolveResult,tracing=false);
define_hook!(NormalModuleFactoryResolveForScheme: SeriesBail(data: &mut ModuleFactoryCreateData, resource_data: &mut ResourceData, for_name: &Scheme) -> bool,tracing=false);
define_hook!(NormalModuleFactoryResolveInScheme: SeriesBail(data: &mut ModuleFactoryCreateData, resource_data: &mut ResourceData, for_name: &Scheme) -> bool,tracing=false);
define_hook!(NormalModuleFactoryAfterResolve: SeriesBail(data: &mut ModuleFactoryCreateData, create_data: &mut NormalModuleCreateData) -> bool,tracing=false);
define_hook!(NormalModuleFactoryCreateModule: SeriesBail(data: &mut ModuleFactoryCreateData, create_data: &mut NormalModuleCreateData) -> BoxModule,tracing=false);
define_hook!(NormalModuleFactoryModule: Series(data: &mut ModuleFactoryCreateData, create_data: &mut NormalModuleCreateData, module: &mut BoxModule),tracing=false);
define_hook!(NormalModuleFactoryParser: Series(module_type: &ModuleType, parser: &mut Box<dyn ParserAndGenerator>, parser_options: Option<&ParserOptions>),tracing=false);
define_hook!(NormalModuleFactoryResolveLoader: SeriesBail(context: &Context, resolver: &Resolver, l: &ModuleRuleUseLoader) -> BoxLoader,tracing=false);
define_hook!(NormalModuleFactoryAfterFactorize: Series(data: &mut ModuleFactoryCreateData, module: &mut BoxModule),tracing=false);
pub enum NormalModuleFactoryResolveResult {
Module(BoxModule),
Ignored,
}
#[derive(Debug, Default)]
pub struct NormalModuleFactoryHooks {
pub before_resolve: NormalModuleFactoryBeforeResolveHook,
pub factorize: NormalModuleFactoryFactorizeHook,
pub resolve: NormalModuleFactoryResolveHook,
pub resolve_for_scheme: NormalModuleFactoryResolveForSchemeHook,
pub resolve_in_scheme: NormalModuleFactoryResolveInSchemeHook,
pub after_resolve: NormalModuleFactoryAfterResolveHook,
pub create_module: NormalModuleFactoryCreateModuleHook,
pub module: NormalModuleFactoryModuleHook,
pub parser: NormalModuleFactoryParserHook,
pub resolve_loader: NormalModuleFactoryResolveLoaderHook,
pub after_factorize: NormalModuleFactoryAfterFactorizeHook,
}
#[derive(Debug)]
pub struct NormalModuleFactory {
options: Arc<CompilerOptions>,
loader_resolver_factory: Arc<ResolverFactory>,
plugin_driver: SharedPluginDriver,
}
#[async_trait::async_trait]
impl ModuleFactory for NormalModuleFactory {
async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result<ModuleFactoryResult> {
if let Some(before_resolve_data) = self.before_resolve(data).await? {
return Ok(before_resolve_data);
}
let mut factory_result = self.factorize(data).await?;
if let Some(module) = &mut factory_result.module {
self
.plugin_driver
.normal_module_factory_hooks
.after_factorize
.call(data, module)
.await?;
}
Ok(factory_result)
}
}
const HYPHEN: char = '-';
const EXCLAMATION: char = '!';
const DOT: char = '.';
const SLASH: char = '/';
const QUESTION_MARK: char = '?';
impl NormalModuleFactory {
pub fn new(
options: Arc<CompilerOptions>,
loader_resolver_factory: Arc<ResolverFactory>,
plugin_driver: SharedPluginDriver,
) -> Self {
Self {
options,
loader_resolver_factory,
plugin_driver,
}
}
async fn before_resolve(
&self,
data: &mut ModuleFactoryCreateData,
) -> Result<Option<ModuleFactoryResult>> {
if let Some(false) = self
.plugin_driver
.normal_module_factory_hooks
.before_resolve
.call(data)
.await?
{
return Ok(Some(ModuleFactoryResult::default()));
}
Ok(None)
}
fn get_loader_resolver(&self) -> Arc<Resolver> {
self
.loader_resolver_factory
.get(ResolveOptionsWithDependencyType {
resolve_options: None,
resolve_to_context: false,
dependency_category: DependencyCategory::CommonJS,
})
}
async fn resolve_normal_module(
&self,
data: &mut ModuleFactoryCreateData,
) -> Result<Option<ModuleFactoryResult>> {
let dependency = data.dependencies[0]
.as_module_dependency()
.expect("should be module dependency");
let dependency_type = *dependency.dependency_type();
let dependency_category = *dependency.category();
let dependency_range = dependency.range();
let dependency_optional = dependency.get_optional();
let importer = data.issuer_identifier;
let raw_request = data.request.clone();
let mut file_dependencies = Default::default();
let mut missing_dependencies = Default::default();
let plugin_driver = &self.plugin_driver;
let loader_resolver = self.get_loader_resolver();
let mut match_resource_data = None;
let mut match_module_type = None;
let mut inline_loaders = vec![];
let mut no_pre_auto_loaders = false;
let mut no_auto_loaders = false;
let mut no_pre_post_auto_loaders = false;
let mut scheme = get_scheme(&data.request);
let context_scheme = get_scheme(data.context.as_ref());
let mut unresolved_resource = data.request.as_str();
if scheme.is_none() {
let mut request_without_match_resource = data.request.as_str();
request_without_match_resource = {
if let Ok((resource, full_matched)) = match_resource(request_without_match_resource) {
let match_resource = {
let mut chars = resource.chars();
let first_char = chars.next();
let second_char = chars.next();
if matches!(first_char, Some(DOT))
&& (matches!(second_char, Some(SLASH))
|| (matches!(second_char, Some(DOT)) && matches!(chars.next(), Some(SLASH))))
{
data
.context
.as_path()
.join(resource)
.as_std_path()
.absolutize()
.to_string_lossy()
.into_owned()
} else {
resource.to_owned()
}
};
let ResourceParsedData {
path,
query,
fragment,
} = parse_resource(&match_resource).expect("Should parse resource");
match_resource_data = Some(ResourceData::new_with_path(
match_resource,
path,
query,
fragment,
));
let whole_matched = full_matched;
match request_without_match_resource
.char_indices()
.nth(whole_matched.chars().count())
{
Some((pos, _)) => &request_without_match_resource[pos..],
None => {
unreachable!("Invalid dependency: {:?}", &data.dependencies[0])
}
}
} else {
request_without_match_resource
}
};
scheme = get_scheme(request_without_match_resource);
if scheme.is_none() && context_scheme.is_none() {
let mut request = request_without_match_resource.chars();
let first_char = request.next();
let second_char = request.next();
if first_char.is_none() {
return Err(EmptyDependency::new(dependency.range()).into());
}
no_pre_auto_loaders =
matches!(first_char, Some(HYPHEN)) && matches!(second_char, Some(EXCLAMATION));
no_auto_loaders = no_pre_auto_loaders || matches!(first_char, Some(EXCLAMATION));
no_pre_post_auto_loaders =
matches!(first_char, Some(EXCLAMATION)) && matches!(second_char, Some(EXCLAMATION));
let mut raw_elements = {
let s = match request_without_match_resource.char_indices().nth({
if no_pre_auto_loaders || no_pre_post_auto_loaders {
2
} else if no_auto_loaders {
1
} else {
0
}
}) {
Some((pos, _)) => &request_without_match_resource[pos..],
None => request_without_match_resource,
};
split_element(s)
};
unresolved_resource = raw_elements
.pop()
.ok_or_else(|| error!("Invalid request: {request_without_match_resource}"))?;
inline_loaders.extend(raw_elements.into_iter().map(|r| {
let resource = parse_resource(r);
let ident = resource.as_ref().and_then(|r| {
r.query
.as_ref()
.and_then(|q| q.starts_with("??").then(|| &q[2..]))
});
ModuleRuleUseLoader {
loader: r.to_owned(),
options: ident.and_then(|ident| {
data
.options
.__references
.get(ident)
.map(|object| object.to_string())
}),
}
}));
scheme = get_scheme(unresolved_resource);
} else {
unresolved_resource = request_without_match_resource;
}
}
let resource = unresolved_resource.to_owned();
let resource_data = if !scheme.is_none() {
let mut resource_data = ResourceData::new_with_resource(resource);
plugin_driver
.normal_module_factory_hooks
.resolve_for_scheme
.call(data, &mut resource_data, &scheme)
.await?;
resource_data
} else if !context_scheme.is_none()
&& let Some(resource_data) = {
let mut resource_data = ResourceData::new_with_resource(resource.clone());
let handled = plugin_driver
.normal_module_factory_hooks
.resolve_in_scheme
.call(data, &mut resource_data, &context_scheme)
.await?
.unwrap_or_default();
handled.then_some(resource_data)
}
{
resource_data
} else {
if resource.is_empty() || resource.starts_with(QUESTION_MARK) {
ResourceData::new_with_resource(resource.clone())
} else {
let resolve_args = ResolveArgs {
importer: importer.as_ref(),
issuer: data.issuer.as_deref(),
context: if context_scheme != Scheme::None {
self.options.context.clone()
} else {
data.context.clone()
},
specifier: &resource,
dependency_type: &dependency_type,
dependency_category: &dependency_category,
span: dependency_range,
resolve_options: data.resolve_options.clone(),
resolve_to_context: false,
optional: dependency_optional,
file_dependencies: &mut file_dependencies,
missing_dependencies: &mut missing_dependencies,
};
let resource_data = resolve(resolve_args, plugin_driver).await;
match resource_data {
Ok(ResolveResult::Resource(resource)) => resource.into(),
Ok(ResolveResult::Ignored) => {
let ident = format!("{}/{}", &data.context, resource);
let module_identifier = ModuleIdentifier::from(format!("ignored|{ident}"));
let mut raw_module = if matches!(
dependency_type,
DependencyType::CssUrl | DependencyType::NewUrl
) {
RawModule::new(
r#"/* (ignored-asset) */
module.exports = "data:,";
"#
.to_owned(),
module_identifier,
format!("{} (ignored-asset)", data.request),
RuntimeGlobals::MODULE,
)
.boxed()
} else {
RawModule::new(
"/* (ignored) */".to_owned(),
module_identifier,
format!("{} (ignored)", data.request),
Default::default(),
)
.boxed()
};
raw_module.set_factory_meta(FactoryMeta {
side_effect_free: Some(true),
});
return Ok(Some(ModuleFactoryResult::new_with_module(raw_module)));
}
Err(err) => {
data.add_file_dependencies(file_dependencies);
data.add_missing_dependencies(missing_dependencies);
return Err(err);
}
}
}
};
let resolved_module_rules = if let Some(match_resource_data) = &mut match_resource_data
&& let Ok((module, module_type)) = match_ext(match_resource_data.resource())
{
match_module_type = Some(module_type.into());
match_resource_data.set_resource(module.into());
vec![]
} else {
self
.calculate_module_rules(
if let Some(match_resource_data) = match_resource_data.as_ref() {
match_resource_data
} else {
&resource_data
},
data.dependencies[0].as_ref(),
data.issuer.as_deref(),
data.issuer_layer.as_deref(),
)
.await?
};
let mut resolved_inline_loaders = vec![];
for l in inline_loaders {
resolved_inline_loaders
.push(resolve_each(plugin_driver, &data.context, &loader_resolver, &l).await?)
}
let user_request = {
let suffix =
stringify_loaders_and_resource(&resolved_inline_loaders, resource_data.resource());
if let Some(match_resource_data) = &match_resource_data {
let mut resource = match_resource_data.resource().to_owned();
resource += "!=!";
resource += &*suffix;
resource
} else {
suffix.into_owned()
}
};
let loaders: Vec<BoxLoader> = {
let mut pre_loaders: Vec<ModuleRuleUseLoader> = vec![];
let mut post_loaders: Vec<ModuleRuleUseLoader> = vec![];
let mut normal_loaders: Vec<ModuleRuleUseLoader> = vec![];
for rule in &resolved_module_rules {
let rule_use = match &rule.r#use {
ModuleRuleUse::Array(array_use) => Cow::Borrowed(array_use),
ModuleRuleUse::Func(func_use) => {
let resource_data_for_rules = match_resource_data.as_ref().unwrap_or(&resource_data);
let context = FuncUseCtx {
resource: resource_data_for_rules.path().map(|x| x.to_string()),
resource_query: resource_data_for_rules.query().map(|q| q.to_owned()),
resource_fragment: resource_data_for_rules.fragment().map(|f| f.to_owned()),
real_resource: resource_data.path().map(|p| p.to_string()),
issuer: data.issuer.clone(),
issuer_layer: data.issuer_layer.clone(),
};
Cow::Owned(func_use(context).await?)
}
};
match rule.enforce {
ModuleRuleEnforce::Pre => {
if !no_pre_auto_loaders && !no_pre_post_auto_loaders {
pre_loaders.extend_from_slice(&rule_use);
}
}
ModuleRuleEnforce::Normal => {
if !no_auto_loaders && !no_pre_auto_loaders {
normal_loaders.extend_from_slice(&rule_use);
}
}
ModuleRuleEnforce::Post => {
if !no_pre_post_auto_loaders {
post_loaders.extend_from_slice(&rule_use);
}
}
}
}
let mut all_loaders = Vec::with_capacity(
pre_loaders.len()
+ post_loaders.len()
+ normal_loaders.len()
+ resolved_inline_loaders.len(),
);
for l in post_loaders {
all_loaders
.push(resolve_each(plugin_driver, &self.options.context, &loader_resolver, &l).await?)
}
let mut resolved_normal_loaders = vec![];
for l in normal_loaders {
resolved_normal_loaders
.push(resolve_each(plugin_driver, &self.options.context, &loader_resolver, &l).await?)
}
if match_resource_data.is_some() {
all_loaders.extend(resolved_normal_loaders);
all_loaders.extend(resolved_inline_loaders);
} else {
all_loaders.extend(resolved_inline_loaders);
all_loaders.extend(resolved_normal_loaders);
}
for l in pre_loaders {
all_loaders
.push(resolve_each(plugin_driver, &self.options.context, &loader_resolver, &l).await?)
}
all_loaders
};
let request = if !loaders.is_empty() {
let s = loaders
.iter()
.map(|i| i.identifier().as_str())
.collect::<Vec<_>>()
.join("!");
format!("{s}!{}", resource_data.resource())
} else {
resource_data.resource().to_owned()
};
let resolved_module_type =
self.calculate_module_type(match_module_type, &resolved_module_rules);
let resolved_module_layer =
self.calculate_module_layer(data.issuer_layer.as_ref(), &resolved_module_rules);
let resolved_resolve_options = self.calculate_resolve_options(&resolved_module_rules);
let (resolved_parser_options, resolved_generator_options) =
self.calculate_parser_and_generator_options(&resolved_module_rules);
let (resolved_parser_options, resolved_generator_options) = self
.merge_global_parser_and_generator_options(
&resolved_module_type,
resolved_parser_options,
resolved_generator_options,
);
let resolved_side_effects = self.calculate_side_effects(&resolved_module_rules);
let resolved_extract_source_map = self.calculate_extract_source_map(&resolved_module_rules);
let mut resolved_parser_and_generator = self
.plugin_driver
.registered_parser_and_generator_builder
.get(&resolved_module_type)
.ok_or_else(|| {
error!(
"No parser registered for '{}'",
resolved_module_type.as_str()
)
})?(
resolved_parser_options.as_ref(),
resolved_generator_options.as_ref(),
);
self
.plugin_driver
.normal_module_factory_hooks
.parser
.call(
&resolved_module_type,
&mut resolved_parser_and_generator,
resolved_parser_options.as_ref(),
)
.await?;
let mut create_data = {
let mut create_data = NormalModuleCreateData {
raw_request,
request,
user_request,
match_resource: match_resource_data
.as_ref()
.map(|d| d.resource().to_owned()),
side_effects: resolved_side_effects,
context: resource_data.context().map(|c| c.to_owned()),
resource_resolve_data: resource_data,
};
if let Some(plugin_result) = self
.plugin_driver
.normal_module_factory_hooks
.after_resolve
.call(data, &mut create_data)
.await?
&& !plugin_result
{
return Ok(Some(ModuleFactoryResult::default()));
}
create_data
};
let mut module = if let Some(module) = self
.plugin_driver
.normal_module_factory_hooks
.create_module
.call(data, &mut create_data)
.await?
{
module
} else {
NormalModule::new(
create_data.request.clone(),
create_data.user_request.clone(),
create_data.raw_request.clone(),
resolved_module_type,
resolved_module_layer,
resolved_parser_and_generator,
resolved_parser_options,
resolved_generator_options,
match_resource_data,
Arc::new(create_data.resource_resolve_data.clone()),
resolved_resolve_options,
loaders,
create_data.context.clone().map(|x| x.into()),
resolved_extract_source_map,
)
.boxed()
};
self
.plugin_driver
.normal_module_factory_hooks
.module
.call(data, &mut create_data, &mut module)
.await?;
data.add_file_dependencies(file_dependencies);
data.add_missing_dependencies(missing_dependencies);
Ok(Some(ModuleFactoryResult::new_with_module(module)))
}
async fn calculate_module_rules<'a>(
&'a self,
resource_data: &ResourceData,
dependency: &dyn Dependency,
issuer: Option<&'a str>,
issuer_layer: Option<&'a str>,
) -> Result<Vec<&'a ModuleRuleEffect>> {
let mut rules = Vec::new();
module_rules_matcher(
&self.options.module.rules,
resource_data,
issuer,
issuer_layer,
dependency.category(),
dependency.get_attributes(),
&mut rules,
)
.await?;
Ok(rules)
}
fn calculate_resolve_options(&self, module_rules: &[&ModuleRuleEffect]) -> Option<Arc<Resolve>> {
let mut resolved: Option<Resolve> = None;
for rule in module_rules {
if let Some(rule_resolve) = &rule.resolve {
if let Some(r) = resolved {
resolved = Some(r.merge(rule_resolve.to_owned()));
} else {
resolved = Some(rule_resolve.to_owned());
}
}
}
resolved.map(Arc::new)
}
fn calculate_side_effects(&self, module_rules: &[&ModuleRuleEffect]) -> Option<bool> {
let mut side_effect_res = None;
for rule in module_rules.iter() {
if rule.side_effects.is_some() {
side_effect_res = rule.side_effects;
}
}
side_effect_res
}
fn calculate_extract_source_map(&self, module_rules: &[&ModuleRuleEffect]) -> Option<bool> {
let mut extract_source_map_res = None;
for rule in module_rules.iter() {
if rule.extract_source_map.is_some() {
extract_source_map_res = rule.extract_source_map;
}
}
extract_source_map_res
}
fn calculate_parser_and_generator_options(
&self,
module_rules: &[&ModuleRuleEffect],
) -> (Option<ParserOptions>, Option<GeneratorOptions>) {
let mut resolved_parser = None;
let mut resolved_generator = None;
for rule in module_rules {
resolved_parser = resolved_parser.merge_from(&rule.parser);
resolved_generator = resolved_generator.merge_from(&rule.generator);
}
(resolved_parser, resolved_generator)
}
fn merge_global_parser_and_generator_options(
&self,
module_type: &ModuleType,
parser: Option<ParserOptions>,
generator: Option<GeneratorOptions>,
) -> (Option<ParserOptions>, Option<GeneratorOptions>) {
let global_parser = self.options.module.parser.as_ref().and_then(|p| {
let options = p.get(module_type.as_str());
match module_type {
ModuleType::JsAuto | ModuleType::JsDynamic | ModuleType::JsEsm => {
rspack_util::merge_from_optional_with(
p.get("javascript").cloned(),
options,
|javascript_options, options| match (javascript_options, options) {
(
ParserOptions::Javascript(a),
ParserOptions::JavascriptAuto(b)
| ParserOptions::JavascriptDynamic(b)
| ParserOptions::JavascriptEsm(b),
) => ParserOptions::Javascript(a.merge_from(b)),
_ => unreachable!(),
},
)
}
ModuleType::CssAuto | ModuleType::CssModule => rspack_util::merge_from_optional_with(
p.get("css").cloned(),
options,
|css_options, options| match (css_options, options) {
(ParserOptions::Css(a), ParserOptions::CssAuto(b)) => {
ParserOptions::CssAuto(Into::<CssAutoParserOptions>::into(a).merge_from(b))
}
(ParserOptions::Css(a), ParserOptions::CssModule(b)) => {
ParserOptions::CssModule(Into::<CssModuleParserOptions>::into(a).merge_from(b))
}
_ => unreachable!(),
},
),
_ => options.cloned(),
}
});
let global_generator = self.options.module.generator.as_ref().and_then(|g| {
let options = g.get(module_type.as_str());
match module_type {
ModuleType::AssetInline | ModuleType::AssetResource => {
rspack_util::merge_from_optional_with(
g.get("asset").cloned(),
options,
|asset_options, options| match (asset_options, options) {
(GeneratorOptions::Asset(a), GeneratorOptions::AssetInline(b)) => {
GeneratorOptions::AssetInline(
Into::<AssetInlineGeneratorOptions>::into(a).merge_from(b),
)
}
(GeneratorOptions::Asset(a), GeneratorOptions::AssetResource(b)) => {
GeneratorOptions::AssetResource(
Into::<AssetResourceGeneratorOptions>::into(a).merge_from(b),
)
}
_ => unreachable!(),
},
)
}
ModuleType::CssAuto | ModuleType::CssModule => rspack_util::merge_from_optional_with(
g.get("css").cloned(),
options,
|css_options, options| match (css_options, options) {
(GeneratorOptions::Css(a), GeneratorOptions::CssAuto(b)) => {
GeneratorOptions::CssAuto(Into::<CssAutoGeneratorOptions>::into(a).merge_from(b))
}
(GeneratorOptions::Css(a), GeneratorOptions::CssModule(b)) => {
GeneratorOptions::CssModule(Into::<CssModuleGeneratorOptions>::into(a).merge_from(b))
}
_ => unreachable!(),
},
),
ModuleType::Json => rspack_util::merge_from_optional_with(
g.get("json").cloned(),
options,
|json_options, options| match (json_options, options) {
(GeneratorOptions::Json(a), GeneratorOptions::Json(b)) => {
GeneratorOptions::Json(a.merge_from(b))
}
_ => unreachable!(),
},
),
_ => options.cloned(),
}
});
let parser = rspack_util::merge_from_optional_with(
global_parser,
parser.as_ref(),
|global, local| match (global, local) {
(ParserOptions::Asset(a), ParserOptions::Asset(b)) => ParserOptions::Asset(a.merge_from(b)),
(ParserOptions::Css(a), ParserOptions::Css(b)) => ParserOptions::Css(a.merge_from(b)),
(ParserOptions::CssAuto(a), ParserOptions::CssAuto(b)) => {
ParserOptions::CssAuto(a.merge_from(b))
}
(ParserOptions::CssModule(a), ParserOptions::CssModule(b)) => {
ParserOptions::CssModule(a.merge_from(b))
}
(
ParserOptions::Javascript(a),
ParserOptions::JavascriptAuto(b)
| ParserOptions::JavascriptDynamic(b)
| ParserOptions::JavascriptEsm(b),
) => ParserOptions::Javascript(a.merge_from(b)),
(ParserOptions::Json(a), ParserOptions::Json(b)) => ParserOptions::Json(a.merge_from(b)),
(global, _) => global,
},
);
let generator = rspack_util::merge_from_optional_with(
global_generator,
generator.as_ref(),
|global, local| match (&global, local) {
(GeneratorOptions::Asset(_), GeneratorOptions::Asset(_))
| (GeneratorOptions::AssetInline(_), GeneratorOptions::AssetInline(_))
| (GeneratorOptions::AssetResource(_), GeneratorOptions::AssetResource(_))
| (GeneratorOptions::Css(_), GeneratorOptions::Css(_))
| (GeneratorOptions::CssAuto(_), GeneratorOptions::CssAuto(_))
| (GeneratorOptions::CssModule(_), GeneratorOptions::CssModule(_))
| (GeneratorOptions::Json(_), GeneratorOptions::Json(_)) => global.merge_from(local),
_ => global,
},
);
(parser, generator)
}
fn calculate_module_type(
&self,
matched_module_type: Option<ModuleType>,
module_rules: &[&ModuleRuleEffect],
) -> ModuleType {
let mut resolved_module_type = matched_module_type.unwrap_or(ModuleType::JsAuto);
for module_rule in module_rules.iter() {
if let Some(module_type) = module_rule.r#type {
resolved_module_type = module_type;
};
}
resolved_module_type
}
fn calculate_module_layer(
&self,
issuer_layer: Option<&ModuleLayer>,
module_rules: &[&ModuleRuleEffect],
) -> Option<ModuleLayer> {
let mut resolved_module_layer = issuer_layer;
for module_rule in module_rules.iter() {
if let Some(module_layer) = &module_rule.layer {
resolved_module_layer = Some(module_layer);
};
}
resolved_module_layer.cloned()
}
async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result<ModuleFactoryResult> {
if let Some(result) = self
.plugin_driver
.normal_module_factory_hooks
.factorize
.call(data)
.await?
{
return Ok(ModuleFactoryResult::new_with_module(result));
}
if let Some(result) = self
.plugin_driver
.normal_module_factory_hooks
.resolve
.call(data)
.await?
{
if let NormalModuleFactoryResolveResult::Module(result) = result {
return Ok(ModuleFactoryResult::new_with_module(result));
} else {
let ident = format!("{}/{}", &data.context, data.request);
let module_identifier = ModuleIdentifier::from(format!("ignored|{ident}"));
let mut raw_module = RawModule::new(
"/* (ignored) */".to_owned(),
module_identifier,
format!("{} (ignored)", data.request),
Default::default(),
)
.boxed();
raw_module.set_factory_meta(FactoryMeta {
side_effect_free: Some(true),
});
return Ok(ModuleFactoryResult::new_with_module(raw_module));
}
}
if let Some(result) = self.resolve_normal_module(data).await? {
return Ok(result);
}
Err(error!(
"Failed to factorize module, neither hook nor factorize method returns"
))
}
}
async fn resolve_each(
plugin_driver: &SharedPluginDriver,
context: &Context,
loader_resolver: &Resolver,
l: &ModuleRuleUseLoader,
) -> Result<Arc<dyn Loader<RunnerContext>>> {
plugin_driver
.normal_module_factory_hooks
.resolve_loader
.call(context, loader_resolver, l)
.await?
.ok_or_else(|| error!("Unable to resolve loader {}", l.loader))
}
#[derive(Debug)]
pub struct NormalModuleCreateData {
pub raw_request: String,
pub request: String,
pub user_request: String,
pub resource_resolve_data: ResourceData,
pub match_resource: Option<String>,
pub side_effects: Option<bool>,
pub context: Option<String>,
}
fn split_element(mut input: &str) -> Vec<&str> {
use winnow::{
combinator::separated,
error::ContextError,
token::{take_till, take_while},
};
separated::<_, _, _, _, ContextError, _, _>(.., take_till(.., '!'), take_while(1.., '!'))
.parse_next(&mut input)
.expect("split should never fail")
}
fn match_resource(mut input: &str) -> winnow::ModalResult<(&str, &str)> {
use winnow::{combinator::terminated, token::take_until};
let backup = input;
let res = terminated(take_until(1.., '!'), "!=!").parse_next(&mut input)?;
let whole_matched = &backup[..backup.len() - input.len()];
Ok((res, whole_matched))
}
fn match_ext(mut input: &str) -> winnow::ModalResult<(&str, &str)> {
use winnow::{
combinator::{alt, delimited, eof, preceded, terminated},
token::take_until,
};
let parser = (
alt((take_until(0.., ".rspack"), take_until(0.., ".webpack"))),
preceded(
alt((".rspack", ".webpack")),
delimited('[', take_until(1.., ']'), ']'),
),
);
terminated(parser, eof).parse_next(&mut input)
}
#[test]
fn test_split_element() {
assert_eq!(split_element("a!a"), vec!["a", "a"]);
assert_eq!(split_element("a!!a"), vec!["a", "a"]);
assert_eq!(split_element("!!a!!a!!"), vec!["", "a", "a", ""]);
}
#[test]
fn test_match_ext() {
assert!(match_ext("foo.webpack[type/javascript]").is_ok());
let cap = match_ext("foo.webpack[type/javascript]").unwrap();
assert_eq!(cap, ("foo", "type/javascript"));
assert_eq!(
match_ext("foo.css.webpack[javascript/auto]"),
Ok(("foo.css", "javascript/auto"))
);
assert!(match_ext("foo.rspack[type/javascript]").is_ok());
let cap = match_ext("foo.rspack[type/javascript]").unwrap();
assert_eq!(cap, ("foo", "type/javascript"));
assert_eq!(
match_ext("foo.css.rspack[javascript/auto]"),
Ok(("foo.css", "javascript/auto"))
);
}