bundler: Make output deterministic (#1166)

swc_bundler:
 - Make output deterministic
This commit is contained in:
강동윤 2020-10-16 18:02:42 +09:00 committed by GitHub
parent 11d137ac11
commit 41d1738b82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 52 additions and 92 deletions

View File

@ -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"

View File

@ -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],
}

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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)]

View File

@ -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"),