Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turbopack: side effect directive #76876

Merged
merged 2 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions turbopack/crates/turbopack-ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ use turbo_tasks::{
trace::TraceRawVcs, FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryJoinIterExt,
Value, ValueToString, Vc,
};
use turbo_tasks_fs::{rope::Rope, FileJsonContent, FileSystemPath};
use turbo_tasks_fs::{glob::Glob, rope::Rope, FileJsonContent, FileSystemPath};
use turbopack_core::{
asset::{Asset, AssetContent},
chunk::{
Expand All @@ -92,7 +92,7 @@ pub use turbopack_resolve::ecmascript as resolve;

use self::chunk::{EcmascriptChunkItemContent, EcmascriptChunkType, EcmascriptExports};
use crate::{
chunk::EcmascriptChunkPlaceable,
chunk::{placeable::is_marked_as_side_effect_free, EcmascriptChunkPlaceable},
code_gen::CodeGens,
parse::generate_js_source_map,
references::{
Expand Down Expand Up @@ -646,6 +646,21 @@ impl EcmascriptChunkPlaceable for EcmascriptModuleAsset {
async fn get_async_module(self: Vc<Self>) -> Result<Vc<OptionAsyncModule>> {
Ok(*self.analyze().await?.async_module)
}

#[turbo_tasks::function]
async fn is_marked_as_side_effect_free(
self: Vc<Self>,
side_effect_free_packages: Vc<Glob>,
) -> Result<Vc<bool>> {
// Check package.json first, so that we can skip parsing the module if it's marked that way.
let pkg_side_effect_free =
is_marked_as_side_effect_free(self.ident().path(), side_effect_free_packages);
Ok(if *pkg_side_effect_free.await? {
pkg_side_effect_free
} else {
Vc::cell(self.analyze().await?.has_side_effect_free_directive)
})
}
}

#[turbo_tasks::value_impl]
Expand Down
38 changes: 38 additions & 0 deletions turbopack/crates/turbopack-ecmascript/src/references/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use std::{borrow::Cow, collections::BTreeMap, future::Future, mem::take, ops::De
use anyhow::{bail, Result};
use constant_condition::{ConstantConditionCodeGen, ConstantConditionValue};
use constant_value::ConstantValueCodeGen;
use either::Either;
use indexmap::map::Entry;
use lazy_static::lazy_static;
use num_traits::Zero;
Expand All @@ -42,6 +43,7 @@ use swc_core::{
},
ecma::{
ast::*,
utils::IsDirective,
visit::{
fields::{AssignExprField, AssignTargetField, SimpleAssignTargetField},
AstParentKind, AstParentNodeRef, VisitAstPath, VisitWithAstPath,
Expand Down Expand Up @@ -169,6 +171,7 @@ pub struct AnalyzeEcmascriptModuleResult {
pub code_generation: ResolvedVc<CodeGens>,
pub exports: ResolvedVc<EcmascriptExports>,
pub async_module: ResolvedVc<OptionAsyncModule>,
pub has_side_effect_free_directive: bool,
/// `true` when the analysis was successful.
pub successful: bool,
pub source_map: ResolvedVc<OptionStringifiedSourceMap>,
Expand Down Expand Up @@ -221,6 +224,7 @@ pub struct AnalyzeEcmascriptModuleResultBuilder {
async_module: ResolvedVc<OptionAsyncModule>,
successful: bool,
source_map: Option<ResolvedVc<OptionStringifiedSourceMap>>,
has_side_effect_free_directive: bool,
}

impl AnalyzeEcmascriptModuleResultBuilder {
Expand All @@ -238,6 +242,7 @@ impl AnalyzeEcmascriptModuleResultBuilder {
async_module: ResolvedVc::cell(None),
successful: false,
source_map: None,
has_side_effect_free_directive: false,
}
}

Expand Down Expand Up @@ -297,6 +302,11 @@ impl AnalyzeEcmascriptModuleResultBuilder {
self.async_module = ResolvedVc::cell(Some(async_module));
}

/// Set whether this module is side-efffect free according to a user-provided directive.
pub fn set_has_side_effect_free_directive(&mut self, value: bool) {
self.has_side_effect_free_directive = value;
}

/// Sets whether the analysis was successful.
pub fn set_successful(&mut self, successful: bool) {
self.successful = successful;
Expand Down Expand Up @@ -411,6 +421,7 @@ impl AnalyzeEcmascriptModuleResultBuilder {
code_generation: ResolvedVc::cell(self.code_gens),
exports: self.exports.resolved_cell(),
async_module: self.async_module,
has_side_effect_free_directive: self.has_side_effect_free_directive,
successful: self.successful,
source_map,
},
Expand Down Expand Up @@ -586,6 +597,33 @@ pub(crate) async fn analyse_ecmascript_module_internal(
return analysis.build(Default::default(), false).await;
};

let has_side_effect_free_directive = match program {
Program::Module(module) => Either::Left(
module
.body
.iter()
.take_while(|i| match i {
ModuleItem::Stmt(stmt) => stmt.directive_continue(),
ModuleItem::ModuleDecl(_) => false,
})
.filter_map(|i| i.as_stmt()),
),
Program::Script(script) => Either::Right(
script
.body
.iter()
.take_while(|stmt| stmt.directive_continue()),
),
}
.any(|f| match f {
Stmt::Expr(ExprStmt { expr, .. }) => match &**expr {
Expr::Lit(Lit::Str(Str { value, .. })) => value == "use turbopack no side effects",
_ => false,
},
_ => false,
});
analysis.set_has_side_effect_free_directive(has_side_effect_free_directive);

let compile_time_info = compile_time_info_for_module_type(
*raw_module.compile_time_info,
eval_context.is_esm(specified_type),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { foo } from './lib/index.js'

it('should respect side effects directive', () => {
expect(foo).toBe(789)

const modules = Object.keys(__turbopack_modules__)
expect(modules).toContainEqual(expect.stringContaining('input/lib/foo'))
expect(modules).not.toContainEqual(expect.stringContaining('input/lib/index'))
expect(modules).not.toContainEqual(expect.stringContaining('input/lib/bar'))
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use turbopack no side effects"

export const bar = 123;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use turbopack no side effects"

export const foo = 789;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use turbopack no side effects"

export {foo} from "./foo";
export {bar} from "./bar";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"treeShakingMode": "reexports-only"
}
Loading