Skip to content

Commit

Permalink
chore: optimize side effects detect
Browse files Browse the repository at this point in the history
  • Loading branch information
wre232114 committed May 16, 2024
1 parent 4f958b0 commit c6a3afa
Show file tree
Hide file tree
Showing 31 changed files with 11,985 additions and 8,951 deletions.
7 changes: 5 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
"prettier.enable": false,
"editor.defaultFormatter": "biomejs.biome",
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.defaultFormatter": "biomejs.biome"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.defaultFormatter": "biomejs.biome"
},
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
}
}
2 changes: 1 addition & 1 deletion crates/compiler/tests/fixtures/tree_shake/cjs/output.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//index.js:
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};r[e](i,i.exports,o,n);t[e]=i;return i.exports}o(e)})({"d2214aaa":function (module, exports, farmRequire, farmDynamicRequire) {
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"d2214aaa":function (module, exports, farmRequire, farmDynamicRequire) {
console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]);
}
,},"d2214aaa");(function(_){for(var r in _){_[r].__farm_resource_pot__='index_6889.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"3da733a3":function (module, exports, farmRequire, farmDynamicRequire) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//index.js:
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};r[e](i,i.exports,o,n);t[e]=i;return i.exports}o(e)})({"ec853507":function (module, exports, farmRequire, farmDynamicRequire) {
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"ec853507":function (module, exports, farmRequire, farmDynamicRequire) {
console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]);
}
,},"ec853507");(function(_){for(var r in _){_[r].__farm_resource_pot__='index_d7d4.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"3e3af5b6":function (module, exports, farmRequire, farmDynamicRequire) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
,});

//index.js:
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};r[e](i,i.exports,o,n);t[e]=i;return i.exports}o(e)})({"d2214aaa":function (module, exports, farmRequire, farmDynamicRequire) {
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"d2214aaa":function (module, exports, farmRequire, farmDynamicRequire) {
console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]);
}
,},"d2214aaa");(function(_){for(var r in _){_[r].__farm_resource_pot__='index_5d9b.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"7c4a34c2":async function (module, exports, farmRequire, farmDynamicRequire) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//index.js:
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};r[e](i,i.exports,o,n);t[e]=i;return i.exports}o(e)})({"d2214aaa":function (module, exports, farmRequire, farmDynamicRequire) {
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"d2214aaa":function (module, exports, farmRequire, farmDynamicRequire) {
console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]);
}
,},"d2214aaa");(function(_){for(var r in _){_[r].__farm_resource_pot__='index_6b9f.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"569704c1":function (module, exports, farmRequire, farmDynamicRequire) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//index.js:
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};r[e](i,i.exports,o,n);t[e]=i;return i.exports}o(e)})({"d2214aaa":function (module, exports, farmRequire, farmDynamicRequire) {
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"d2214aaa":function (module, exports, farmRequire, farmDynamicRequire) {
console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]);
}
,},"d2214aaa");(function(_){for(var r in _){_[r].__farm_resource_pot__='index_ecb7.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"569704c1":function (module, exports, farmRequire, farmDynamicRequire) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//index.js:
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};r[e](i,i.exports,o,n);t[e]=i;return i.exports}o(e)})({"0b3bded0":function (module, exports, farmRequire, farmDynamicRequire) {
(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"0b3bded0":function (module, exports, farmRequire, farmDynamicRequire) {
console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]);
}
,},"0b3bded0");(function(_){for(var r in _){_[r].__farm_resource_pot__='index_5314.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"b5d64806":function (module, exports, farmRequire, farmDynamicRequire) {
Expand Down Expand Up @@ -31,10 +31,6 @@
throw error;
}
var Context = React.createContext();
var LOADABLE_REQUIRED_CHUNKS_KEY = "__LOADABLE_REQUIRED_CHUNKS__";
function getRequiredChunkKey(namespace) {
return "" + namespace + LOADABLE_REQUIRED_CHUNKS_KEY;
}
var sharedInternals = Object.freeze({
__proto__: null,
getRequiredChunkKey: getRequiredChunkKey,
Expand Down Expand Up @@ -329,7 +325,6 @@
return null;
}
}), loadable$1 = _createLoadable$1.loadable, lazy$1 = _createLoadable$1.lazy;
var BROWSER = typeof window !== "undefined";
function loadableReady(done, _temp) {
if (done === void 0) {
done = function done() {};
Expand Down
4 changes: 4 additions & 0 deletions crates/core/src/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ impl ScriptModuleMetaData {
self.ast = ast;
}

pub fn take_comments(&mut self) -> CommentsMetaData {
std::mem::replace(&mut self.comments, CommentsMetaData::default())
}

pub fn set_comments(&mut self, comments: CommentsMetaData) {
self.comments = comments;
}
Expand Down
27 changes: 15 additions & 12 deletions crates/plugin_tree_shake/src/init_tree_shake_module_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,32 @@ use std::collections::HashMap;
use farmfe_core::{
module::{module_graph::ModuleGraph, ModuleId},
parking_lot::Mutex,
rayon::iter::{IntoParallelRefIterator, ParallelIterator},
rayon::iter::{IntoParallelIterator, ParallelIterator},
swc_common::GLOBALS,
};

use crate::module::TreeShakeModule;

pub fn init_tree_shake_module_map(
module_graph: &ModuleGraph,
module_graph: &mut ModuleGraph,
context: &std::sync::Arc<farmfe_core::context::CompilationContext>,
) -> HashMap<ModuleId, TreeShakeModule> {
let tree_shake_modules_map =
Mutex::new(std::collections::HashMap::<ModuleId, TreeShakeModule>::new());
module_graph.modules().par_iter().for_each(|module| {
if !module.module_type.is_script() || module.external {
return;
}
module_graph
.modules_mut()
.into_par_iter()
.for_each(|module| {
if !module.module_type.is_script() || module.external {
return;
}

GLOBALS.set(&context.meta.script.globals, || {
let tree_shake_module = TreeShakeModule::new(module);
tree_shake_modules_map
.lock()
.insert(module.id.clone(), tree_shake_module);
GLOBALS.set(&context.meta.script.globals, || {
let tree_shake_module = TreeShakeModule::new(module);
tree_shake_modules_map
.lock()
.insert(module.id.clone(), tree_shake_module);
});
});
});
tree_shake_modules_map.into_inner()
}
36 changes: 18 additions & 18 deletions crates/plugin_tree_shake/src/mark_initial_side_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ use std::collections::HashMap;

use farmfe_core::module::{module_graph::ModuleGraph, ModuleId};

use crate::module::TreeShakeModule;
use crate::module::{TreeShakeModule, UsedExports};

pub fn mark_initial_side_effects(
module_graph: &ModuleGraph,
tree_shake_modules_map: &mut HashMap<ModuleId, TreeShakeModule>,
) -> Vec<ModuleId> {
let mut entry_module_ids = vec![];

// mark entry modules as side_effects
for (entry_module_id, _) in module_graph.entries.clone() {
// mark entry modules as UsedExports::All
if let Some(tree_shake_module) = tree_shake_modules_map.get_mut(&entry_module_id) {
tree_shake_module.side_effects = true;
tree_shake_module.pending_used_exports = UsedExports::All;
}

entry_module_ids.push(entry_module_id);
Expand Down Expand Up @@ -42,21 +42,21 @@ pub fn mark_initial_side_effects(
}
}

// update contains_self_executed_stmt for the tree_shake_modules
for tree_shake_module_id in module_graph.toposort().0 {
if let Some(tree_shake_module) = tree_shake_modules_map.get(&tree_shake_module_id) {
let contains_self_executed_stmt = tree_shake_module.contains_self_executed_stmt;
module_graph
.dependents_ids(&tree_shake_module_id)
.into_iter()
.for_each(|dept_id| {
if let Some(dept_tree_shake_module) = tree_shake_modules_map.get_mut(&dept_id) {
dept_tree_shake_module.contains_self_executed_stmt =
dept_tree_shake_module.contains_self_executed_stmt || contains_self_executed_stmt
}
});
}
}
// // update contains_self_executed_stmt for the tree_shake_modules
// for tree_shake_module_id in module_graph.toposort().0 {
// if let Some(tree_shake_module) = tree_shake_modules_map.get(&tree_shake_module_id) {
// let contains_self_executed_stmt = tree_shake_module.contains_self_executed_stmt;
// module_graph
// .dependents_ids(&tree_shake_module_id)
// .into_iter()
// .for_each(|dept_id| {
// if let Some(dept_tree_shake_module) = tree_shake_modules_map.get_mut(&dept_id) {
// dept_tree_shake_module.contains_self_executed_stmt =
// dept_tree_shake_module.contains_self_executed_stmt || contains_self_executed_stmt
// }
// });
// }
// }

entry_module_ids
}
24 changes: 19 additions & 5 deletions crates/plugin_tree_shake/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{

use farmfe_core::{
module::{Module, ModuleId, ModuleSystem},
swc_common::Mark,
swc_common::{comments::SingleThreadedComments, Mark},
};

use crate::statement_graph::{
Expand Down Expand Up @@ -67,6 +67,13 @@ impl Default for UsedExports {
}

impl UsedExports {
pub fn as_partial(&self) -> &HashSet<UsedExportsIdent> {
match self {
UsedExports::All => panic!("UsedExports is not Partial"),
UsedExports::Partial(res) => res,
}
}

pub fn add_used_export(&mut self, used_export: UsedExportsIdent) {
// All means all exports are used, only handle Partial here
if let UsedExports::Partial(used_exports) = self {
Expand Down Expand Up @@ -122,23 +129,25 @@ pub struct TreeShakeModule {
}

impl TreeShakeModule {
pub fn new(module: &Module) -> Self {
pub fn new(module: &mut Module) -> Self {
farmfe_core::farm_profile_function!(format!(
"TreeShakeModule::new {:?}",
module.id.to_string()
));
let module_system = module.meta.as_script().module_system.clone();

// 1. generate statement graph
let comments_meta = module.meta.as_script_mut().take_comments();
let ast = &module.meta.as_script().ast;
let unresolved_mark = Mark::from_u32(module.meta.as_script().unresolved_mark);
let top_level_mark = Mark::from_u32(module.meta.as_script().top_level_mark);

let comments = SingleThreadedComments::from(comments_meta);
let stmt_graph = if module_system == ModuleSystem::EsModule {
StatementGraph::new(ast, unresolved_mark, top_level_mark)
StatementGraph::new(ast, unresolved_mark, top_level_mark, &comments)
} else {
StatementGraph::empty()
};
module.meta.as_script_mut().set_comments(comments.into());

// 2. set default used exports
let handled_used_exports = if module.side_effects {
Expand All @@ -150,6 +159,8 @@ impl TreeShakeModule {
Self {
module_id: module.id.clone(),
contains_self_executed_stmt: module.side_effects
|| !matches!(module_system, ModuleSystem::EsModule)
|| stmt_graph.contains_bare_import_stmt()
|| !stmt_graph.preserved_side_effects_stmts().is_empty(),
stmt_graph,
pending_used_exports: handled_used_exports.clone(),
Expand Down Expand Up @@ -355,7 +366,10 @@ impl TreeShakeModule {
// skip default for export * from 'xxx'
if ident != *"default" {
for export_all_stmt_id in export_all_stmt_id {
used_idents.push((UsedStatementIdent::InExportAll(ident), export_all_stmt_id));
used_idents.push((
UsedStatementIdent::InExportAll(ident.clone()),
export_all_stmt_id,
));
}
}
}
Expand Down
48 changes: 37 additions & 11 deletions crates/plugin_tree_shake/src/statement_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::VecDeque;
use std::collections::{HashMap, HashSet};

use farmfe_core::petgraph::Direction;
use farmfe_core::swc_common::comments::SingleThreadedComments;
use farmfe_core::swc_common::Mark;
use farmfe_core::swc_ecma_ast::{Id, ImportSpecifier, ModuleDecl, ModuleExportName};
use farmfe_core::{
Expand Down Expand Up @@ -93,13 +94,13 @@ pub enum StatementSideEffects {
/// ```
WriteTopLevelVar(HashSet<Id>),

/// Access global variable, it's always preserved, for example:
/// Maybe modify global variable, it's always preserved, for example:
/// ```js
/// console.log('123');
/// window.b = 3;
/// document.body.addEventListener('click', () =/* */> {});
/// ```
AccessGlobalVar, // TODO investigate how top level mark works
WriteOrCallGlobalVar,

/// Unclassified default self executed statements are always treated as side effects. For example:
/// ```js
Expand All @@ -114,9 +115,6 @@ pub enum StatementSideEffects {
/// console.log('123');
/// }
/// foo();
/// for (let i = 0; i < 10; i++) {
/// window['a' + i] = i;
/// }
/// ```
/// They may be classified in the future to improve the accuracy of tree shaking
UnclassifiedSelfExecuted,
Expand All @@ -130,7 +128,10 @@ pub enum StatementSideEffects {

impl StatementSideEffects {
pub fn is_preserved(&self) -> bool {
matches!(self, Self::AccessGlobalVar | Self::UnclassifiedSelfExecuted)
matches!(
self,
Self::WriteOrCallGlobalVar | Self::UnclassifiedSelfExecuted
)
}

pub fn merge_side_effects(&mut self, other: Self) {
Expand All @@ -141,8 +142,8 @@ impl StatementSideEffects {
a.extend(b.iter().cloned());
}
(Self::NoSideEffects, _) => original_self_value = other,
(Self::AccessGlobalVar | Self::UnclassifiedSelfExecuted, _) => {}
(Self::WriteTopLevelVar(_), Self::AccessGlobalVar | Self::UnclassifiedSelfExecuted) => {
(Self::WriteOrCallGlobalVar | Self::UnclassifiedSelfExecuted, _) => {}
(Self::WriteTopLevelVar(_), Self::WriteOrCallGlobalVar | Self::UnclassifiedSelfExecuted) => {
original_self_value = other;
}
(_, Self::NoSideEffects) => {}
Expand Down Expand Up @@ -204,6 +205,7 @@ impl Statement {
stmt: &ModuleItem,
unresolved_mark: Mark,
top_level_mark: Mark,
comments: &SingleThreadedComments,
) -> Self {
// 1. analyze all import, export and defined idents of the ModuleItem
let AnalyzedStatementInfo {
Expand All @@ -217,6 +219,7 @@ impl Statement {
stmt,
unresolved_mark,
top_level_mark,
comments,
);

Self {
Expand Down Expand Up @@ -265,14 +268,19 @@ pub struct StatementGraph {
}

impl StatementGraph {
pub fn new(module: &SwcModule, unresolved_mark: Mark, top_level_mark: Mark) -> Self {
pub fn new(
module: &SwcModule,
unresolved_mark: Mark,
top_level_mark: Mark,
comments: &SingleThreadedComments,
) -> Self {
let mut g = petgraph::graph::Graph::new();
let mut id_index_map = HashMap::new();

let mut reverse_defined_idents_map = HashMap::new();
// 1. analyze all defined idents of each statement
for (index, item) in module.body.iter().enumerate() {
let stmt = Statement::new(index, item, unresolved_mark, top_level_mark);
let stmt = Statement::new(index, item, unresolved_mark, top_level_mark, comments);

// export named does not define any idents
if !matches!(item, ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(_))) {
Expand Down Expand Up @@ -330,6 +338,24 @@ impl StatementGraph {
.collect()
}

pub fn contains_bare_import_stmt(&self) -> bool {
self
.stmt_ids()
.into_iter()
.any(|stmt_id| self.is_bare_import_stmt(stmt_id))
}

/// true if stmt is import './xxx'. (without specifiers)
pub fn is_bare_import_stmt(&self, stmt_id: StatementId) -> bool {
let stmt = self.stmt(&stmt_id);

if let Some(import_info) = &stmt.import_info {
return import_info.specifiers.is_empty();
}

false
}

pub fn add_edge(&mut self, from: StatementId, to: StatementId, edge_weight: StatementGraphEdge) {
let from_node = self.id_index_map.get(&from).unwrap();
let to_node = self.id_index_map.get(&to).unwrap();
Expand Down Expand Up @@ -599,7 +625,7 @@ impl StatementGraph {
all_dept_used_idents.extend(dept_used_idents);
}
}
StatementSideEffects::AccessGlobalVar
StatementSideEffects::WriteOrCallGlobalVar
| StatementSideEffects::UnclassifiedSelfExecuted
| StatementSideEffects::NoSideEffects => { /* These 3 types are handled already */ }
}
Expand Down

0 comments on commit c6a3afa

Please sign in to comment.