mirror of
https://github.com/swc-project/swc.git
synced 2024-12-29 16:42:28 +03:00
bundler: Make output deterministic (#1166)
swc_bundler: - Make output deterministic
This commit is contained in:
parent
11d137ac11
commit
41d1738b82
@ -6,17 +6,18 @@ edition = "2018"
|
||||
license = "Apache-2.0/MIT"
|
||||
name = "swc_bundler"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
concurrent = ["swc_common/concurrent", "dashmap", "rayon"]
|
||||
concurrent = ["swc_common/concurrent", "dashmap", "rayon", "indexmap/rayon"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
crc = "1.8"
|
||||
dashmap = {version = "3", optional = true}
|
||||
indexmap = "1.6"
|
||||
is-macro = "0.1"
|
||||
log = "0.4"
|
||||
once_cell = "1"
|
||||
|
@ -7,10 +7,13 @@ use crate::{
|
||||
use anyhow::{Context, Error};
|
||||
#[cfg(feature = "concurrent")]
|
||||
use rayon::iter::ParallelIterator;
|
||||
use std::mem::{replace, take};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
mem::{replace, take},
|
||||
};
|
||||
use swc_common::{Spanned, SyntaxContext, DUMMY_SP};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::{find_ids, ident::IdentLike, Id};
|
||||
use swc_ecma_utils::{ident::IdentLike, Id};
|
||||
use swc_ecma_visit::{noop_fold_type, noop_visit_mut_type, Fold, FoldWith, VisitMut, VisitMutWith};
|
||||
|
||||
impl<L, R> Bundler<'_, L, R>
|
||||
@ -62,7 +65,7 @@ where
|
||||
// Transitive dependencies
|
||||
let mut additional_modules = vec![];
|
||||
let mut reexports = vec![];
|
||||
let mut decls_for_reexport = vec![];
|
||||
let mut decls_for_reexport: HashMap<_, Vec<VarDeclarator>> = HashMap::new();
|
||||
|
||||
// Remove transitive dependencies which is merged by parent moudle.
|
||||
for v in info.exports.reexports.clone() {
|
||||
@ -82,7 +85,7 @@ where
|
||||
|
||||
// export * from './foo';
|
||||
if specifiers.is_empty() {
|
||||
decls_for_reexport.extend(
|
||||
decls_for_reexport.entry(src.module_id).or_default().extend(
|
||||
imported
|
||||
.exports
|
||||
.items
|
||||
@ -293,6 +296,18 @@ where
|
||||
};
|
||||
entry.body.visit_mut_with(&mut injector);
|
||||
|
||||
// Inject variables
|
||||
if let Some(decls) = decls_for_reexport.remove(&src.module_id) {
|
||||
entry
|
||||
.body
|
||||
.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Const,
|
||||
declare: false,
|
||||
decls,
|
||||
}))));
|
||||
}
|
||||
|
||||
// print_hygiene(
|
||||
// &format!(
|
||||
// "entry:reexport injection {:?} <- {:?}",
|
||||
@ -305,6 +320,12 @@ where
|
||||
assert_eq!(injector.imported, vec![]);
|
||||
}
|
||||
|
||||
let decls_for_reexport: Vec<_> = decls_for_reexport
|
||||
.into_iter()
|
||||
.map(|(_, decls)| decls)
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if !decls_for_reexport.is_empty() {
|
||||
entry
|
||||
.body
|
||||
@ -486,7 +507,9 @@ impl VisitMut for UnexportAsVar<'_> {
|
||||
}],
|
||||
})));
|
||||
}
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(ref export)) => {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
|
||||
ref export @ NamedExport { src: None, .. },
|
||||
)) => {
|
||||
let mut decls = vec![];
|
||||
for s in &export.specifiers {
|
||||
match s {
|
||||
@ -544,74 +567,6 @@ impl VisitMut for UnexportAsVar<'_> {
|
||||
fn visit_mut_stmt(&mut self, _: &mut Stmt) {}
|
||||
}
|
||||
|
||||
struct AliasExports {
|
||||
/// Syntax context of the importer.
|
||||
importer_ctxt: SyntaxContext,
|
||||
decls: Vec<VarDeclarator>,
|
||||
}
|
||||
|
||||
impl VisitMut for AliasExports {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
|
||||
for item in items.iter_mut() {
|
||||
item.visit_mut_with(self);
|
||||
}
|
||||
|
||||
if !self.decls.is_empty() {
|
||||
items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Const,
|
||||
decls: take(&mut self.decls),
|
||||
declare: false,
|
||||
}))))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_module_decl(&mut self, decl: &mut ModuleDecl) {
|
||||
match decl {
|
||||
ModuleDecl::ExportDecl(ref export) => match &export.decl {
|
||||
Decl::Class(c) => self.decls.push(VarDeclarator {
|
||||
span: c.class.span,
|
||||
name: Pat::Ident(c.ident.clone()),
|
||||
init: Some(Box::new(Expr::Ident(Ident::new(
|
||||
c.ident.sym.clone(),
|
||||
c.ident.span.with_ctxt(self.importer_ctxt),
|
||||
)))),
|
||||
definite: false,
|
||||
}),
|
||||
Decl::Fn(f) => self.decls.push(VarDeclarator {
|
||||
span: f.function.span,
|
||||
name: Pat::Ident(Ident::new(
|
||||
f.ident.sym.clone(),
|
||||
f.ident.span.with_ctxt(self.importer_ctxt),
|
||||
)),
|
||||
init: Some(Box::new(Expr::Ident(f.ident.clone()))),
|
||||
definite: false,
|
||||
}),
|
||||
Decl::Var(var) => {
|
||||
let ids = find_ids::<_, Ident>(&var.decls);
|
||||
for ident in ids {
|
||||
self.decls.push(VarDeclarator {
|
||||
span: ident.span,
|
||||
name: Pat::Ident(Ident::new(
|
||||
ident.sym.clone(),
|
||||
ident.span.with_ctxt(self.importer_ctxt),
|
||||
)),
|
||||
init: Some(Box::new(Expr::Ident(ident))),
|
||||
definite: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_stmt(&mut self, _: &mut Stmt) {}
|
||||
}
|
||||
|
||||
struct DepUnexporter<'a> {
|
||||
exports: &'a [Specifier],
|
||||
}
|
||||
|
@ -70,6 +70,9 @@ where
|
||||
|
||||
// print_hygiene(&format!("{}", info.fm.name), &self.cm, &entry);
|
||||
|
||||
self.merge_reexports(plan, module_plan, &mut entry, &info, merged)
|
||||
.context("failed to merge reepxorts")?;
|
||||
|
||||
if module_plan.chunks.is_empty() && module_plan.transitive_chunks.is_empty() {
|
||||
return Ok(entry);
|
||||
}
|
||||
@ -81,9 +84,6 @@ where
|
||||
plan.normal.get(&info.id)
|
||||
);
|
||||
|
||||
self.merge_reexports(plan, module_plan, &mut entry, &info, merged)
|
||||
.context("failed to merge reepxorts")?;
|
||||
|
||||
// We handle this kind of modules specially.
|
||||
if self.scope.should_be_wrapped_with_a_fn(info.id) {
|
||||
return Ok(entry);
|
||||
|
@ -49,12 +49,9 @@ pub(super) fn least_common_ancestor(b: &PlanBuilder, module_ids: &[ModuleId]) ->
|
||||
});
|
||||
}
|
||||
|
||||
fn check_itself<I>(b: &PlanBuilder, li: I, ri: &[ModuleId]) -> Option<ModuleId>
|
||||
where
|
||||
I: IntoIterator<Item = ModuleId>,
|
||||
{
|
||||
fn check_itself(b: &PlanBuilder, li: &[ModuleId], ri: &[ModuleId]) -> Option<ModuleId> {
|
||||
let g = &b.direct_deps;
|
||||
for l in li {
|
||||
for &l in li {
|
||||
// Root
|
||||
if g.neighbors_directed(l, Incoming).count() == 0 {
|
||||
return Some(l);
|
||||
@ -78,12 +75,13 @@ where
|
||||
fn check_itself_and_parent(b: &PlanBuilder, li: &[ModuleId], ri: &[ModuleId]) -> Option<ModuleId> {
|
||||
let g = &b.direct_deps;
|
||||
|
||||
if let Some(id) = check_itself(b, li.iter().copied(), ri) {
|
||||
if let Some(id) = check_itself(b, li, ri) {
|
||||
return Some(id);
|
||||
}
|
||||
|
||||
for &l in li {
|
||||
let mut l_dependants = g.neighbors_directed(l, Incoming).collect::<Vec<_>>();
|
||||
l_dependants.sort();
|
||||
for &l_dependant in &l_dependants {
|
||||
if g.neighbors_directed(l_dependant, Incoming).count() == 0 {
|
||||
return Some(l_dependant);
|
||||
@ -102,6 +100,7 @@ fn check_itself_and_parent(b: &PlanBuilder, li: &[ModuleId], ri: &[ModuleId]) ->
|
||||
|
||||
for &r in ri {
|
||||
let mut r_dependants = g.neighbors_directed(r, Incoming).collect::<Vec<_>>();
|
||||
r_dependants.sort();
|
||||
for &r_dependant in &r_dependants {
|
||||
if g.neighbors_directed(r_dependant, Incoming).count() == 0 {
|
||||
return Some(r_dependant);
|
||||
|
@ -242,7 +242,7 @@ where
|
||||
entry
|
||||
);
|
||||
if entry != root_entry && dep != root_entry {
|
||||
done.insert(dep);
|
||||
// done.insert(dep);
|
||||
plans.normal.entry(entry).or_default().chunks.push(dep);
|
||||
}
|
||||
continue;
|
||||
|
@ -3,7 +3,7 @@ use super::{
|
||||
Bundler,
|
||||
};
|
||||
use crate::{id::Id, load::Load, resolve::Resolve};
|
||||
use std::collections::HashMap;
|
||||
use indexmap::IndexMap;
|
||||
use swc_atoms::{js_word, JsWord};
|
||||
use swc_common::{FileName, SyntaxContext};
|
||||
use swc_ecma_ast::*;
|
||||
@ -39,7 +39,7 @@ where
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct RawExports {
|
||||
/// Key is None if it's exported from the module itself.
|
||||
pub items: HashMap<Option<Str>, Vec<Specifier>>,
|
||||
pub items: IndexMap<Option<Str>, Vec<Specifier>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -20,34 +20,37 @@ use url::Url;
|
||||
#[test]
|
||||
#[ignore = "Too slow"]
|
||||
fn oak_6_3_1_application() {
|
||||
run("https://deno.land/x/oak@v6.3.1/application.ts");
|
||||
run("https://deno.land/x/oak@v6.3.1/application.ts", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Too slow"]
|
||||
fn oak_6_3_1_mod() {
|
||||
run("https://deno.land/x/oak@v6.3.1/mod.ts");
|
||||
run("https://deno.land/x/oak@v6.3.1/mod.ts", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Too slow"]
|
||||
fn std_0_74_9_http_server() {
|
||||
run("https://deno.land/std@0.74.0/http/server.ts");
|
||||
run("https://deno.land/std@0.74.0/http/server.ts", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Too slow"]
|
||||
fn oak_6_3_1_example() {
|
||||
run("https://deno.land/x/oak@v6.3.1/examples/server.ts");
|
||||
run("https://deno.land/x/oak@v6.3.1/examples/server.ts", None);
|
||||
}
|
||||
|
||||
fn run(url: &str) {
|
||||
fn run(url: &str, expeceted_bytes: Option<usize>) {
|
||||
let dir = tempfile::tempdir().expect("failed to crate temp file");
|
||||
let path = dir.path().join("main.js");
|
||||
println!("{}", path.display());
|
||||
|
||||
let src = bundle(url);
|
||||
write(&path, &src).unwrap();
|
||||
if let Some(expected) = expeceted_bytes {
|
||||
assert_eq!(src.len(), expected);
|
||||
}
|
||||
|
||||
let output = Command::new("deno")
|
||||
.arg("run")
|
||||
@ -112,6 +115,8 @@ struct Loader {
|
||||
|
||||
impl Load for Loader {
|
||||
fn load(&self, file: &FileName) -> Result<(Lrc<SourceFile>, Module), Error> {
|
||||
eprintln!("{}", file);
|
||||
|
||||
let url = match file {
|
||||
FileName::Custom(v) => v,
|
||||
_ => unreachable!("this test only uses url"),
|
||||
|
Loading…
Reference in New Issue
Block a user