mirror of
https://github.com/swc-project/swc.git
synced 2024-12-24 22:22:34 +03:00
fix(bundler): Use proper algorithm for dependency analysis (#1610)
swc_bundler: - Optimize detection of circular imports.
This commit is contained in:
parent
308792dc90
commit
731dc68c92
@ -9,7 +9,7 @@ include = ["Cargo.toml", "build.rs", "src/**/*.rs", "src/**/*.js"]
|
|||||||
license = "Apache-2.0/MIT"
|
license = "Apache-2.0/MIT"
|
||||||
name = "swc_bundler"
|
name = "swc_bundler"
|
||||||
repository = "https://github.com/swc-project/swc.git"
|
repository = "https://github.com/swc-project/swc.git"
|
||||||
version = "0.32.5"
|
version = "0.32.7"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[features]
|
[features]
|
||||||
|
@ -63,7 +63,7 @@ where
|
|||||||
module.visit_mut_with(&mut DefaultHandler {
|
module.visit_mut_with(&mut DefaultHandler {
|
||||||
local_ctxt: info.local_ctxt(),
|
local_ctxt: info.local_ctxt(),
|
||||||
});
|
});
|
||||||
module.sort(info.id, &ctx.graph, &self.cm);
|
module.sort(info.id, &ctx.graph, &ctx.cycles, &self.cm);
|
||||||
|
|
||||||
let stmt = ModuleItem::Stmt(wrap_module(
|
let stmt = ModuleItem::Stmt(wrap_module(
|
||||||
SyntaxContext::empty(),
|
SyntaxContext::empty(),
|
||||||
|
@ -29,6 +29,7 @@ use EdgeDirection::Outgoing;
|
|||||||
pub(super) struct Ctx {
|
pub(super) struct Ctx {
|
||||||
/// Full dependency graph.
|
/// Full dependency graph.
|
||||||
pub graph: ModuleGraph,
|
pub graph: ModuleGraph,
|
||||||
|
pub cycles: Vec<Vec<ModuleId>>,
|
||||||
pub merged: CHashSet<ModuleId>,
|
pub merged: CHashSet<ModuleId>,
|
||||||
pub transitive_remap: CloneMap<SyntaxContext, SyntaxContext>,
|
pub transitive_remap: CloneMap<SyntaxContext, SyntaxContext>,
|
||||||
pub export_stars_in_wrapped: Lock<FxHashMap<ModuleId, Vec<SyntaxContext>>>,
|
pub export_stars_in_wrapped: Lock<FxHashMap<ModuleId, Vec<SyntaxContext>>>,
|
||||||
@ -405,7 +406,7 @@ where
|
|||||||
|
|
||||||
inline(self.injected_ctxt, entry);
|
inline(self.injected_ctxt, entry);
|
||||||
|
|
||||||
entry.sort(id, &ctx.graph, &self.cm);
|
entry.sort(id, &ctx.graph, &ctx.cycles, &self.cm);
|
||||||
|
|
||||||
// crate::debug::print_hygiene("done", &self.cm, &entry.clone().into());
|
// crate::debug::print_hygiene("done", &self.cm, &entry.clone().into());
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ where
|
|||||||
entries: AHashMap<String, TransformedModule>,
|
entries: AHashMap<String, TransformedModule>,
|
||||||
) -> Result<Vec<Bundle>, Error> {
|
) -> Result<Vec<Bundle>, Error> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let (plan, graph) = self.determine_entries(entries).context("failed to plan")?;
|
let (plan, graph, cycles) = self.determine_entries(entries).context("failed to plan")?;
|
||||||
let dur = Instant::now() - start;
|
let dur = Instant::now() - start;
|
||||||
log::debug!("Dependency analysis took {:?}", dur);
|
log::debug!("Dependency analysis took {:?}", dur);
|
||||||
|
|
||||||
@ -68,6 +68,7 @@ where
|
|||||||
|
|
||||||
let ctx = Ctx {
|
let ctx = Ctx {
|
||||||
graph,
|
graph,
|
||||||
|
cycles,
|
||||||
merged: Default::default(),
|
merged: Default::default(),
|
||||||
transitive_remap: Default::default(),
|
transitive_remap: Default::default(),
|
||||||
export_stars_in_wrapped: Default::default(),
|
export_stars_in_wrapped: Default::default(),
|
||||||
|
@ -10,6 +10,7 @@ struct PlanBuilder {
|
|||||||
tracked: FxHashSet<(ModuleId, ModuleId)>,
|
tracked: FxHashSet<(ModuleId, ModuleId)>,
|
||||||
|
|
||||||
graph: ModuleGraph,
|
graph: ModuleGraph,
|
||||||
|
cycles: Vec<Vec<ModuleId>>,
|
||||||
all: Vec<ModuleId>,
|
all: Vec<ModuleId>,
|
||||||
|
|
||||||
kinds: FxHashMap<ModuleId, BundleKind>,
|
kinds: FxHashMap<ModuleId, BundleKind>,
|
||||||
@ -31,16 +32,7 @@ where
|
|||||||
pub(super) fn determine_entries(
|
pub(super) fn determine_entries(
|
||||||
&self,
|
&self,
|
||||||
entries: AHashMap<String, TransformedModule>,
|
entries: AHashMap<String, TransformedModule>,
|
||||||
) -> Result<(Plan, ModuleGraph), Error> {
|
) -> Result<(Plan, ModuleGraph, Vec<Vec<ModuleId>>), Error> {
|
||||||
let plan = self.calculate_plan(entries)?;
|
|
||||||
|
|
||||||
Ok(plan)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_plan(
|
|
||||||
&self,
|
|
||||||
entries: AHashMap<String, TransformedModule>,
|
|
||||||
) -> Result<(Plan, ModuleGraph), Error> {
|
|
||||||
let mut builder = PlanBuilder::default();
|
let mut builder = PlanBuilder::default();
|
||||||
|
|
||||||
for (name, module) in entries {
|
for (name, module) in entries {
|
||||||
@ -49,7 +41,7 @@ where
|
|||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.add_to_graph(&mut builder, module.id);
|
self.add_to_graph(&mut builder, module.id, &mut vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@ -58,11 +50,31 @@ where
|
|||||||
all: builder.all,
|
all: builder.all,
|
||||||
},
|
},
|
||||||
builder.graph,
|
builder.graph,
|
||||||
|
builder.cycles,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_to_graph(&self, builder: &mut PlanBuilder, module_id: ModuleId) {
|
fn add_to_graph(
|
||||||
if !builder.all.contains(&module_id) {
|
&self,
|
||||||
|
builder: &mut PlanBuilder,
|
||||||
|
module_id: ModuleId,
|
||||||
|
path: &mut Vec<ModuleId>,
|
||||||
|
) {
|
||||||
|
let visited = builder.all.contains(&module_id);
|
||||||
|
let cycle_rpos = if visited {
|
||||||
|
path.iter().rposition(|v| *v == module_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(rpos) = cycle_rpos {
|
||||||
|
let cycle = path[rpos..].to_vec();
|
||||||
|
builder.cycles.push(cycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.push(module_id);
|
||||||
|
|
||||||
|
if !visited {
|
||||||
builder.all.push(module_id);
|
builder.all.push(module_id);
|
||||||
}
|
}
|
||||||
builder.graph.add_node(module_id);
|
builder.graph.add_node(module_id);
|
||||||
@ -84,8 +96,11 @@ where
|
|||||||
|
|
||||||
// Prevent infinite loops.
|
// Prevent infinite loops.
|
||||||
if builder.tracked.insert((module_id, src.module_id)) {
|
if builder.tracked.insert((module_id, src.module_id)) {
|
||||||
self.add_to_graph(builder, src.module_id);
|
self.add_to_graph(builder, src.module_id, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let res = path.pop();
|
||||||
|
debug_assert_eq!(res, Some(module_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,8 @@ use super::stmt::sort_stmts;
|
|||||||
use crate::dep_graph::ModuleGraph;
|
use crate::dep_graph::ModuleGraph;
|
||||||
use crate::modules::Modules;
|
use crate::modules::Modules;
|
||||||
use crate::ModuleId;
|
use crate::ModuleId;
|
||||||
use fxhash::FxBuildHasher;
|
|
||||||
use fxhash::FxHashSet;
|
use fxhash::FxHashSet;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use petgraph::algo::all_simple_paths;
|
|
||||||
use petgraph::EdgeDirection::Outgoing;
|
use petgraph::EdgeDirection::Outgoing;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::iter::from_fn;
|
use std::iter::from_fn;
|
||||||
@ -29,6 +27,7 @@ impl Modules {
|
|||||||
&mut self,
|
&mut self,
|
||||||
entry_id: ModuleId,
|
entry_id: ModuleId,
|
||||||
graph: &ModuleGraph,
|
graph: &ModuleGraph,
|
||||||
|
cycle: &Vec<Vec<ModuleId>>,
|
||||||
cm: &Lrc<SourceMap>,
|
cm: &Lrc<SourceMap>,
|
||||||
) -> Vec<Chunk> {
|
) -> Vec<Chunk> {
|
||||||
let injected_ctxt = self.injected_ctxt;
|
let injected_ctxt = self.injected_ctxt;
|
||||||
@ -51,6 +50,7 @@ impl Modules {
|
|||||||
modules,
|
modules,
|
||||||
entry_id,
|
entry_id,
|
||||||
graph,
|
graph,
|
||||||
|
cycle,
|
||||||
cm,
|
cm,
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -64,6 +64,7 @@ fn toposort_real_modules<'a>(
|
|||||||
mut modules: Vec<(ModuleId, Module)>,
|
mut modules: Vec<(ModuleId, Module)>,
|
||||||
entry: ModuleId,
|
entry: ModuleId,
|
||||||
graph: &'a ModuleGraph,
|
graph: &'a ModuleGraph,
|
||||||
|
cycles: &'a Vec<Vec<ModuleId>>,
|
||||||
cm: &Lrc<SourceMap>,
|
cm: &Lrc<SourceMap>,
|
||||||
) -> Vec<Chunk> {
|
) -> Vec<Chunk> {
|
||||||
let mut queue = modules.iter().map(|v| v.0).collect::<VecDeque<_>>();
|
let mut queue = modules.iter().map(|v| v.0).collect::<VecDeque<_>>();
|
||||||
@ -72,7 +73,7 @@ fn toposort_real_modules<'a>(
|
|||||||
let mut chunks = vec![];
|
let mut chunks = vec![];
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let sorted_ids = toposort_real_module_ids(queue, graph).collect::<Vec<_>>();
|
let sorted_ids = toposort_real_module_ids(queue, graph, &cycles).collect::<Vec<_>>();
|
||||||
let end = Instant::now();
|
let end = Instant::now();
|
||||||
log::debug!("Toposort of module ids took {:?}", end - start);
|
log::debug!("Toposort of module ids took {:?}", end - start);
|
||||||
for ids in sorted_ids {
|
for ids in sorted_ids {
|
||||||
@ -128,50 +129,35 @@ fn toposort_real_modules<'a>(
|
|||||||
chunks
|
chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all modules in a cycle.
|
fn cycles_for(
|
||||||
fn all_modules_in_circle(
|
cycles: &Vec<Vec<ModuleId>>,
|
||||||
id: ModuleId,
|
id: ModuleId,
|
||||||
done: &FxHashSet<ModuleId>,
|
checked: &mut Vec<ModuleId>,
|
||||||
already_in_index: &mut IndexSet<ModuleId, FxBuildHasher>,
|
) -> IndexSet<ModuleId> {
|
||||||
graph: &ModuleGraph,
|
checked.push(id);
|
||||||
) -> IndexSet<ModuleId, FxBuildHasher> {
|
let mut v = cycles
|
||||||
let deps = graph
|
|
||||||
.neighbors_directed(id, Outgoing)
|
|
||||||
.filter(|dep| !done.contains(&dep) && !already_in_index.contains(dep))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut ids = deps
|
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.filter(|v| v.contains(&id))
|
||||||
.flat_map(|dep| {
|
|
||||||
let mut paths =
|
|
||||||
all_simple_paths::<Vec<_>, _>(&graph, dep, id, 0, None).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for path in paths.iter_mut() {
|
|
||||||
path.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
paths
|
|
||||||
})
|
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter(|module_id| !done.contains(&module_id) && !already_in_index.contains(module_id))
|
.copied()
|
||||||
.collect::<IndexSet<ModuleId, FxBuildHasher>>();
|
.collect::<IndexSet<_>>();
|
||||||
|
|
||||||
already_in_index.extend(ids.iter().copied());
|
let ids = v.clone();
|
||||||
let mut new_ids = IndexSet::<_, FxBuildHasher>::default();
|
|
||||||
|
|
||||||
for &dep_id in &ids {
|
for added in ids {
|
||||||
let others = all_modules_in_circle(dep_id, done, already_in_index, graph);
|
if checked.contains(&added) {
|
||||||
new_ids.extend(others)
|
continue;
|
||||||
|
}
|
||||||
|
v.extend(cycles_for(cycles, added, checked));
|
||||||
}
|
}
|
||||||
ids.extend(new_ids);
|
|
||||||
|
|
||||||
ids
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toposort_real_module_ids<'a>(
|
fn toposort_real_module_ids<'a>(
|
||||||
mut queue: VecDeque<ModuleId>,
|
mut queue: VecDeque<ModuleId>,
|
||||||
graph: &'a ModuleGraph,
|
graph: &'a ModuleGraph,
|
||||||
|
cycles: &'a Vec<Vec<ModuleId>>,
|
||||||
) -> impl 'a + Iterator<Item = Vec<ModuleId>> {
|
) -> impl 'a + Iterator<Item = Vec<ModuleId>> {
|
||||||
let mut done = FxHashSet::<ModuleId>::default();
|
let mut done = FxHashSet::<ModuleId>::default();
|
||||||
|
|
||||||
@ -197,8 +183,7 @@ fn toposort_real_module_ids<'a>(
|
|||||||
|
|
||||||
// dbg!(&deps);
|
// dbg!(&deps);
|
||||||
|
|
||||||
let mut all_modules_in_circle =
|
let mut all_modules_in_circle = cycles_for(cycles, id, &mut Default::default());
|
||||||
all_modules_in_circle(id, &done, &mut Default::default(), graph);
|
|
||||||
all_modules_in_circle.reverse();
|
all_modules_in_circle.reverse();
|
||||||
|
|
||||||
if all_modules_in_circle.is_empty() {
|
if all_modules_in_circle.is_empty() {
|
||||||
|
@ -19,12 +19,18 @@ impl Modules {
|
|||||||
/// dependency between statements.
|
/// dependency between statements.
|
||||||
///
|
///
|
||||||
/// TODO: Change this to return [Module].
|
/// TODO: Change this to return [Module].
|
||||||
pub fn sort(&mut self, entry_id: ModuleId, module_graph: &ModuleGraph, cm: &Lrc<SourceMap>) {
|
pub fn sort(
|
||||||
|
&mut self,
|
||||||
|
entry_id: ModuleId,
|
||||||
|
module_graph: &ModuleGraph,
|
||||||
|
cycles: &Vec<Vec<ModuleId>>,
|
||||||
|
cm: &Lrc<SourceMap>,
|
||||||
|
) {
|
||||||
log::debug!("Sorting {:?}", entry_id);
|
log::debug!("Sorting {:?}", entry_id);
|
||||||
|
|
||||||
let injected_ctxt = self.injected_ctxt;
|
let injected_ctxt = self.injected_ctxt;
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let chunks = self.take_chunks(entry_id, module_graph, cm);
|
let chunks = self.take_chunks(entry_id, module_graph, cycles, cm);
|
||||||
let dur = Instant::now() - start;
|
let dur = Instant::now() - start;
|
||||||
log::debug!("Sorting took {:?}", dur);
|
log::debug!("Sorting took {:?}", dur);
|
||||||
|
|
||||||
|
@ -1837,9 +1837,9 @@ function hasInvalidTimeData(obj) {
|
|||||||
} else return false;
|
} else return false;
|
||||||
}
|
}
|
||||||
const INVALID = "Invalid DateTime";
|
const INVALID = "Invalid DateTime";
|
||||||
|
const INVALID1 = "Invalid Duration";
|
||||||
let intlDTCache = {
|
let intlDTCache = {
|
||||||
};
|
};
|
||||||
const INVALID1 = "Invalid Duration";
|
|
||||||
let now = ()=>Date.now()
|
let now = ()=>Date.now()
|
||||||
, defaultZone = null, defaultLocale = null, defaultNumberingSystem = null, defaultOutputCalendar = null, throwOnInvalid = false;
|
, defaultZone = null, defaultLocale = null, defaultNumberingSystem = null, defaultOutputCalendar = null, throwOnInvalid = false;
|
||||||
const INVALID2 = "Invalid Interval";
|
const INVALID2 = "Invalid Interval";
|
||||||
@ -2575,6 +2575,17 @@ function diffRelative(start, end, opts) {
|
|||||||
}
|
}
|
||||||
return format(0, opts.units[opts.units.length - 1]);
|
return format(0, opts.units[opts.units.length - 1]);
|
||||||
}
|
}
|
||||||
|
function friendlyDuration(durationish) {
|
||||||
|
if (isNumber(durationish)) {
|
||||||
|
return Duration.fromMillis(durationish);
|
||||||
|
} else if (Duration.isDuration(durationish)) {
|
||||||
|
return durationish;
|
||||||
|
} else if (typeof durationish === "object") {
|
||||||
|
return Duration.fromObject(durationish);
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentError(`Unknown duration argument ${durationish} of type ${typeof durationish}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
class DateTime {
|
class DateTime {
|
||||||
constructor(config1){
|
constructor(config1){
|
||||||
const zone1 = config1.zone || Settings.defaultZone;
|
const zone1 = config1.zone || Settings.defaultZone;
|
||||||
@ -3709,17 +3720,6 @@ class Locale {
|
|||||||
return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;
|
return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function friendlyDuration(durationish) {
|
|
||||||
if (isNumber(durationish)) {
|
|
||||||
return Duration.fromMillis(durationish);
|
|
||||||
} else if (Duration.isDuration(durationish)) {
|
|
||||||
return durationish;
|
|
||||||
} else if (typeof durationish === "object") {
|
|
||||||
return Duration.fromObject(durationish);
|
|
||||||
} else {
|
|
||||||
throw new InvalidArgumentError(`Unknown duration argument ${durationish} of type ${typeof durationish}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class Settings {
|
class Settings {
|
||||||
static get now() {
|
static get now() {
|
||||||
return now;
|
return now;
|
||||||
|
@ -1837,9 +1837,9 @@ function hasInvalidTimeData(obj) {
|
|||||||
} else return false;
|
} else return false;
|
||||||
}
|
}
|
||||||
const INVALID = "Invalid DateTime";
|
const INVALID = "Invalid DateTime";
|
||||||
|
const INVALID1 = "Invalid Duration";
|
||||||
let intlDTCache = {
|
let intlDTCache = {
|
||||||
};
|
};
|
||||||
const INVALID1 = "Invalid Duration";
|
|
||||||
let now = ()=>Date.now()
|
let now = ()=>Date.now()
|
||||||
, defaultZone = null, defaultLocale = null, defaultNumberingSystem = null, defaultOutputCalendar = null, throwOnInvalid = false;
|
, defaultZone = null, defaultLocale = null, defaultNumberingSystem = null, defaultOutputCalendar = null, throwOnInvalid = false;
|
||||||
const INVALID2 = "Invalid Interval";
|
const INVALID2 = "Invalid Interval";
|
||||||
@ -2576,6 +2576,17 @@ function diffRelative(start, end, opts) {
|
|||||||
}
|
}
|
||||||
return format(0, opts.units[opts.units.length - 1]);
|
return format(0, opts.units[opts.units.length - 1]);
|
||||||
}
|
}
|
||||||
|
function friendlyDuration(durationish) {
|
||||||
|
if (isNumber(durationish)) {
|
||||||
|
return Duration.fromMillis(durationish);
|
||||||
|
} else if (Duration.isDuration(durationish)) {
|
||||||
|
return durationish;
|
||||||
|
} else if (typeof durationish === "object") {
|
||||||
|
return Duration.fromObject(durationish);
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentError(`Unknown duration argument ${durationish} of type ${typeof durationish}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
class DateTime {
|
class DateTime {
|
||||||
constructor(config1){
|
constructor(config1){
|
||||||
const zone1 = config1.zone || Settings.defaultZone;
|
const zone1 = config1.zone || Settings.defaultZone;
|
||||||
@ -3710,17 +3721,6 @@ class Locale {
|
|||||||
return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;
|
return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function friendlyDuration(durationish) {
|
|
||||||
if (isNumber(durationish)) {
|
|
||||||
return Duration.fromMillis(durationish);
|
|
||||||
} else if (Duration.isDuration(durationish)) {
|
|
||||||
return durationish;
|
|
||||||
} else if (typeof durationish === "object") {
|
|
||||||
return Duration.fromObject(durationish);
|
|
||||||
} else {
|
|
||||||
throw new InvalidArgumentError(`Unknown duration argument ${durationish} of type ${typeof durationish}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class Settings {
|
class Settings {
|
||||||
static get now() {
|
static get now() {
|
||||||
return now;
|
return now;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
console.log('a');
|
console.log('a');
|
||||||
console.log('c');
|
|
||||||
console.log('b');
|
console.log('b');
|
||||||
|
console.log('c');
|
||||||
console.log('entry');
|
console.log('entry');
|
||||||
|
Loading…
Reference in New Issue
Block a user