use std::{
borrow::Cow,
collections::{BTreeMap, VecDeque},
fmt::Debug,
hash::Hasher,
mem,
sync::{Arc, LazyLock},
};
use rayon::prelude::*;
use regex::Regex;
use rspack_cacheable::{cacheable, cacheable_dyn, with::As};
use rspack_collections::{
Identifiable, Identifier, IdentifierIndexMap, IdentifierIndexSet, IdentifierMap, IdentifierSet,
};
use rspack_error::{Diagnosable, Diagnostic, Error, Result, ToStringResultToRspackResultExt};
use rspack_hash::{HashDigest, HashFunction, RspackHash, RspackHashDigest};
use rspack_hook::define_hook;
use rspack_sources::{
BoxSource, CachedSource, ConcatSource, RawStringSource, ReplaceSource, Source, SourceExt,
};
use rspack_util::{
SpanExt, ext::DynHash, fx_hash::FxIndexMap, itoa, json_stringify, json_stringify_str,
source_map::SourceMapKind, swc::join_atom,
};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use swc_core::{
atoms::Atom,
common::{Spanned, SyntaxContext, comments::SingleThreadedComments},
ecma::visit::swc_ecma_ast,
};
use swc_experimental_ecma_ast::{
Ast, ClassExpr, EsVersion, GetSpan, Ident, ObjectPatProp, Prop, StringAllocator, Visit, VisitWith,
};
use swc_experimental_ecma_parser::{EsSyntax, Parser, StringSource, Syntax};
use swc_experimental_ecma_semantic::resolver::{Semantic, resolver};
use crate::{
AsyncDependenciesBlockIdentifier, BoxDependency, BoxDependencyTemplate, BoxModule,
BoxModuleDependency, BuildContext, BuildInfo, BuildMeta, BuildMetaDefaultObject,
BuildMetaExportsType, BuildResult, ChunkGraph, ChunkInitFragments, ChunkRenderContext,
CodeGenerationDataTopLevelDeclarations, CodeGenerationExportsFinalNames,
CodeGenerationPublicPathAutoReplace, CodeGenerationResult, Compilation, ConcatenatedModuleIdent,
ConcatenationScope, ConditionalInitFragment, ConnectionState, Context, DEFAULT_EXPORT,
DEFAULT_EXPORT_ATOM, DependenciesBlock, DependencyId, DependencyType, ExportInfoHashKey,
ExportProvided, ExportsArgument, ExportsInfoArtifact, ExportsInfoGetter, ExportsType,
FactoryMeta, GetUsedNameParam, ImportedByDeferModulesArtifact, InitFragment, InitFragmentStage,
LibIdentOptions, Module, ModuleArgument, ModuleCodeGenerationContext, ModuleGraph,
ModuleGraphCacheArtifact, ModuleGraphConnection, ModuleIdentifier, ModuleLayer,
ModuleStaticCache, ModuleType, NAMESPACE_OBJECT_EXPORT, ParserOptions, PrefetchExportsInfoMode,
Resolve, RuntimeCondition, RuntimeGlobals, RuntimeSpec, SideEffectsStateArtifact, SourceType,
URLStaticMode, UsageState, UsedName, UsedNameItem, escape_identifier, fast_set, filter_runtime,
find_target, get_runtime_key, impl_source_map_config, merge_runtime_condition,
merge_runtime_condition_non_false, module_update_hash, property_access, property_name,
render_make_deferred_namespace_mode_from_exports_type,
reserved_names::RESERVED_NAMES,
subtract_runtime_condition, to_identifier_with_escaped, to_normal_comment,
utils::{SourceSizeCache, SourceSizeCacheSerde},
};
type ExportsDefinitionArgs = Vec<(String, String)>;
define_hook!(ConcatenatedModuleExportsDefinitions: SeriesBail(exports_definitions: &mut ExportsDefinitionArgs, is_entry_module: bool) -> bool);
define_hook!(ConcatenatedModuleConcatenatedInfo: Series(compilation: &Compilation, module: ModuleIdentifier, runtime: Option<&RuntimeSpec>, info: &mut ConcatenatedModuleInfo, all_used_names: &mut HashSet<Atom>));
#[derive(Debug, Default)]
pub struct ConcatenatedModuleHooks {
pub exports_definitions: ConcatenatedModuleExportsDefinitionsHook,
pub concatenated_info: ConcatenatedModuleConcatenatedInfoHook,
}
#[cacheable]
#[derive(Debug)]
pub struct RootModuleContext {
pub id: ModuleIdentifier,
pub readable_identifier: String,
pub name_for_condition: Option<Box<str>>,
pub lib_indent: Option<String>,
pub resolve_options: Option<Arc<Resolve>>,
pub code_generation_dependencies: Option<Vec<BoxModuleDependency>>,
pub presentational_dependencies: Option<Vec<BoxDependencyTemplate>>,
pub context: Option<Context>,
pub layer: Option<ModuleLayer>,
pub side_effect_connection_state: ConnectionState,
pub factory_meta: Option<FactoryMeta>,
pub build_meta: BuildMeta,
pub exports_argument: ExportsArgument,
pub module_argument: ModuleArgument,
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct RawBinding {
info_id: ModuleIdentifier,
raw_name: Atom,
comment: Option<String>,
ids: Vec<Atom>,
export_name: Vec<Atom>,
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct SymbolBinding {
info_id: ModuleIdentifier,
name: Atom,
comment: Option<String>,
ids: Vec<Atom>,
export_name: Vec<Atom>,
}
#[derive(Debug, Clone)]
pub enum Binding {
Raw(RawBinding),
Symbol(SymbolBinding),
}
#[derive(Debug)]
pub enum BindingType {
Raw,
Symbol,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct NonDeferAccess(bool);
fn merge_non_defer_access(a: NonDeferAccess, b: NonDeferAccess) -> NonDeferAccess {
NonDeferAccess(a.0 || b.0)
}
fn subtract_non_defer_access(a: NonDeferAccess, b: NonDeferAccess) -> NonDeferAccess {
NonDeferAccess(a.0 && !b.0)
}
#[cacheable]
#[derive(Debug, Clone)]
pub struct ConcatenatedInnerModule {
pub id: ModuleIdentifier,
pub size: f64,
pub shorten_id: String,
}
static REGEX: LazyLock<Regex> = LazyLock::new(|| {
let pattern = r"\.+\/|(\/index)?\.([a-zA-Z0-9]{1,4})($|\s|\?)|\s*\+\s*\d+\s*modules";
Regex::new(pattern).expect("should construct the regex")
});
#[derive(Default)]
struct NameAllocator {
used_names: HashSet<Atom>,
used_strings: HashSet<String>,
suffix_counters: HashMap<Atom, u32>,
}
impl NameAllocator {
fn new(used_names: HashSet<Atom>) -> Self {
let mut used_strings: HashSet<String> = HashSet::default();
used_strings.reserve(used_names.len());
for name in &used_names {
used_strings.insert(name.as_ref().to_string());
}
Self {
used_names,
used_strings,
suffix_counters: HashMap::default(),
}
}
fn contains(&self, name: &Atom) -> bool {
self.used_names.contains(name)
}
fn insert(&mut self, name: Atom) {
self.used_strings.insert(name.as_ref().to_string());
self.used_names.insert(name);
}
fn find_new_name(&mut self, old_name: &str, extra_info: &[Atom]) -> Atom {
let mut name = old_name.to_string();
for info_part in extra_info {
let info_str = info_part.as_ref();
let mut new_name = String::with_capacity(info_str.len() + 1 + name.len());
new_name.push_str(info_str);
if !name.is_empty() {
if name.starts_with('_') || info_str.ends_with('_') {
new_name.push_str(&name);
} else {
new_name.push('_');
new_name.push_str(&name);
}
}
name = new_name;
let escaped = to_identifier_with_escaped(name.clone());
if !self.used_strings.contains(&escaped) {
self.used_strings.insert(escaped.clone());
let candidate: Atom = escaped.into();
self.used_names.insert(candidate.clone());
return candidate;
}
}
let base_str = to_identifier_with_escaped(name);
if !base_str.is_empty() && !self.used_strings.contains(&base_str) {
self.used_strings.insert(base_str.clone());
let base: Atom = base_str.into();
self.used_names.insert(base.clone());
return base;
}
let base: Atom = base_str.into();
let counter = self.suffix_counters.entry(base.clone()).or_insert(0);
let mut i = *counter;
let mut i_buffer = itoa::Buffer::new();
let mut base_with_underscore = String::with_capacity(base.len() + 1);
base_with_underscore.push_str(base.as_ref());
base_with_underscore.push('_');
let mut numbered = String::with_capacity(base_with_underscore.len() + 8);
loop {
numbered.clear();
numbered.push_str(&base_with_underscore);
numbered.push_str(i_buffer.format(i));
if !self.used_strings.contains(&numbered) {
self.used_strings.insert(numbered.clone());
let candidate: Atom = Atom::from(numbered.as_str());
self.used_names.insert(candidate.clone());
*counter = i + 1;
return candidate;
}
i += 1;
}
}
}
#[derive(Debug, Clone)]
pub enum ConcatenationEntry {
Concatenated(ConcatenationEntryConcatenated),
External(ConcatenationEntryExternal),
}
impl ConcatenationEntry {
pub fn module(&self, mg: &ModuleGraph) -> ModuleIdentifier {
match self {
ConcatenationEntry::Concatenated(c) => c.module,
ConcatenationEntry::External(e) => e.module(mg),
}
}
}
#[derive(Debug, Clone)]
pub struct ConcatenationEntryConcatenated {
module: ModuleIdentifier,
}
#[derive(Debug, Clone)]
pub struct ConcatenationEntryExternal {
dependency: DependencyId,
runtime_condition: RuntimeCondition,
non_defer_access: NonDeferAccess,
}
impl ConcatenationEntryExternal {
pub fn module(&self, mg: &ModuleGraph) -> ModuleIdentifier {
let con = mg
.connection_by_dependency_id(&self.dependency)
.expect("should have connection");
*con.module_identifier()
}
}
#[derive(Clone, Debug, Default)]
pub struct ConcatenatedImportMapItem {
pub specifiers: HashSet<Atom>,
pub namespace: Option<Atom>,
}
pub type ConcatenatedImportMap =
Option<FxIndexMap<(String, Option<String>), ConcatenatedImportMapItem>>;
#[derive(Debug, Clone, Default)]
pub struct ConcatenatedModuleInfo {
pub index: usize,
pub module: ModuleIdentifier,
pub namespace_export_symbol: Option<Atom>,
pub chunk_init_fragments: ChunkInitFragments,
pub module_ctxt: SyntaxContext,
pub global_ctxt: SyntaxContext,
pub runtime_requirements: RuntimeGlobals,
pub has_ast: bool,
pub source: Option<ReplaceSource>,
pub internal_source: Option<Arc<dyn Source>>,
pub internal_names: HashMap<Atom, Atom>,
pub export_map: Option<HashMap<Atom, String>>,
pub raw_export_map: Option<HashMap<Atom, String>>,
pub import_map: ConcatenatedImportMap,
pub namespace_object_name: Option<Atom>,
pub interop_namespace_object_used: bool,
pub interop_namespace_object_name: Option<Atom>,
pub interop_namespace_object2_used: bool,
pub interop_namespace_object2_name: Option<Atom>,
pub interop_default_access_used: bool,
pub interop_default_access_name: Option<Atom>,
pub global_scope_ident: Vec<ConcatenatedModuleIdent>,
pub idents: Vec<ConcatenatedModuleIdent>,
pub all_used_names: HashSet<Atom>,
pub binding_to_ref: FxIndexMap<(Atom, SyntaxContext), Vec<ConcatenatedModuleIdent>>,
pub public_path_auto_replacement: Option<bool>,
pub static_url_replacement: bool,
}
impl ConcatenatedModuleInfo {
pub fn get_internal_name<'me>(&'me self, atom: &Atom) -> Option<&'me Atom> {
if let Some(name) = self.internal_names.get(atom) {
return Some(name);
}
if atom.as_str() == "default" {
return self.internal_names.get(&*DEFAULT_EXPORT_ATOM);
}
if let Some(name) = &self.namespace_object_name
&& name == atom
{
return Some(name);
}
if self.interop_default_access_used
&& let Some(name) = &self.interop_default_access_name
&& name == atom
{
return Some(name);
}
if self.interop_namespace_object_used
&& let Some(name) = &self.interop_namespace_object_name
&& name == atom
{
return Some(name);
}
if self.interop_namespace_object2_used
&& let Some(name) = &self.interop_namespace_object2_name
&& name == atom
{
return Some(name);
}
None
}
}
#[derive(Debug, Clone)]
pub struct ExternalModuleInfo {
pub index: usize,
pub module: ModuleIdentifier,
pub interop_namespace_object_used: bool,
pub interop_namespace_object_name: Option<Atom>,
pub interop_namespace_object2_used: bool,
pub interop_namespace_object2_name: Option<Atom>,
pub interop_default_access_used: bool,
pub interop_default_access_name: Option<Atom>,
pub name: Option<Atom>,
pub deferred: bool,
pub deferred_namespace_object_name: Option<Atom>,
pub deferred_namespace_object_used: bool,
pub deferred_name: Option<Atom>,
pub runtime_requirements: RuntimeGlobals,
}
impl ExternalModuleInfo {
pub fn new(index: usize, module: ModuleIdentifier) -> Self {
Self {
index,
module,
interop_namespace_object_used: false,
interop_namespace_object_name: None,
interop_namespace_object2_used: false,
interop_namespace_object2_name: None,
interop_default_access_used: false,
interop_default_access_name: None,
name: None,
deferred: false,
deferred_namespace_object_name: None,
deferred_namespace_object_used: false,
deferred_name: None,
runtime_requirements: RuntimeGlobals::default(),
}
}
}
#[derive(Debug, Clone)]
struct ConcatenatedImport {
connection: Arc<ModuleGraphConnection>,
runtime_condition: RuntimeCondition,
non_defer_access: NonDeferAccess,
}
#[derive(Debug, Clone)]
struct MergedConcatenatedImport {
runtime_condition: RuntimeCondition,
non_defer_access: NonDeferAccess,
}
#[derive(Debug, Clone)]
pub enum ModuleInfo {
External(ExternalModuleInfo),
Concatenated(Box<ConcatenatedModuleInfo>),
}
impl ModuleInfo {
pub fn is_external(&self) -> bool {
matches!(self, ModuleInfo::External(_))
}
pub fn try_as_concatenated_mut(&mut self) -> Option<&mut ConcatenatedModuleInfo> {
if let Self::Concatenated(v) = self {
Some(v)
} else {
None
}
}
pub fn try_as_external(&self) -> Option<&ExternalModuleInfo> {
if let Self::External(v) = self {
Some(v)
} else {
None
}
}
pub fn try_as_concatenated(&self) -> Option<&ConcatenatedModuleInfo> {
if let Self::Concatenated(v) = self {
Some(v)
} else {
None
}
}
pub fn as_concatenated_mut(&mut self) -> &mut ConcatenatedModuleInfo {
if let Self::Concatenated(v) = self {
v
} else {
panic!("should convert as concatenated module info")
}
}
pub fn as_external(&self) -> &ExternalModuleInfo {
if let Self::External(v) = self {
v
} else {
panic!("should convert as external module info")
}
}
pub fn as_concatenated(&self) -> &ConcatenatedModuleInfo {
if let Self::Concatenated(v) = self {
v
} else {
panic!("should convert as concatenated module info")
}
}
pub fn id(&self) -> ModuleIdentifier {
match self {
ModuleInfo::External(e) => e.module,
ModuleInfo::Concatenated(c) => c.module,
}
}
pub fn set_interop_namespace_object_used(&mut self, v: bool) {
match self {
ModuleInfo::External(e) => e.interop_namespace_object_used = v,
ModuleInfo::Concatenated(c) => c.interop_namespace_object_used = v,
}
}
pub fn set_interop_namespace_object_name(&mut self, v: Option<Atom>) {
match self {
ModuleInfo::External(e) => e.interop_namespace_object_name = v,
ModuleInfo::Concatenated(c) => c.interop_namespace_object_name = v,
}
}
pub fn set_interop_namespace_object2_used(&mut self, v: bool) {
match self {
ModuleInfo::External(e) => e.interop_namespace_object2_used = v,
ModuleInfo::Concatenated(c) => c.interop_namespace_object2_used = v,
}
}
pub fn set_interop_namespace_object2_name(&mut self, v: Option<Atom>) {
match self {
ModuleInfo::External(e) => e.interop_namespace_object2_name = v,
ModuleInfo::Concatenated(c) => c.interop_namespace_object2_name = v,
}
}
pub fn set_deferred_namespace_object_used(&mut self, v: bool) {
match self {
ModuleInfo::External(e) => e.deferred_namespace_object_used = v,
ModuleInfo::Concatenated(_) => unreachable!(),
}
}
pub fn get_interop_namespace_object_used(&self) -> bool {
match self {
ModuleInfo::External(e) => e.interop_namespace_object_used,
ModuleInfo::Concatenated(c) => c.interop_namespace_object_used,
}
}
pub fn get_interop_namespace_object_name(&self) -> Option<&Atom> {
match self {
ModuleInfo::External(e) => e.interop_namespace_object_name.as_ref(),
ModuleInfo::Concatenated(c) => c.interop_namespace_object_name.as_ref(),
}
}
pub fn get_interop_namespace_object2_used(&self) -> bool {
match self {
ModuleInfo::External(e) => e.interop_namespace_object2_used,
ModuleInfo::Concatenated(c) => c.interop_namespace_object2_used,
}
}
pub fn get_interop_namespace_object2_name(&self) -> Option<&Atom> {
match self {
ModuleInfo::External(e) => e.interop_namespace_object2_name.as_ref(),
ModuleInfo::Concatenated(c) => c.interop_namespace_object2_name.as_ref(),
}
}
pub fn get_interop_default_access_name(&self) -> Option<&Atom> {
match self {
ModuleInfo::External(e) => e.interop_default_access_name.as_ref(),
ModuleInfo::Concatenated(c) => c.interop_default_access_name.as_ref(),
}
}
pub fn get_interop_default_access_used(&self) -> bool {
match self {
ModuleInfo::External(e) => e.interop_default_access_used,
ModuleInfo::Concatenated(c) => c.interop_default_access_used,
}
}
pub fn get_deferred_namespace_object_name(&self) -> Option<&Atom> {
match self {
ModuleInfo::External(e) => e.deferred_namespace_object_name.as_ref(),
ModuleInfo::Concatenated(_) => unreachable!(),
}
}
pub fn set_interop_default_access_used(&mut self, v: bool) {
match self {
ModuleInfo::External(e) => e.interop_default_access_used = v,
ModuleInfo::Concatenated(c) => c.interop_default_access_used = v,
}
}
pub fn set_interop_default_access_name(&mut self, v: Option<Atom>) {
match self {
ModuleInfo::External(e) => e.interop_default_access_name = v,
ModuleInfo::Concatenated(c) => c.interop_default_access_name = v,
}
}
pub fn get_runtime_requirements(&self) -> &RuntimeGlobals {
match self {
ModuleInfo::External(e) => &e.runtime_requirements,
ModuleInfo::Concatenated(c) => &c.runtime_requirements,
}
}
}
impl ModuleInfo {
pub fn index(&self) -> usize {
match self {
ModuleInfo::External(e) => e.index,
ModuleInfo::Concatenated(c) => c.index,
}
}
}
#[derive(Default, Clone, Debug)]
pub struct ImportSpec {
pub atoms: BTreeMap<Atom, Atom>,
pub default_import: Option<Atom>,
pub ns_import: Option<Atom>,
}
#[impl_source_map_config]
#[cacheable]
#[derive(Debug)]
pub struct ConcatenatedModule {
id: ModuleIdentifier,
root_module_ctxt: RootModuleContext,
modules: Vec<ConcatenatedInnerModule>,
runtime: Option<RuntimeSpec>,
blocks: Vec<AsyncDependenciesBlockIdentifier>,
dependencies: Vec<DependencyId>,
#[cacheable(with=As<SourceSizeCacheSerde>)]
cached_source_sizes: SourceSizeCache,
diagnostics: Vec<Diagnostic>,
build_info: BuildInfo,
}
#[allow(unused)]
impl ConcatenatedModule {
pub fn new(
id: ModuleIdentifier,
root_module_ctxt: RootModuleContext,
mut modules: Vec<ConcatenatedInnerModule>,
runtime: Option<RuntimeSpec>,
) -> Self {
let RootModuleContext {
module_argument,
exports_argument,
..
} = root_module_ctxt;
Self {
id,
root_module_ctxt,
modules,
runtime,
dependencies: vec![],
blocks: vec![],
cached_source_sizes: SourceSizeCache::default(),
diagnostics: vec![],
build_info: BuildInfo {
cacheable: true,
strict: true,
module_argument,
exports_argument,
top_level_declarations: Some(Default::default()),
..Default::default()
},
source_map_kind: SourceMapKind::empty(),
}
}
pub fn create(
root_module_ctxt: RootModuleContext,
mut modules: Vec<ConcatenatedInnerModule>,
hash_function: Option<HashFunction>,
runtime: Option<RuntimeSpec>,
compilation: &Compilation,
) -> Self {
modules.sort_unstable_by(|a, b| a.id.cmp(&b.id));
let id = Self::create_identifier(&root_module_ctxt, &modules, hash_function);
Self::new(id.as_str().into(), root_module_ctxt, modules, runtime)
}
fn create_identifier(
root_module_ctxt: &RootModuleContext,
modules: &Vec<ConcatenatedInnerModule>,
hash_function: Option<HashFunction>,
) -> String {
let mut identifiers = vec![];
for m in modules {
identifiers.push(m.shorten_id.as_str());
}
let mut hash = RspackHash::new(&hash_function.unwrap_or(HashFunction::MD4));
if let Some(id) = identifiers.first() {
hash.write(id.as_bytes());
}
for id in identifiers.iter().skip(1) {
hash.write(b" ");
hash.write(id.as_bytes());
}
let res = hash.digest(&HashDigest::Hex);
format!("{}|{}", root_module_ctxt.id, res.encoded())
}
pub fn id(&self) -> ModuleIdentifier {
self.id
}
pub fn get_root(&self) -> ModuleIdentifier {
self.root_module_ctxt.id
}
pub fn get_modules(&self) -> &[ConcatenatedInnerModule] {
self.modules.as_slice()
}
}
impl Identifiable for ConcatenatedModule {
#[inline]
fn identifier(&self) -> ModuleIdentifier {
self.id
}
}
impl DependenciesBlock for ConcatenatedModule {
fn add_block_id(&mut self, block: AsyncDependenciesBlockIdentifier) {
self.blocks.push(block)
}
fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] {
&self.blocks
}
fn add_dependency_id(&mut self, dependency: DependencyId) {
self.dependencies.push(dependency)
}
fn remove_dependency_id(&mut self, dependency: DependencyId) {
self.dependencies.retain(|d| d != &dependency)
}
fn get_dependencies(&self) -> &[DependencyId] {
&self.dependencies
}
}
pub fn render_imports(source: &str, attr: Option<&str>, import_spec: &ImportSpec) -> String {
let atoms = &import_spec.atoms;
let default_import = import_spec.default_import.as_ref();
let ns_import = import_spec.ns_import.as_ref();
let source_str = format!("{}{}", json_stringify_str(source), attr.unwrap_or_default());
let mut render_default = false;
let mut render_ns = false;
let import_ns_stmt = if let Some(ns_import) = ns_import {
render_ns = true;
format!(
"import {}* as {ns_import} from {source_str};\n",
default_import
.map(|default_import| {
render_default = true;
format!("{default_import}, ")
})
.unwrap_or_default()
)
} else {
Default::default()
};
let import_stmt = if atoms.is_empty() {
if render_ns {
Default::default()
} else {
format!(
"import {}{};\n",
default_import
.map(|default_atom| { format!("{default_atom} from ") })
.unwrap_or_default(),
&source_str
)
}
} else {
format!(
"import {}{{ {} }} from {};\n",
if render_default {
Default::default()
} else {
default_import
.map(|default_atom| format!("{default_atom}, "))
.unwrap_or_default()
},
atoms
.iter()
.map(|(atom, internal)| {
if atom == internal {
atom.to_string()
} else {
format!("{atom} as {internal}")
}
})
.collect::<Vec<String>>()
.join(", "),
source_str
)
};
format!("{import_ns_stmt}{import_stmt}")
}
#[cacheable_dyn]
#[async_trait::async_trait]
impl Module for ConcatenatedModule {
fn module_type(&self) -> &ModuleType {
&ModuleType::JsEsm
}
fn factory_meta(&self) -> Option<&FactoryMeta> {
self.root_module_ctxt.factory_meta.as_ref()
}
fn set_factory_meta(&mut self, v: FactoryMeta) {
self.root_module_ctxt.factory_meta = Some(v);
}
fn build_info(&self) -> &BuildInfo {
&self.build_info
}
fn build_info_mut(&mut self) -> &mut BuildInfo {
&mut self.build_info
}
fn build_meta(&self) -> &BuildMeta {
&self.root_module_ctxt.build_meta
}
fn build_meta_mut(&mut self) -> &mut BuildMeta {
&mut self.root_module_ctxt.build_meta
}
fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] {
&[SourceType::JavaScript]
}
fn source(&self) -> Option<&BoxSource> {
None
}
fn get_layer(&self) -> Option<&ModuleLayer> {
self.root_module_ctxt.layer.as_ref()
}
fn readable_identifier(&self, _context: &Context) -> Cow<'_, str> {
let mut modules_count_buffer = itoa::Buffer::new();
let modules_count_str = modules_count_buffer.format(self.modules.len() - 1);
Cow::Owned(format!(
"{} + {} modules",
self.root_module_ctxt.readable_identifier, modules_count_str
))
}
fn size(&self, source_type: Option<&SourceType>, _compilation: Option<&Compilation>) -> f64 {
if let Some(source_type) = source_type {
if let Some(size) = self.cached_source_sizes.get(source_type) {
return size;
}
let size = self.modules.iter().fold(0.0, |acc, cur| acc + cur.size);
self.cached_source_sizes.get_or_insert(source_type, size)
} else {
self.modules.iter().fold(0.0, |acc, cur| acc + cur.size)
}
}
async fn build(
mut self: Box<Self>,
_build_context: BuildContext,
compilation: Option<&Compilation>,
) -> Result<BuildResult> {
let compilation = compilation.expect("should pass compilation");
let module_graph = compilation.get_module_graph();
let modules = self
.modules
.iter()
.map(|item| Some(&item.id))
.collect::<HashSet<_>>();
let root_module = module_graph
.module_by_identifier(&self.root_module_ctxt.id)
.expect("should have root module");
self.build_info.inline_exports = root_module.build_info().inline_exports;
for m in self.modules.iter() {
let module = module_graph
.module_by_identifier(&m.id)
.expect("should have module");
let cur_build_info = module.build_info();
if !cur_build_info.cacheable {
self.build_info.cacheable = false;
}
for dep_id in module.get_dependencies().iter() {
let dep = module_graph.dependency_by_id(dep_id);
let module_id_of_dep = module_graph.module_identifier_by_dependency_id(dep_id);
if !is_esm_dep_like(dep) || !modules.contains(&module_id_of_dep) {
self.dependencies.push(*dep_id);
}
}
for b in module.get_blocks() {
self.blocks.push(*b);
}
self.diagnostics.extend(module.diagnostics().into_owned());
let module_build_info = module.build_info();
if let Some(decls) = &module_build_info.top_level_declarations
&& let Some(top_level_declarations) = &mut self.build_info.top_level_declarations
{
top_level_declarations.extend(decls.iter().cloned());
} else {
self.build_info.top_level_declarations = None;
}
if module_build_info.need_create_require {
self.build_info.need_create_require = true;
}
self
.build_info
.assets
.extend(module_build_info.assets.as_ref().clone());
}
Ok(BuildResult {
module: BoxModule::new(self),
dependencies: vec![],
blocks: vec![],
optimization_bailouts: vec![],
})
}
async fn code_generation(
&self,
code_generation_context: &mut ModuleCodeGenerationContext,
) -> Result<CodeGenerationResult> {
let ModuleCodeGenerationContext {
compilation,
runtime_template,
runtime,
..
} = code_generation_context;
let runtime = if let Some(self_runtime) = &self.runtime
&& let Some(runtime) = runtime
{
Some(Cow::Owned(
runtime
.intersection(self_runtime)
.copied()
.collect::<RuntimeSpec>(),
))
} else {
runtime.map(Cow::Borrowed)
};
let runtime = runtime.as_deref();
let context = compilation.options.context.clone();
let (references_info, module_to_info_map) = self.get_modules_with_info(
compilation.get_module_graph(),
&compilation.module_graph_cache_artifact,
runtime,
&compilation
.build_module_graph_artifact
.side_effects_state_artifact,
&compilation.imported_by_defer_modules_artifact,
&compilation.exports_info_artifact,
);
let mut needed_namespace_objects = IdentifierIndexSet::default();
let mut needed_namespace_objects_queue = VecDeque::new();
let mut all_used_names: HashSet<Atom> = RESERVED_NAMES.iter().map(|s| Atom::new(*s)).collect();
let arc_map = Arc::new(module_to_info_map);
let tmp = rspack_parallel::scope::<_, Result<_>>(|token| {
arc_map.iter().for_each(|(id, info)| {
let concatenation_scope = if let ModuleInfo::Concatenated(info) = &info {
let concatenation_scope =
ConcatenationScope::new(self.id, arc_map.clone(), info.as_ref().clone());
Some(concatenation_scope)
} else {
None
};
let s = unsafe { token.used((&self, &compilation, runtime, id, info)) };
s.spawn(|(module, compilation, runtime, id, info)| async move {
let updated_module_info = module
.analyze_module(compilation, info, runtime, concatenation_scope)
.await?;
Ok((*id, updated_module_info))
});
})
})
.await
.into_iter()
.map(|r| r.to_rspack_result())
.collect::<Result<Vec<_>>>()?;
let mut updated_pairs = vec![];
for item in tmp {
updated_pairs.push(item?);
}
let mut module_to_info_map = Arc::into_inner(arc_map).expect("reference count should be one");
for (id, module_info) in updated_pairs {
module_to_info_map.insert(id, module_info);
}
let mut top_level_declarations: HashSet<Atom> = HashSet::default();
let mut public_path_auto_replace: bool = false;
let mut static_url_replace: bool = false;
for (module_info_id, _) in references_info.iter() {
let Some(ModuleInfo::Concatenated(info)) = module_to_info_map.get(module_info_id) else {
continue;
};
if info.has_ast {
all_used_names.extend(info.all_used_names.iter().cloned());
}
}
for (id, info) in module_to_info_map.iter_mut() {
if let ModuleInfo::Concatenated(info) = info {
compilation
.plugin_driver
.concatenated_module_hooks
.concatenated_info
.call(compilation, *id, runtime, info, &mut all_used_names)
.await?;
}
}
let module_graph = compilation.get_module_graph();
let mut import_stmts = FxIndexMap::<(String, Option<String>), ImportSpec>::default();
let (escaped_name_entries, escaped_identifier_entries) = module_to_info_map
.par_values()
.map(|info| {
let (name_capacity, identifier_capacity) = match info {
ModuleInfo::Concatenated(info) => {
let import_map = info.import_map.as_ref();
let import_sources = import_map.map_or(0, |map| map.len());
let imported_names = import_map.map_or(0, |map| {
map
.values()
.map(|imported| {
imported.specifiers.len() + usize::from(imported.namespace.is_some())
})
.sum::<usize>()
});
(
info.binding_to_ref.len() + imported_names,
1 + import_sources,
)
}
ModuleInfo::External(_) => (0, 1),
};
let mut escaped_names =
HashMap::with_capacity_and_hasher(name_capacity, Default::default());
let mut escaped_identifiers = Vec::with_capacity(identifier_capacity);
let readable_identifier = get_cached_readable_identifier(
&info.id(),
module_graph,
&compilation.module_static_cache,
&context,
);
let splitted_readable_identifier = split_readable_identifier(&readable_identifier);
escaped_identifiers.push((readable_identifier, splitted_readable_identifier));
match info {
ModuleInfo::Concatenated(info) => {
for (id, _) in info.binding_to_ref.iter() {
escaped_names
.entry(id.0.clone())
.or_insert_with(|| escape_name_atom_ref(&id.0));
}
if let Some(import_map) = &info.import_map {
for ((source, _), imported) in import_map.iter() {
let specifiers = &imported.specifiers;
escaped_identifiers
.push((source.clone(), split_readable_identifier(source.as_str())));
for atom in specifiers {
escaped_names
.entry(atom.clone())
.or_insert_with(|| escape_name_atom_ref(atom));
}
if let Some(ns_symbol) = &imported.namespace {
escaped_names
.entry(ns_symbol.clone())
.or_insert_with(|| escape_name_atom_ref(ns_symbol));
}
}
}
}
ModuleInfo::External(_) => (),
}
(
escaped_names.into_iter().collect::<Vec<_>>(),
escaped_identifiers,
)
})
.reduce(
|| (Vec::new(), Vec::new()),
|mut a, mut b| {
a.0.append(&mut b.0);
a.1.append(&mut b.1);
a
},
);
let mut escaped_names =
HashMap::with_capacity_and_hasher(escaped_name_entries.len(), Default::default());
for (name, escaped_name) in escaped_name_entries {
escaped_names.insert(name, escaped_name);
}
let mut escaped_identifiers =
HashMap::with_capacity_and_hasher(escaped_identifier_entries.len(), Default::default());
for (identifier, parts) in escaped_identifier_entries {
escaped_identifiers.insert(identifier, parts);
}
let mut name_allocator = NameAllocator::new(all_used_names);
for info in module_to_info_map.values_mut() {
let module = module_graph
.module_by_identifier(&info.id())
.expect("should have module identifier");
let readable_identifier = get_cached_readable_identifier(
&info.id(),
module_graph,
&compilation.module_static_cache,
&context,
);
let exports_type: BuildMetaExportsType = module.build_meta().exports_type;
let default_object: BuildMetaDefaultObject = module.build_meta().default_object;
match info {
ModuleInfo::Concatenated(info) => {
for (id, refs) in info.binding_to_ref.iter() {
let name = &id.0;
let ctxt = id.1;
if ctxt != info.module_ctxt {
continue;
}
if name_allocator.contains(name) {
let new_name = name_allocator.find_new_name(
escaped_names
.get(name)
.expect("should have escaped name")
.as_ref(),
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
);
info.internal_names.insert(name.clone(), new_name.clone());
top_level_declarations.insert(new_name.clone());
let source = info.source.as_mut().expect("should have source");
for identifier in refs {
let span = identifier.id.span();
let low = span.real_lo();
let high = span.real_hi();
if identifier.shorthand {
source.insert(high, format!(": {new_name}"), None);
continue;
}
source.replace(low, high, new_name.to_string(), None);
}
} else {
name_allocator.insert(name.clone());
info.internal_names.insert(name.clone(), name.clone());
top_level_declarations.insert(name.clone());
}
}
if let Some(import_map) = info.import_map.take() {
for ((source, attr), imported) in import_map {
let source_parts = escaped_identifiers
.get(&source)
.expect("should have escaped identifier");
let total_imported_atoms = import_stmts.entry((source, attr)).or_default();
if let Some(ns_import) = imported.namespace {
if let Some(internal_ns_import) = total_imported_atoms.ns_import.as_ref() {
info
.internal_names
.insert(ns_import, internal_ns_import.clone());
} else {
let ns_import_key = ns_import.clone();
let new_name = if name_allocator.contains(&ns_import) {
name_allocator.find_new_name(
escaped_names
.get(&ns_import)
.expect("should have escaped name")
.as_ref(),
&[],
)
} else {
name_allocator.insert(ns_import);
ns_import_key.clone()
};
info.internal_names.insert(ns_import_key, new_name.clone());
total_imported_atoms.ns_import = Some(new_name);
}
}
for atom in imported.specifiers {
if let Some(internal_atom) = total_imported_atoms.atoms.get(&atom) {
if let Some(raw_export_map) = info.raw_export_map.as_mut()
&& raw_export_map.contains_key(&atom)
{
raw_export_map.insert(atom.clone(), internal_atom.to_string());
}
info.internal_names.insert(atom, internal_atom.clone());
continue;
}
let new_name = if name_allocator.contains(&atom) {
let new_name = if atom == "default" {
name_allocator.find_new_name("", source_parts)
} else {
name_allocator.find_new_name(
escaped_names
.get(&atom)
.expect("should have escaped name")
.as_ref(),
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
)
};
if let Some(raw_export_map) = info.raw_export_map.as_mut()
&& raw_export_map.contains_key(&atom)
{
raw_export_map.insert(atom.clone(), new_name.to_string());
}
new_name
} else {
name_allocator.insert(atom.clone());
atom.clone()
};
if atom == "default" {
total_imported_atoms.default_import = Some(new_name.clone());
} else {
total_imported_atoms
.atoms
.insert(atom.clone(), new_name.clone());
}
info.internal_names.insert(atom, new_name);
}
}
}
if let Some(ref namespace_export_symbol) = info.namespace_export_symbol
&& namespace_export_symbol.starts_with(NAMESPACE_OBJECT_EXPORT)
&& namespace_export_symbol.len() > NAMESPACE_OBJECT_EXPORT.len()
{
let name =
Atom::from(namespace_export_symbol[NAMESPACE_OBJECT_EXPORT.len()..].to_string());
name_allocator.insert(name.clone());
info
.internal_names
.insert(namespace_export_symbol.clone(), name.clone());
top_level_declarations.insert(name.clone());
}
let namespace_object_name =
if let Some(ref namespace_export_symbol) = info.namespace_export_symbol {
info.internal_names.get(namespace_export_symbol).cloned()
} else {
Some(
name_allocator.find_new_name(
"namespaceObject",
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
),
)
};
if let Some(namespace_object_name) = namespace_object_name {
info.namespace_object_name = Some(namespace_object_name.clone());
top_level_declarations.insert(namespace_object_name);
}
if let Some(info_auto) = info.public_path_auto_replacement {
public_path_auto_replace = public_path_auto_replace || info_auto;
}
if info.static_url_replacement {
static_url_replace = true;
}
}
ModuleInfo::External(info) => {
let external_name: Atom = name_allocator.find_new_name(
"",
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
);
info.name = Some(external_name.clone());
top_level_declarations.insert(external_name.clone());
if info.deferred {
let external_name = name_allocator.find_new_name(
"deferred",
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
);
info.deferred_name = Some(external_name.clone());
top_level_declarations.insert(external_name.clone());
let external_name_interop = name_allocator.find_new_name(
"deferredNamespaceObject",
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
);
info.deferred_namespace_object_name = Some(external_name_interop.clone());
top_level_declarations.insert(external_name_interop.clone());
}
}
}
if exports_type != BuildMetaExportsType::Namespace {
let external_name_interop: Atom = name_allocator.find_new_name(
"namespaceObject",
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
);
info.set_interop_namespace_object_name(Some(external_name_interop.clone()));
top_level_declarations.insert(external_name_interop.clone());
}
if exports_type == BuildMetaExportsType::Default
&& !matches!(default_object, BuildMetaDefaultObject::Redirect)
{
let external_name_interop: Atom = name_allocator.find_new_name(
"namespaceObject2",
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
);
info.set_interop_namespace_object2_name(Some(external_name_interop.clone()));
top_level_declarations.insert(external_name_interop.clone());
}
if matches!(
exports_type,
BuildMetaExportsType::Dynamic | BuildMetaExportsType::Unset
) {
let external_name_interop: Atom = name_allocator.find_new_name(
"default",
escaped_identifiers
.get(&readable_identifier)
.expect("should have escaped identifier"),
);
info.set_interop_default_access_name(Some(external_name_interop.clone()));
top_level_declarations.insert(external_name_interop.clone());
}
}
fast_set(&mut escaped_names, HashMap::default());
fast_set(&mut escaped_identifiers, HashMap::default());
fast_set(&mut name_allocator, NameAllocator::default());
let mut changes = module_to_info_map
.par_values()
.filter_map(|info| {
let ModuleInfo::Concatenated(info) = info else {
return None;
};
let module = module_graph
.module_by_identifier(&info.module)
.expect("should have module");
let build_meta = module.build_meta();
let mut refs = vec![];
for reference in info.global_scope_ident.iter() {
let name = &reference.id.sym;
let match_result = ConcatenationScope::match_module_reference(name.as_str());
if let Some(match_info) = match_result {
let referenced_info_id = &references_info[match_info.index].0;
refs.push((
reference.clone(),
referenced_info_id,
match_info
.ids
.into_iter()
.map(|item| Atom::from(item.as_str()))
.collect::<Vec<_>>(),
match_info.call,
!match_info.direct_import,
match_info.deferred_import,
build_meta.strict_esm_module,
match_info.asi_safe,
));
}
}
let mut changes = vec![];
for (
reference_ident,
referenced_info_id,
export_name,
call,
call_context,
deferred_import,
strict_esm_module,
asi_safe,
) in refs
{
let final_name = Self::get_final_name(
compilation.get_module_graph(),
&compilation.module_graph_cache_artifact,
&compilation.exports_info_artifact,
&compilation.module_static_cache,
referenced_info_id,
export_name,
&module_to_info_map,
runtime,
deferred_import,
call,
call_context,
strict_esm_module,
asi_safe,
&context,
);
let span = reference_ident.id.span();
let low = span.real_lo();
let high = span.real_hi();
changes.push((final_name, (low, high + 2)));
}
Some((info.module, changes))
})
.collect::<Vec<_>>();
for (module_info_id, module_changes) in changes.iter_mut() {
for (name_result, (low, high)) in mem::take(module_changes) {
name_result.apply_to_info(
&mut module_to_info_map,
&mut needed_namespace_objects,
&mut needed_namespace_objects_queue,
);
let info = module_to_info_map
.get_mut(module_info_id)
.and_then(|info| info.try_as_concatenated_mut())
.expect("should have concatenate module info");
let source = info.source.as_mut().expect("should have source");
source.replace(low, high, name_result.name, None);
}
}
fast_set(&mut changes, Vec::new());
let mut exports_map: HashMap<Atom, String> = HashMap::default();
let mut unused_exports: HashSet<Atom> = HashSet::default();
let mut inlined_exports: HashSet<Atom> = HashSet::default();
let root_info = module_to_info_map
.get(&self.root_module_ctxt.id)
.expect("should have root module");
let root_module_id = root_info.id();
let module_graph = compilation.get_module_graph();
let root_module = module_graph
.module_by_identifier(&root_module_id)
.expect("should have box module");
let strict_esm_module = root_module.build_meta().strict_esm_module;
let exports_info = compilation
.exports_info_artifact
.get_prefetched_exports_info(&root_module_id, PrefetchExportsInfoMode::Default);
let mut exports_final_names: Vec<(String, String)> = vec![];
for (_, export_info) in exports_info.exports() {
let name = export_info.name().cloned().unwrap_or_else(|| "".into());
if matches!(export_info.provided(), Some(ExportProvided::NotProvided)) {
continue;
}
let used_name = export_info.get_used_name(None, runtime);
let Some(used_name) = used_name else {
unused_exports.insert(name);
continue;
};
let UsedNameItem::Str(used_name) = used_name else {
inlined_exports.insert(name);
continue;
};
exports_map.insert(used_name.clone(), {
let final_name = Self::get_final_name(
compilation.get_module_graph(),
&compilation.module_graph_cache_artifact,
&compilation.exports_info_artifact,
&compilation.module_static_cache,
&root_module_id,
[name.clone()].to_vec(),
&module_to_info_map,
runtime,
false,
false,
false,
strict_esm_module,
Some(true),
&compilation.options.context,
);
final_name.apply_to_info(
&mut module_to_info_map,
&mut needed_namespace_objects,
&mut needed_namespace_objects_queue,
);
exports_final_names.push((used_name.to_string(), final_name.name.clone()));
format!(
"/* {} */ {}",
if export_info.is_reexport() {
"reexport"
} else {
"binding"
},
final_name.name
)
});
}
let mut result: ConcatSource = ConcatSource::default();
let mut should_add_esm_flag = false;
let mut chunk_init_fragments: Vec<Box<dyn InitFragment<ChunkRenderContext>>> = Vec::new();
for ((source, attr), import_spec) in import_stmts {
let content = render_imports(&source, attr.as_deref(), &import_spec);
chunk_init_fragments.push(Box::new(ConditionalInitFragment::new(
content.clone(),
InitFragmentStage::StageESMImports,
0,
crate::InitFragmentKey::ESMImport(content),
None,
RuntimeCondition::Boolean(true),
)));
}
let root_exports_info = compilation
.exports_info_artifact
.get_prefetched_exports_info(&self.id(), PrefetchExportsInfoMode::Default);
if root_exports_info.other_exports_info().get_used(runtime) != UsageState::Unused
|| root_exports_info
.get_read_only_export_info(&"__esModule".into())
.get_used(runtime)
!= UsageState::Unused
{
should_add_esm_flag = true
}
if !exports_map.is_empty() {
let mut definitions = Vec::new();
for (key, value) in exports_map.iter() {
definitions.push(format!(
"\n {}: {}",
property_name(key).expect("should convert to property_name"),
runtime_template.returning_function(value, "")
));
}
let exports_argument = self.get_exports_argument();
let should_skip_render_definitions = compilation
.plugin_driver
.concatenated_module_hooks
.exports_definitions
.call(
&mut exports_final_names,
compilation
.build_chunk_graph_artifact
.chunk_graph
.is_entry_module(&self.id),
)
.await?;
if !matches!(should_skip_render_definitions, Some(true)) {
if should_add_esm_flag {
result.add(RawStringSource::from_static("// ESM COMPAT FLAG\n"));
result.add(RawStringSource::from(
runtime_template.define_es_module_flag_statement(self.get_exports_argument()),
));
}
result.add(RawStringSource::from_static("\n// EXPORTS\n"));
result.add(RawStringSource::from(format!(
"{}({}, {{{}\n}});\n",
runtime_template.render_runtime_globals(&RuntimeGlobals::DEFINE_PROPERTY_GETTERS),
runtime_template.render_exports_argument(exports_argument),
definitions.join(",")
)));
}
}
if !unused_exports.is_empty() {
result.add(RawStringSource::from(format!(
"\n// UNUSED EXPORTS: {}\n",
join_atom(unused_exports.iter(), ", ")
)));
}
if !inlined_exports.is_empty() {
result.add(RawStringSource::from(format!(
"\n// INLINED EXPORTS: {}\n",
join_atom(inlined_exports.iter(), ", ")
)));
}
let mut namespace_object_sources: IdentifierMap<String> = IdentifierMap::default();
while let Some(module_info_id) = needed_namespace_objects_queue.pop_front() {
let module_info = module_to_info_map
.get(&module_info_id)
.map(|m| m.as_concatenated())
.expect("should have module info");
let module_graph = compilation.get_module_graph();
let box_module = module_graph
.module_by_identifier(&module_info_id)
.expect("should have box module");
let module_readable_identifier = get_cached_readable_identifier(
&module_info_id,
module_graph,
&compilation.module_static_cache,
&context,
);
let strict_esm_module = box_module.build_meta().strict_esm_module;
let name_space_name = module_info.namespace_object_name.clone();
if let Some(ref _namespace_export_symbol) = module_info.namespace_export_symbol {
continue;
}
let mut ns_obj = Vec::new();
let exports_info = compilation
.exports_info_artifact
.get_prefetched_exports_info(&module_info_id, PrefetchExportsInfoMode::Default);
for (_, export_info) in exports_info.exports() {
if matches!(export_info.provided(), Some(ExportProvided::NotProvided)) {
continue;
}
if let Some(UsedNameItem::Str(used_name)) = export_info.get_used_name(None, runtime) {
let final_name = Self::get_final_name(
compilation.get_module_graph(),
&compilation.module_graph_cache_artifact,
&compilation.exports_info_artifact,
&compilation.module_static_cache,
&module_info_id,
vec![export_info.name().cloned().unwrap_or_else(|| "".into())],
&module_to_info_map,
runtime,
false,
false,
false,
strict_esm_module,
Some(true),
&context,
);
final_name.apply_to_info(
&mut module_to_info_map,
&mut needed_namespace_objects,
&mut needed_namespace_objects_queue,
);
ns_obj.push(format!(
"\n {}: {}",
property_name(&used_name).expect("should have property_name"),
runtime_template.returning_function(&final_name.name, "")
));
}
}
let name = name_space_name.expect("should have name_space_name");
let define_getters = if !ns_obj.is_empty() {
format!(
"{}({}, {{ {} }});\n",
runtime_template.render_runtime_globals(&RuntimeGlobals::DEFINE_PROPERTY_GETTERS),
name,
ns_obj.join(",")
)
} else {
String::new()
};
namespace_object_sources.insert(
module_info_id,
format!(
"// NAMESPACE OBJECT: {}\nvar {} = {{}};\n{}({});\n{}\n",
module_readable_identifier,
name,
runtime_template.render_runtime_globals(&RuntimeGlobals::MAKE_NAMESPACE_OBJECT),
name,
define_getters
),
);
}
for info in module_to_info_map.values() {
if let Some(info) = info.try_as_concatenated()
&& let Some(source) = namespace_object_sources.get(&info.module)
{
result.add(RawStringSource::from(source.as_str()));
}
if let Some(info) = info.try_as_external()
&& info.deferred
{
let module_id = json_stringify(
ChunkGraph::get_module_id(&compilation.module_ids_artifact, info.module)
.expect("should have module id"),
);
let module = module_graph
.module_by_identifier(&info.module)
.expect("should have module");
let module_readable_identifier = get_cached_readable_identifier(
&info.module,
module_graph,
&compilation.module_static_cache,
&context,
);
let loader = runtime_template.get_optimized_deferred_module(
module.get_exports_type(
module_graph,
&compilation.module_graph_cache_artifact,
&compilation.exports_info_artifact,
root_module.build_meta().strict_esm_module,
),
&module_id,
Default::default(),
);
result.add(RawStringSource::from(format!(
"\n// DEFERRED EXTERNAL MODULE: {module_readable_identifier}\nvar {} = {loader};",
info
.deferred_name
.as_ref()
.expect("should have deferred_name"),
)));
if info.deferred_namespace_object_used {
let module_id = json_stringify(
ChunkGraph::get_module_id(&compilation.module_ids_artifact, info.module)
.expect("should have module id"),
);
let module = module_graph
.module_by_identifier(&info.module)
.expect("should have module");
result.add(RawStringSource::from(format!(
"\nvar {} = /*#__PURE__*/{}({}, {});",
info
.deferred_namespace_object_name
.as_ref()
.expect("should have deferred_namespace_object_name"),
runtime_template
.render_runtime_globals(&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT),
module_id,
render_make_deferred_namespace_mode_from_exports_type(module.get_exports_type(
module_graph,
&compilation.module_graph_cache_artifact,
&compilation.exports_info_artifact,
root_module.build_meta().strict_esm_module,
)),
)));
}
}
}
let module_graph = compilation.get_module_graph();
for (module_info_id, reference_info) in references_info {
let mut name = None;
let mut is_conditional = false;
let info = module_to_info_map
.get_mut(&module_info_id)
.expect("should have module info");
let module_readable_identifier = get_cached_readable_identifier(
&module_info_id,
module_graph,
&compilation.module_static_cache,
&context,
);
match info {
ModuleInfo::Concatenated(info) => {
result.add(RawStringSource::from(
format!("\n;// CONCATENATED MODULE: {module_readable_identifier}\n").as_str(),
));
result.add(info.source.take().expect("should have source"));
chunk_init_fragments.extend(mem::take(&mut info.chunk_init_fragments));
runtime_template
.runtime_requirements_mut()
.insert(info.runtime_requirements);
name.clone_from(&info.namespace_object_name);
}
ModuleInfo::External(info) => {
if !info.deferred {
result.add(RawStringSource::from(format!(
"\n// EXTERNAL MODULE: {module_readable_identifier}\n"
)));
let condition = runtime_template.runtime_condition_expression(
&compilation.build_chunk_graph_artifact.chunk_graph,
Some(&reference_info.runtime_condition),
runtime,
);
if condition != "true" {
is_conditional = true;
result.add(RawStringSource::from(format!("if ({condition}) {{\n")));
}
result.add(RawStringSource::from(format!(
"var {} = {}({});",
info.name.as_ref().expect("should have name"),
runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
json_stringify(
ChunkGraph::get_module_id(&compilation.module_ids_artifact, info.module)
.expect("should have module id")
)
)));
name.clone_from(&info.name);
}
if info.deferred && reference_info.non_defer_access == NonDeferAccess(true) {
result.add(RawStringSource::from(format!(
"\n// non-deferred import to a deferred module ({})\nvar {} = {}.a;",
get_cached_readable_identifier(
&info.module,
module_graph,
&compilation.module_static_cache,
&context,
),
info.name.as_ref().expect("should have name"),
info
.deferred_name
.as_ref()
.expect("should have deferred_name"),
)));
}
}
}
if info.get_interop_namespace_object_used() {
result.add(RawStringSource::from(format!(
"\nvar {} = /*#__PURE__*/{}({}, 2);",
info
.get_interop_namespace_object_name()
.expect("should have interop_namespace_object_name"),
runtime_template.render_runtime_globals(&RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT),
name.as_ref().expect("should have name")
)));
}
if info.get_interop_namespace_object2_used() {
result.add(RawStringSource::from(format!(
"\nvar {} = /*#__PURE__*/{}({});",
info
.get_interop_namespace_object2_name()
.expect("should have interop_namespace_object2_name"),
runtime_template.render_runtime_globals(&RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT),
name.as_ref().expect("should have name")
)));
}
if info.get_interop_default_access_used() {
result.add(RawStringSource::from(format!(
"\nvar {} = /*#__PURE__*/{}({});",
info
.get_interop_default_access_name()
.expect("should have interop_default_access_name"),
runtime_template.render_runtime_globals(&RuntimeGlobals::COMPAT_GET_DEFAULT_EXPORT),
name.expect("should have name")
)));
}
if is_conditional {
result.add(RawStringSource::from_static("\n}"));
}
}
fast_set(&mut module_to_info_map, IdentifierIndexMap::default());
let mut code_generation_result = CodeGenerationResult::default();
code_generation_result.add(SourceType::JavaScript, CachedSource::new(result).boxed());
code_generation_result.chunk_init_fragments = chunk_init_fragments;
if public_path_auto_replace {
code_generation_result
.data
.insert(CodeGenerationPublicPathAutoReplace(true));
}
if static_url_replace {
code_generation_result.data.insert(URLStaticMode);
}
code_generation_result
.data
.insert(CodeGenerationDataTopLevelDeclarations::new(
top_level_declarations,
));
if !exports_final_names.is_empty() {
let exports_final_names_map: HashMap<String, String> =
exports_final_names.into_iter().collect();
code_generation_result
.data
.insert(CodeGenerationExportsFinalNames::new(
exports_final_names_map,
));
}
Ok(code_generation_result)
}
async fn get_runtime_hash(
&self,
compilation: &Compilation,
generation_runtime: Option<&RuntimeSpec>,
) -> Result<RspackHashDigest> {
let mut hasher = RspackHash::from(&compilation.options.output);
let runtime = if let Some(self_runtime) = &self.runtime
&& let Some(generation_runtime) = generation_runtime
{
Some(Cow::Owned(
generation_runtime
.intersection(self_runtime)
.copied()
.collect::<RuntimeSpec>(),
))
} else {
generation_runtime.map(Cow::Borrowed)
};
let runtime = runtime.as_deref();
let concatenation_entries = self.create_concatenation_list(
runtime,
compilation.get_module_graph(),
&compilation.module_graph_cache_artifact,
&compilation
.build_module_graph_artifact
.side_effects_state_artifact,
&compilation.exports_info_artifact,
);
let hashes = rspack_parallel::scope::<_, Result<_>>(|token| {
concatenation_entries.into_iter().for_each(|job| {
let s = unsafe { token.used((job, compilation, generation_runtime)) };
s.spawn(|(job, compilation, generation_runtime)| async move {
match job {
ConcatenationEntry::Concatenated(e) => {
let digest = compilation
.get_module_graph()
.module_by_identifier(&e.module)
.expect("should have module")
.get_runtime_hash(compilation, generation_runtime)
.await?;
Ok(Some(digest.encoded().to_string()))
}
ConcatenationEntry::External(e) => Ok(
ChunkGraph::get_module_id(
&compilation.module_ids_artifact,
e.module(compilation.get_module_graph()),
)
.map(|id| id.to_string()),
),
}
})
})
})
.await
.into_iter()
.map(|res| res.to_rspack_result())
.collect::<Result<Vec<_>>>()?;
for hash in hashes {
(hash?).dyn_hash(&mut hasher);
}
module_update_hash(self, &mut hasher, compilation, generation_runtime);
Ok(hasher.digest(&compilation.options.output.hash_digest))
}
fn name_for_condition(&self) -> Option<Box<str>> {
self.root_module_ctxt.name_for_condition.clone()
}
fn lib_ident(&self, _options: LibIdentOptions) -> Option<Cow<'_, str>> {
self.root_module_ctxt.lib_indent.clone().map(Cow::Owned)
}
fn get_resolve_options(&self) -> Option<Arc<Resolve>> {
self.root_module_ctxt.resolve_options.clone()
}
fn get_code_generation_dependencies(&self) -> Option<&[BoxModuleDependency]> {
if let Some(deps) = self
.root_module_ctxt
.code_generation_dependencies
.as_deref()
&& !deps.is_empty()
{
Some(deps)
} else {
None
}
}
fn get_presentational_dependencies(&self) -> Option<&[BoxDependencyTemplate]> {
if let Some(deps) = self.root_module_ctxt.presentational_dependencies.as_deref()
&& !deps.is_empty()
{
Some(deps)
} else {
None
}
}
fn get_context(&self) -> Option<Box<Context>> {
self.root_module_ctxt.context.clone().map(Box::new)
}
fn get_side_effects_connection_state(
&self,
_module_graph: &ModuleGraph,
_module_graph_cache: &ModuleGraphCacheArtifact,
_side_effects_state_artifact: &SideEffectsStateArtifact,
_module_chain: &mut IdentifierSet,
_connection_state_cache: &mut IdentifierMap<ConnectionState>,
) -> ConnectionState {
self.root_module_ctxt.side_effect_connection_state
}
}
impl Diagnosable for ConcatenatedModule {
fn add_diagnostic(&mut self, diagnostic: Diagnostic) {
self.diagnostics.push(diagnostic);
}
fn add_diagnostics(&mut self, mut diagnostics: Vec<Diagnostic>) {
self.diagnostics.append(&mut diagnostics);
}
fn diagnostics(&self) -> Cow<'_, [Diagnostic]> {
Cow::Borrowed(&self.diagnostics)
}
}
struct ConcatenationArtifacts<'a> {
mg_cache: &'a ModuleGraphCacheArtifact,
side_effects_state_artifact: &'a SideEffectsStateArtifact,
exports_info_artifact: &'a ExportsInfoArtifact,
}
impl ConcatenatedModule {
fn get_modules_with_info(
&self,
mg: &ModuleGraph,
mg_cache: &ModuleGraphCacheArtifact,
runtime: Option<&RuntimeSpec>,
side_effects_state_artifact: &SideEffectsStateArtifact,
imported_by_defer_modules_artifact: &ImportedByDeferModulesArtifact,
exports_info_artifact: &ExportsInfoArtifact,
) -> (
Vec<(ModuleIdentifier, MergedConcatenatedImport)>,
IdentifierIndexMap<ModuleInfo>,
) {
let ordered_concatenation_list = self.create_concatenation_list(
runtime,
mg,
mg_cache,
side_effects_state_artifact,
exports_info_artifact,
);
let mut list = vec![];
let mut map = IdentifierIndexMap::default();
for (i, concatenation_entry) in ordered_concatenation_list.into_iter().enumerate() {
let module_id = concatenation_entry.module(mg);
map
.entry(module_id)
.or_insert_with(|| match &concatenation_entry {
ConcatenationEntry::Concatenated(_) => {
ModuleInfo::Concatenated(Box::new(ConcatenatedModuleInfo {
index: i,
module: module_id,
..Default::default()
}))
}
ConcatenationEntry::External(_) => ModuleInfo::External(ExternalModuleInfo {
deferred: mg.is_deferred(imported_by_defer_modules_artifact, &module_id),
..ExternalModuleInfo::new(i, module_id)
}),
});
let info = match concatenation_entry {
ConcatenationEntry::Concatenated(_) => MergedConcatenatedImport {
runtime_condition: RuntimeCondition::Boolean(true),
non_defer_access: NonDeferAccess(true),
},
ConcatenationEntry::External(e) => MergedConcatenatedImport {
runtime_condition: e.runtime_condition,
non_defer_access: e.non_defer_access,
},
};
list.push((module_id, info));
}
(list, map)
}
fn create_concatenation_list(
&self,
runtime: Option<&RuntimeSpec>,
mg: &ModuleGraph,
mg_cache: &ModuleGraphCacheArtifact,
side_effects_state_artifact: &SideEffectsStateArtifact,
exports_info_artifact: &ExportsInfoArtifact,
) -> Vec<ConcatenationEntry> {
let artifacts = ConcatenationArtifacts {
mg_cache,
side_effects_state_artifact,
exports_info_artifact,
};
mg_cache.cached_concatenated_module_entries(
(self.id, runtime.map(|r| get_runtime_key(r).clone())),
|| {
let root_module = self.root_module_ctxt.id;
let module_set: IdentifierIndexSet = self.modules.iter().map(|item| item.id).collect();
let mut list = vec![];
let mut exists_entries = IdentifierMap::default();
exists_entries.insert(
root_module,
MergedConcatenatedImport {
runtime_condition: RuntimeCondition::Boolean(true),
non_defer_access: NonDeferAccess(true),
},
);
let imports_map = module_set
.par_iter()
.map(|module| {
let imports =
self.get_concatenated_imports(module, &root_module, runtime, mg, &artifacts);
(*module, imports)
})
.collect::<IdentifierMap<_>>();
let imports = imports_map.get(&root_module).expect("should have imports");
for i in imports {
self.enter_module(
&module_set,
runtime,
mg,
i,
&mut exists_entries,
&mut list,
&imports_map,
);
}
list.push(ConcatenationEntry::Concatenated(
ConcatenationEntryConcatenated {
module: root_module,
},
));
list
},
)
}
#[allow(clippy::too_many_arguments)]
fn enter_module(
&self,
module_set: &IdentifierIndexSet,
runtime: Option<&RuntimeSpec>,
mg: &ModuleGraph,
import: &ConcatenatedImport,
exists_entry: &mut IdentifierMap<MergedConcatenatedImport>,
list: &mut Vec<ConcatenationEntry>,
imports_map: &IdentifierMap<Vec<ConcatenatedImport>>,
) {
let module = import.connection.module_identifier();
if let Some(existing) = exists_entry.get(module)
&& existing.runtime_condition == RuntimeCondition::Boolean(true)
&& existing.non_defer_access == NonDeferAccess(true)
{
return;
}
if module_set.contains(module) {
exists_entry.insert(
*module,
MergedConcatenatedImport {
runtime_condition: RuntimeCondition::Boolean(true),
non_defer_access: NonDeferAccess(true),
},
);
if !matches!(&import.runtime_condition, RuntimeCondition::Boolean(true)) {
panic!(
"Cannot runtime-conditional concatenate a module ({}) in {}. This should not happen.",
module, self.root_module_ctxt.id,
);
}
if !matches!(import.non_defer_access, NonDeferAccess(true)) {
panic!(
"Cannot deferred concatenate a module ({}) in {}. This should not happen.",
module, self.root_module_ctxt.id,
);
}
let imports = imports_map.get(module).expect("should have imports");
for import in imports {
self.enter_module(
module_set,
runtime,
mg,
import,
exists_entry,
list,
imports_map,
);
}
list.push(ConcatenationEntry::Concatenated(
ConcatenationEntryConcatenated { module: *module },
));
} else {
let reduced_runtime_condition;
let reduced_non_defer_access;
if let Some(existing) = exists_entry.get_mut(module) {
reduced_runtime_condition = subtract_runtime_condition(
&import.runtime_condition,
&existing.runtime_condition,
runtime,
);
reduced_non_defer_access =
subtract_non_defer_access(import.non_defer_access, existing.non_defer_access);
if matches!(reduced_runtime_condition, RuntimeCondition::Boolean(false))
&& matches!(reduced_non_defer_access, NonDeferAccess(false))
{
return;
}
if !matches!(reduced_runtime_condition, RuntimeCondition::Boolean(false)) {
existing.runtime_condition = merge_runtime_condition_non_false(
&existing.runtime_condition,
&reduced_runtime_condition,
runtime,
);
}
if !matches!(reduced_non_defer_access, NonDeferAccess(false)) {
existing.non_defer_access =
merge_non_defer_access(existing.non_defer_access, reduced_non_defer_access);
}
} else {
reduced_runtime_condition = import.runtime_condition.clone();
reduced_non_defer_access = import.non_defer_access;
exists_entry.insert(
*module,
MergedConcatenatedImport {
runtime_condition: reduced_runtime_condition.clone(),
non_defer_access: reduced_non_defer_access,
},
);
};
if let Some(ConcatenationEntry::External(last)) = list.last_mut()
&& last.module(mg) == *module
{
last.runtime_condition =
merge_runtime_condition(&last.runtime_condition, &reduced_runtime_condition, runtime);
last.non_defer_access =
merge_non_defer_access(last.non_defer_access, reduced_non_defer_access);
return;
}
list.push(ConcatenationEntry::External(ConcatenationEntryExternal {
dependency: import.connection.dependency_id,
runtime_condition: reduced_runtime_condition,
non_defer_access: reduced_non_defer_access,
}));
}
}
fn get_concatenated_imports(
&self,
module_id: &ModuleIdentifier,
root_module_id: &ModuleIdentifier,
runtime: Option<&RuntimeSpec>,
mg: &ModuleGraph,
artifacts: &ConcatenationArtifacts,
) -> Vec<ConcatenatedImport> {
#[derive(Debug)]
struct ConcatenatedModuleImportInfo<'a> {
connection: &'a ModuleGraphConnection,
source_order: i32,
range_start: Option<u32>,
defer: bool,
}
let mut connections: Vec<&ModuleGraphConnection> =
mg.get_ordered_outgoing_connections(module_id).collect();
if module_id == root_module_id {
for c in mg.get_outgoing_connections(&self.id) {
connections.push(c);
}
}
let mut references = connections
.into_iter()
.filter_map(|connection| {
let dep = mg.dependency_by_id(&connection.dependency_id);
if !is_esm_dep_like(dep) {
return None;
}
let ref_module = mg
.module_by_identifier(connection.module_identifier())
.expect("should have module");
if ref_module
.source_types(mg)
.iter()
.all(|source_type| source_type == &SourceType::Css)
{
return None;
}
if !(connection.resolved_original_module_identifier == Some(*module_id)
&& connection.is_target_active(
mg,
self.runtime.as_ref(),
artifacts.mg_cache,
artifacts.side_effects_state_artifact,
artifacts.exports_info_artifact,
))
{
return None;
}
Some(ConcatenatedModuleImportInfo {
connection,
source_order: dep
.source_order()
.expect("source order should not be empty"),
range_start: dep.range().map(|range| range.start),
defer: dep.get_phase().is_defer(),
})
})
.collect::<Vec<_>>();
references.sort_by(|a, b| {
if a.source_order != b.source_order {
return a.source_order.cmp(&b.source_order);
}
match (a.range_start, b.range_start) {
(None, None) => std::cmp::Ordering::Equal,
(None, Some(_)) => std::cmp::Ordering::Greater,
(Some(_), None) => std::cmp::Ordering::Less,
(Some(a), Some(b)) => a.cmp(&b),
}
});
let mut references_map: IdentifierIndexMap<ConcatenatedImport> = IdentifierIndexMap::default();
for reference in references {
let runtime_condition = filter_runtime(runtime, |r| {
reference.connection.is_target_active(
mg,
r,
artifacts.mg_cache,
artifacts.side_effects_state_artifact,
artifacts.exports_info_artifact,
)
});
if matches!(runtime_condition, RuntimeCondition::Boolean(false)) {
continue;
}
let module: &Identifier = reference.connection.module_identifier();
let non_defer_access = NonDeferAccess(!reference.defer);
match references_map.entry(*module) {
indexmap::map::Entry::Occupied(mut occ) => {
let entry = occ.get();
let merged_runtime_condition = merge_runtime_condition_non_false(
&entry.runtime_condition,
&runtime_condition,
runtime,
);
let merged_non_defer_access =
merge_non_defer_access(entry.non_defer_access, non_defer_access);
let occ = occ.get_mut();
occ.runtime_condition = merged_runtime_condition;
occ.non_defer_access = merged_non_defer_access;
}
indexmap::map::Entry::Vacant(vac) => {
vac.insert(ConcatenatedImport {
connection: Arc::new(reference.connection.clone()),
runtime_condition,
non_defer_access,
});
}
}
}
references_map.into_values().collect()
}
async fn analyze_module(
&self,
compilation: &Compilation,
info: &ModuleInfo,
runtime: Option<&RuntimeSpec>,
concatenation_scope: Option<ConcatenationScope>,
) -> Result<ModuleInfo> {
if let ModuleInfo::Concatenated(boxed_info) = info {
let info = boxed_info.as_ref();
let module_id = info.module;
let concatenation_scope =
concatenation_scope.expect("should have concatenation scope for concatenated module");
let module_graph = compilation.get_module_graph();
let module = module_graph
.module_by_identifier(&module_id)
.unwrap_or_else(|| panic!("should have module {module_id}"));
let mut runtime_template = compilation.runtime_template.create_module_code_template();
let mut code_generation_context = ModuleCodeGenerationContext {
compilation,
runtime,
concatenation_scope: Some(concatenation_scope),
runtime_template: &mut runtime_template,
};
let codegen_res = module.code_generation(&mut code_generation_context).await?;
let CodeGenerationResult {
mut inner,
mut chunk_init_fragments,
mut runtime_requirements,
concatenation_scope,
..
} = codegen_res;
runtime_requirements.extend(*runtime_template.runtime_requirements());
if let Some(fragments) = codegen_res.data.get::<ChunkInitFragments>() {
chunk_init_fragments.extend(fragments.iter().cloned());
}
let concatenation_scope = concatenation_scope.expect("should have concatenation_scope");
let source = inner
.remove(&SourceType::JavaScript)
.expect("should have javascript source");
let source_code = source.source().into_string_lossy();
let comments = SingleThreadedComments::default();
let mut module_info = concatenation_scope.current_module;
let jsx = module
.as_ref()
.as_normal_module()
.and_then(|normal_module| normal_module.get_parser_options())
.and_then(|options: &ParserOptions| {
options
.get_javascript()
.and_then(|js_options| js_options.jsx)
})
.unwrap_or(false);
let mut ast = Ast::new(source_code.len(), StringAllocator::default());
let lexer = swc_experimental_ecma_parser::Lexer::new(
Syntax::Es(EsSyntax {
jsx,
..Default::default()
}),
EsVersion::EsNext,
StringSource::new(source_code.as_ref()),
Some(&comments),
ast.string_allocator(),
);
let mut p = Parser::new_from(&mut ast, lexer);
let ret = p.parse_module();
let module = match ret {
Ok(module) => module,
Err(err) => {
return Err(Error::from_string(
Some(source_code.into_owned()),
err.span().real_lo() as usize,
err.span().real_hi() as usize,
"JavaScript parse error:\n".to_string(),
err.kind().msg().to_string(),
));
}
};
let ast = *
let semantic = resolver(module, ast);
let ids = collect_ident(ast, module);
module_info.module_ctxt = semantic.top_level_scope_id().to_ctxt();
module_info.global_ctxt = semantic.unresolved_scope_id().to_ctxt();
let top_level_scope_id = semantic.top_level_scope_id();
let mut all_used_names = HashSet::default();
all_used_names.reserve(ids.len());
module_info.idents.reserve(ids.len());
module_info.global_scope_ident.reserve(ids.len());
let mut binding_to_ref: FxIndexMap<(Atom, SyntaxContext), Vec<ConcatenatedModuleIdent>> =
FxIndexMap::default();
binding_to_ref.reserve(ids.len());
for ident in ids {
let scope = semantic.node_scope(ident.id);
let is_global = scope.to_ctxt() == module_info.global_ctxt;
let legacy = if is_global {
let leg = ident.to_legacy(ast, &semantic);
module_info.global_scope_ident.push(leg.clone());
all_used_names.insert(leg.id.sym.clone());
Some(leg)
} else {
None
};
if ident.is_class_expr_with_ident {
all_used_names.insert(ast.get_atom(ident.id.sym(ast)));
continue;
}
if scope != top_level_scope_id {
all_used_names.insert(ast.get_atom(ident.id.sym(ast)));
}
let legacy = legacy.unwrap_or_else(|| ident.to_legacy(ast, &semantic));
module_info.idents.push(legacy.clone());
binding_to_ref
.entry((legacy.id.sym.clone(), legacy.id.ctxt))
.or_default()
.push(legacy);
}
module_info.all_used_names = all_used_names;
module_info.binding_to_ref = binding_to_ref;
let result_source = ReplaceSource::new(source.clone());
module_info.has_ast = true;
module_info.runtime_requirements = runtime_requirements;
module_info.internal_source = Some(source);
module_info.source = Some(result_source);
module_info.chunk_init_fragments = chunk_init_fragments;
if let Some(CodeGenerationPublicPathAutoReplace(true)) = codegen_res
.data
.get::<CodeGenerationPublicPathAutoReplace>(
) {
module_info.public_path_auto_replacement = Some(true);
}
if codegen_res.data.contains::<URLStaticMode>() {
module_info.static_url_replacement = true;
}
Ok(ModuleInfo::Concatenated(Box::new(module_info)))
} else {
Ok(info.clone())
}
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::fn_params_excessive_bools)]
fn get_final_name(
module_graph: &ModuleGraph,
module_graph_cache: &ModuleGraphCacheArtifact,
exports_info_artifact: &ExportsInfoArtifact,
module_static_cache: &ModuleStaticCache,
info: &ModuleIdentifier,
export_name: Vec<Atom>,
module_to_info_map: &IdentifierIndexMap<ModuleInfo>,
runtime: Option<&RuntimeSpec>,
dep_deferred: bool,
as_call: bool,
call_context: bool,
strict_esm_module: bool,
asi_safe: Option<bool>,
context: &Context,
) -> FinalNameResult {
let final_binding_result = Self::get_final_binding(
module_graph,
module_graph_cache,
exports_info_artifact,
info,
export_name,
module_to_info_map,
runtime,
as_call,
dep_deferred,
strict_esm_module,
asi_safe,
&mut HashSet::default(),
);
let binding_info_id = final_binding_result.get_info_id();
let FinalBindingResult {
binding,
interop_namespace_object_used,
interop_namespace_object2_used,
interop_default_access_used,
deferred_namespace_object_used,
needed_namespace_object,
} = final_binding_result;
let (ids, comment) = match binding {
Binding::Raw(ref b) => (&b.ids, b.comment.as_ref()),
Binding::Symbol(ref b) => (&b.ids, b.comment.as_ref()),
};
let (reference, is_property_access) = match binding {
Binding::Raw(ref b) => {
let reference = format!(
"{}{}{}",
b.raw_name,
comment.cloned().unwrap_or_default(),
property_access(ids, 0)
);
let is_property_access = !ids.is_empty();
(reference, is_property_access)
}
Binding::Symbol(ref binding) => {
let export_id = &binding.name;
let info = module_to_info_map
.get(&binding.info_id)
.and_then(|info| info.try_as_concatenated())
.expect("should have concatenate module info");
let name = info.internal_names.get(export_id).unwrap_or_else(|| {
panic!(
"The export \"{}\" in \"{}\" has no internal name (existing names: {})",
export_id,
get_cached_readable_identifier(
&info.module,
module_graph,
module_static_cache,
context
),
info
.internal_names
.iter()
.map(|(name, symbol)| format!("{name}: {symbol}"))
.collect::<Vec<String>>()
.join(", ")
)
});
let reference = format!(
"{}{}{}",
name,
comment.cloned().unwrap_or_default(),
property_access(ids, 0)
);
let is_property_access = ids.len() > 1;
(reference, is_property_access)
}
};
if is_property_access && as_call && !call_context {
let res = if asi_safe.unwrap_or_default() {
format!("(0,{reference})")
} else if let Some(_asi_safe) = asi_safe {
format!(";(0,{reference})")
} else {
format!("/*#__PURE__*/Object({reference})")
};
return FinalNameResult {
name: res,
info_id: binding_info_id,
interop_namespace_object_used,
interop_namespace_object2_used,
interop_default_access_used,
deferred_namespace_object_used,
needed_namespace_object,
};
}
FinalNameResult {
name: reference,
info_id: binding_info_id,
interop_namespace_object_used,
interop_namespace_object2_used,
interop_default_access_used,
deferred_namespace_object_used,
needed_namespace_object,
}
}
#[allow(clippy::too_many_arguments)]
fn get_final_binding(
mg: &ModuleGraph,
mg_cache: &ModuleGraphCacheArtifact,
exports_info_artifact: &ExportsInfoArtifact,
info_id: &ModuleIdentifier,
mut export_name: Vec<Atom>,
module_to_info_map: &IdentifierIndexMap<ModuleInfo>,
runtime: Option<&RuntimeSpec>,
as_call: bool,
dep_deferred: bool,
strict_esm_module: bool,
asi_safe: Option<bool>,
already_visited: &mut HashSet<ExportInfoHashKey>,
) -> FinalBindingResult {
let info = module_to_info_map
.get(info_id)
.expect("should have module info");
let module = mg
.module_by_identifier(&info.id())
.expect("should have module");
let exports_type =
module.get_exports_type(mg, mg_cache, exports_info_artifact, strict_esm_module);
let is_module_deferred = matches!(info, ModuleInfo::External(info) if info.deferred)
&& !module.build_meta().has_top_level_await;
let is_deferred = dep_deferred && is_module_deferred;
if export_name.is_empty() {
match exports_type {
ExportsType::DefaultOnly => {
let (raw_name, interop_namespace_object2_used, deferred_namespace_object_used) =
if is_deferred {
(info.get_deferred_namespace_object_name(), None, Some(true))
} else {
(info.get_interop_namespace_object2_name(), Some(true), None)
};
return FinalBindingResult {
binding: Binding::Raw(RawBinding {
info_id: *info_id,
raw_name: raw_name.cloned().expect("should have raw name"),
ids: export_name.clone(),
export_name,
comment: None,
}),
interop_namespace_object2_used,
interop_namespace_object_used: None,
interop_default_access_used: None,
deferred_namespace_object_used,
needed_namespace_object: None,
};
}
ExportsType::DefaultWithNamed => {
let (raw_name, interop_namespace_object_used, deferred_namespace_object_used) =
if is_deferred {
(info.get_deferred_namespace_object_name(), None, Some(true))
} else {
(info.get_interop_namespace_object_name(), Some(true), None)
};
return FinalBindingResult {
binding: Binding::Raw(RawBinding {
info_id: *info_id,
raw_name: raw_name.cloned().expect("should have raw name"),
ids: export_name.clone(),
export_name,
comment: None,
}),
interop_namespace_object_used,
interop_namespace_object2_used: None,
interop_default_access_used: None,
deferred_namespace_object_used,
needed_namespace_object: None,
};
}
_ => {}
}
} else {
match exports_type {
ExportsType::Namespace => {}
ExportsType::DefaultWithNamed => match export_name.first().map(|atom| atom.as_str()) {
Some("default") => {
export_name = export_name[1..].to_vec();
}
Some("__esModule") => {
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
info_id: *info_id,
raw_name: "/* __esModule */true".into(),
ids: export_name[1..].to_vec(),
export_name,
comment: None,
}));
}
_ => {}
},
ExportsType::DefaultOnly => {
if export_name.first().map(|item| item.as_str()) == Some("__esModule") {
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
info_id: info.id(),
raw_name: "/* __esModule */true".into(),
ids: export_name[1..].to_vec(),
export_name,
comment: None,
}));
}
let first_export_id = export_name.remove(0);
if first_export_id != "default" {
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: "/* non-default import from default-exporting module */undefined".into(),
ids: export_name.clone(),
export_name,
info_id: *info_id,
comment: None,
}));
}
}
ExportsType::Dynamic => match export_name.first().map(|atom| atom.as_str()) {
Some("default") => {
export_name = export_name[1..].to_vec();
if is_deferred {
let deferred_name = info
.as_external()
.deferred_name
.as_ref()
.expect("should have deferred_name");
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: format!("{deferred_name}.a").into(),
ids: export_name.clone(),
export_name,
info_id: *info_id,
comment: None,
}));
}
if is_module_deferred {
let name = info.as_external().name.as_ref().expect("should have name");
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: name.clone(),
ids: export_name.clone(),
export_name,
info_id: *info_id,
comment: None,
}));
}
let default_access_name = info
.get_interop_default_access_name()
.cloned()
.expect("should have default access name");
let default_export = if as_call {
format!("{default_access_name}()")
} else if let Some(true) = asi_safe {
format!("({default_access_name}())")
} else if let Some(false) = asi_safe {
format!(";({default_access_name}())")
} else {
format!("{default_access_name}.a")
};
return FinalBindingResult {
binding: Binding::Raw(RawBinding {
raw_name: default_export.into(),
ids: export_name.clone(),
export_name,
info_id: *info_id,
comment: None,
}),
interop_default_access_used: Some(true),
interop_namespace_object_used: None,
interop_namespace_object2_used: None,
deferred_namespace_object_used: None,
needed_namespace_object: None,
};
}
Some("__esModule") => {
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: "/* __esModule */true".into(),
ids: export_name[1..].to_vec(),
export_name,
info_id: *info_id,
comment: None,
}));
}
_ => {}
},
}
}
if export_name.is_empty() {
match info {
ModuleInfo::Concatenated(info) => {
return FinalBindingResult {
binding: Binding::Raw(RawBinding {
raw_name: info
.namespace_object_name
.clone()
.expect("should have namespace_object_name"),
ids: export_name.clone(),
export_name,
info_id: info.module,
comment: None,
}),
needed_namespace_object: Some(info.module),
interop_namespace_object_used: None,
interop_namespace_object2_used: None,
interop_default_access_used: None,
deferred_namespace_object_used: None,
};
}
ModuleInfo::External(info) => {
if is_deferred {
return FinalBindingResult {
binding: Binding::Raw(RawBinding {
raw_name: info
.deferred_namespace_object_name
.clone()
.expect("should have deferred_namespace_object_name"),
ids: export_name.clone(),
export_name,
info_id: info.module,
comment: None,
}),
interop_namespace_object_used: None,
interop_namespace_object2_used: None,
interop_default_access_used: None,
deferred_namespace_object_used: Some(true),
needed_namespace_object: None,
};
}
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: info.name.clone().expect("should have raw name"),
ids: export_name.clone(),
export_name,
info_id: info.module,
comment: None,
}));
}
}
}
let exports_info = exports_info_artifact
.get_prefetched_exports_info(&info.id(), PrefetchExportsInfoMode::Nested(&export_name));
let export_info = exports_info.get_export_info_without_mut_module_graph(&export_name[0]);
let export_info_hash_key = export_info.as_hash_key();
if already_visited.contains(&export_info_hash_key) {
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: "/* circular reexport */ Object(function x() { x() }())".into(),
ids: Vec::new(),
export_name,
info_id: info.id(),
comment: None,
}));
}
already_visited.insert(export_info_hash_key);
match info {
ModuleInfo::Concatenated(info) => {
let export_id = export_name.first().cloned();
if matches!(
export_info.provided(),
Some(crate::ExportProvided::NotProvided)
) {
return FinalBindingResult {
binding: Binding::Raw(RawBinding {
raw_name: info
.namespace_object_name
.clone()
.expect("should have namespace_object_name"),
ids: export_name.clone(),
export_name,
info_id: info.module,
comment: None,
}),
needed_namespace_object: Some(info.module),
interop_namespace_object_used: None,
interop_namespace_object2_used: None,
interop_default_access_used: None,
deferred_namespace_object_used: None,
};
}
if let Some(ref export_id) = export_id
&& let Some(direct_export) = info.export_map.as_ref().and_then(|map| map.get(export_id))
{
if let Some(used_name) = ExportsInfoGetter::get_used_name(
GetUsedNameParam::WithNames(&exports_info),
runtime,
&export_name,
) {
match used_name {
UsedName::Normal(used_name) => {
return FinalBindingResult::from_binding(Binding::Symbol(SymbolBinding {
info_id: info.module,
name: direct_export.as_str().into(),
ids: used_name[1..].to_vec(),
export_name,
comment: None,
}));
}
UsedName::Inlined(inlined) => {
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: inlined
.inlined_value()
.render(&to_normal_comment(&format!(
"inlined export {}",
property_access(&export_name, 0)
)))
.into(),
ids: inlined.suffix_ids().to_vec(),
export_name,
info_id: info.module,
comment: None,
}));
}
}
} else {
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: "/* unused export */ undefined".into(),
ids: export_name[1..].to_vec(),
export_name,
info_id: info.module,
comment: None,
}));
}
}
if let Some(ref export_id) = export_id
&& let Some(raw_export) = info
.raw_export_map
.as_ref()
.and_then(|map| map.get(export_id))
{
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
info_id: info.module,
raw_name: raw_export.as_str().into(),
ids: export_name[1..].to_vec(),
export_name,
comment: None,
}));
}
let reexport = find_target(
&export_info,
mg,
exports_info_artifact,
Arc::new(|module: &ModuleIdentifier| module_to_info_map.contains_key(module)),
&mut Default::default(),
);
match reexport {
crate::FindTargetResult::NoTarget => {}
crate::FindTargetResult::InvalidTarget(target) => {
if let Some(export) = target.export {
let exports_info = exports_info_artifact.get_prefetched_exports_info(
&target.module,
PrefetchExportsInfoMode::Nested(&export),
);
if let Some(UsedName::Inlined(inlined)) = ExportsInfoGetter::get_used_name(
GetUsedNameParam::WithNames(&exports_info),
runtime,
&export,
) {
return FinalBindingResult::from_binding(Binding::Raw(RawBinding {
raw_name: inlined
.inlined_value()
.render(&to_normal_comment(&format!(
"inlined export {}",
property_access(&export_name, 0)
)))
.into(),
ids: inlined.suffix_ids().to_vec(),
export_name,
info_id: info.module,
comment: None,
}));
}
}
panic!(
"Target module of reexport is not part of the concatenation (export '{:?}')",
&export_id
);
}
crate::FindTargetResult::ValidTarget(reexport) => {
if let Some(ref_info) = module_to_info_map.get(&reexport.module) {
return Self::get_final_binding(
mg,
mg_cache,
exports_info_artifact,
&ref_info.id(),
if let Some(reexport_export) = reexport.export {
[reexport_export, export_name[1..].to_vec()].concat()
} else {
export_name[1..].to_vec()
},
module_to_info_map,
runtime,
as_call,
reexport.defer,
module.build_meta().strict_esm_module,
asi_safe,
already_visited,
);
}
}
}
if info.namespace_export_symbol.is_some() {
let used_name = ExportsInfoGetter::get_used_name(
GetUsedNameParam::WithNames(&exports_info),
runtime,
&export_name,
)
.expect("should have export name");
return FinalBindingResult::from_binding(match used_name {
UsedName::Normal(used_name) => Binding::Raw(RawBinding {
info_id: info.module,
raw_name: info
.namespace_object_name
.as_ref()
.expect("should have raw name")
.as_str()
.into(),
ids: used_name,
export_name,
comment: None,
}),
UsedName::Inlined(inlined) => Binding::Raw(RawBinding {
info_id: info.module,
raw_name: inlined
.inlined_value()
.render(&to_normal_comment(&format!(
"inlined export {}",
property_access(&export_name, 0)
)))
.into(),
ids: inlined.suffix_ids().to_vec(),
export_name,
comment: None,
}),
});
}
panic!(
"Cannot get final name for export '{}' of module '{}'",
join_atom(export_name.iter(), "."),
module.identifier()
);
}
ModuleInfo::External(info) => {
let binding = if let Some(used_name) = ExportsInfoGetter::get_used_name(
GetUsedNameParam::WithNames(&exports_info),
runtime,
&export_name,
) {
match used_name {
UsedName::Normal(used_name) => {
let comment = if used_name == export_name {
String::new()
} else {
to_normal_comment(&property_access(&export_name, 0))
};
Binding::Raw(RawBinding {
raw_name: format!(
"{}{}{}",
if is_deferred {
info.deferred_name.as_ref()
} else {
info.name.as_ref()
}
.expect("should have name"),
if is_deferred { ".a" } else { "" },
comment
)
.into(),
ids: used_name,
export_name,
info_id: info.module,
comment: None,
})
}
UsedName::Inlined(inlined) => {
assert!(
!is_deferred,
"inlined export is not possible for deferred external module"
);
Binding::Raw(RawBinding {
raw_name: inlined
.inlined_value()
.render(&to_normal_comment(&format!(
"inlined export {}",
property_access(&export_name, 0)
)))
.into(),
ids: inlined.suffix_ids().to_vec(),
export_name,
info_id: info.module,
comment: None,
})
}
}
} else {
Binding::Raw(RawBinding {
raw_name: "/* unused export */ undefined".into(),
ids: export_name[1..].to_vec(),
export_name,
info_id: info.module,
comment: None,
})
};
FinalBindingResult::from_binding(binding)
}
}
}
}
pub fn is_esm_dep_like(dep: &BoxDependency) -> bool {
matches!(
dep.dependency_type(),
DependencyType::EsmImportSpecifier
| DependencyType::EsmExportImportedSpecifier
| DependencyType::EsmImport
| DependencyType::EsmExportImport
)
}
pub fn find_new_name(old_name: &str, used_names: &HashSet<Atom>, extra_info: &[Atom]) -> Atom {
let mut name = old_name.to_string();
for info_part in extra_info {
let info_str = info_part.as_ref();
let mut new_name = String::with_capacity(info_str.len() + 1 + name.len());
new_name.push_str(info_str);
if !name.is_empty() {
if name.starts_with('_') || info_str.ends_with('_') {
new_name.push_str(&name);
} else {
new_name.push('_');
new_name.push_str(&name);
}
}
name = new_name;
let candidate: Atom = to_identifier_with_escaped(name.clone()).into();
if !used_names.contains(&candidate) {
return candidate;
}
}
let base: Atom = to_identifier_with_escaped(name).into();
if !base.is_empty() && !used_names.contains(&base) {
return base;
}
let mut i = 0;
let mut i_buffer = itoa::Buffer::new();
let mut base_with_underscore = String::with_capacity(base.len() + 1);
base_with_underscore.push_str(base.as_ref());
base_with_underscore.push('_');
let mut numbered = String::with_capacity(base_with_underscore.len() + 8);
loop {
numbered.clear();
numbered.push_str(&base_with_underscore);
numbered.push_str(i_buffer.format(i));
let candidate: Atom = Atom::from(numbered.as_str());
if !used_names.contains(&candidate) {
return candidate;
}
i += 1;
}
}
#[derive(Debug)]
struct FinalBindingResult {
binding: Binding,
interop_namespace_object_used: Option<bool>,
interop_namespace_object2_used: Option<bool>,
interop_default_access_used: Option<bool>,
deferred_namespace_object_used: Option<bool>,
needed_namespace_object: Option<ModuleIdentifier>,
}
impl FinalBindingResult {
pub fn from_binding(binding: Binding) -> Self {
Self {
binding,
interop_namespace_object_used: None,
interop_namespace_object2_used: None,
interop_default_access_used: None,
deferred_namespace_object_used: None,
needed_namespace_object: None,
}
}
pub fn get_info_id(&self) -> Identifier {
match &self.binding {
Binding::Raw(raw) => raw.info_id,
Binding::Symbol(symbol) => symbol.info_id,
}
}
}
#[derive(Debug)]
struct FinalNameResult {
name: String,
info_id: Identifier,
interop_namespace_object_used: Option<bool>,
interop_namespace_object2_used: Option<bool>,
interop_default_access_used: Option<bool>,
deferred_namespace_object_used: Option<bool>,
needed_namespace_object: Option<ModuleIdentifier>,
}
impl FinalNameResult {
fn apply_to_info(
&self,
module_to_info_map: &mut IdentifierIndexMap<ModuleInfo>,
needed_namespace_objects: &mut IdentifierIndexSet,
needed_namespace_objects_queue: &mut VecDeque<ModuleIdentifier>,
) {
let info = module_to_info_map
.get_mut(&self.info_id)
.expect("should have concatenate module info");
if let Some(value) = self.interop_namespace_object_used {
info.set_interop_namespace_object_used(value);
}
if let Some(value) = self.interop_namespace_object2_used {
info.set_interop_namespace_object2_used(value);
}
if let Some(value) = self.interop_default_access_used {
info.set_interop_default_access_used(value);
}
if let Some(value) = self.deferred_namespace_object_used {
info.set_deferred_namespace_object_used(value);
}
if let Some(value) = self.needed_namespace_object
&& needed_namespace_objects.insert(value)
{
needed_namespace_objects_queue.push_back(value);
}
}
}
pub fn get_cached_readable_identifier(
mid: &ModuleIdentifier,
mg: &ModuleGraph,
module_static_cache: &ModuleStaticCache,
compilation_context: &Context,
) -> String {
module_static_cache.cached_readable_identifier((*mid, None), || {
let module = mg.module_by_identifier(mid).expect("should have module");
module.readable_identifier(compilation_context).to_string()
})
}
pub fn split_readable_identifier(extra_info: &str) -> Vec<Atom> {
let extra_info = REGEX.replace_all(extra_info, "");
let mut splitted_info: Vec<Atom> = extra_info
.split('/')
.map(|s| match escape_identifier(s) {
Cow::Borrowed(s) => Atom::from(s),
Cow::Owned(s) => Atom::from(s),
})
.collect();
splitted_info.reverse();
splitted_info
}
fn escaped_name(name: &str) -> Cow<'_, str> {
if name == DEFAULT_EXPORT {
return Cow::Borrowed("");
}
if name == NAMESPACE_OBJECT_EXPORT {
return Cow::Borrowed("namespaceObject");
}
escape_identifier(name)
}
pub fn escape_name(name: &str) -> String {
escaped_name(name).into_owned()
}
pub fn escape_name_atom(name: &str) -> Atom {
match escaped_name(name) {
Cow::Borrowed(name) => Atom::from(name),
Cow::Owned(name) => Atom::from(name),
}
}
pub fn escape_name_atom_ref(name: &Atom) -> Atom {
if name == DEFAULT_EXPORT {
return Atom::default();
}
if name == NAMESPACE_OBJECT_EXPORT {
return Atom::from("namespaceObject");
}
match escape_identifier(name.as_str()) {
Cow::Borrowed(_) => name.clone(),
Cow::Owned(name) => Atom::from(name),
}
}
#[derive(Clone, Debug)]
pub struct NewConcatenatedModuleIdent {
pub id: Ident,
pub shorthand: bool,
pub is_class_expr_with_ident: bool,
}
impl NewConcatenatedModuleIdent {
pub fn to_legacy(&self, ast: &Ast, semantic: &Semantic) -> ConcatenatedModuleIdent {
let span = self.id.span(ast);
let sym = ast.get_atom(self.id.sym(ast));
let ctxt = semantic.node_scope(self.id).to_ctxt();
ConcatenatedModuleIdent {
id: swc_ecma_ast::Ident::new(sym, span, ctxt),
is_class_expr_with_ident: self.is_class_expr_with_ident,
shorthand: self.shorthand,
}
}
}
pub fn collect_ident(
ast: &Ast,
root: swc_experimental_ecma_ast::Module,
) -> Vec<NewConcatenatedModuleIdent> {
struct IdentCollector<'a> {
ast: &'a Ast,
ids: Vec<NewConcatenatedModuleIdent>,
}
impl Visit for IdentCollector<'_> {
fn ast(&self) -> &Ast {
self.ast
}
fn visit_ident(&mut self, node: Ident) {
self.ids.push(NewConcatenatedModuleIdent {
id: node,
shorthand: false,
is_class_expr_with_ident: false,
});
}
fn visit_object_pat_prop(&mut self, n: ObjectPatProp) {
match n {
ObjectPatProp::Assign(assign) => {
self.ids.push(NewConcatenatedModuleIdent {
id: assign.key(self.ast).id(self.ast),
shorthand: true,
is_class_expr_with_ident: false,
});
assign.value(self.ast).visit_with(self);
}
ObjectPatProp::KeyValue(_) | ObjectPatProp::Rest(_) => {
n.visit_children_with(self);
}
}
}
fn visit_prop(&mut self, node: Prop) {
match node {
Prop::Shorthand(node) => {
self.ids.push(NewConcatenatedModuleIdent {
id: node,
shorthand: true,
is_class_expr_with_ident: false,
});
}
_ => {
node.visit_children_with(self);
}
}
}
fn visit_class_expr(&mut self, node: ClassExpr) {
if let Some(ident) = node.ident(self.ast)
&& node.class(self.ast).super_class(self.ast).is_some()
{
self.ids.push(NewConcatenatedModuleIdent {
id: ident,
shorthand: false,
is_class_expr_with_ident: true,
});
}
node.class(self.ast).visit_with(self);
}
}
let mut collector = IdentCollector {
ast,
ids: Vec::new(),
};
collector.visit_module(root);
collector.ids
}