Skip to content

Commit 3755415

Browse files
authored
Turbopack: side effect directive (#76876)
Any suggestions regarding the exact naming? ``` "use turbopack no side effects" export const bar = 123; ```
1 parent ad0d49f commit 3755415

File tree

7 files changed

+78
-2
lines changed

7 files changed

+78
-2
lines changed

turbopack/crates/turbopack-ecmascript/src/lib.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ use turbo_tasks::{
6767
trace::TraceRawVcs, FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryJoinIterExt,
6868
Value, ValueToString, Vc,
6969
};
70-
use turbo_tasks_fs::{rope::Rope, FileJsonContent, FileSystemPath};
70+
use turbo_tasks_fs::{glob::Glob, rope::Rope, FileJsonContent, FileSystemPath};
7171
use turbopack_core::{
7272
asset::{Asset, AssetContent},
7373
chunk::{
@@ -92,7 +92,7 @@ pub use turbopack_resolve::ecmascript as resolve;
9292

9393
use self::chunk::{EcmascriptChunkItemContent, EcmascriptChunkType, EcmascriptExports};
9494
use crate::{
95-
chunk::EcmascriptChunkPlaceable,
95+
chunk::{placeable::is_marked_as_side_effect_free, EcmascriptChunkPlaceable},
9696
code_gen::CodeGens,
9797
parse::generate_js_source_map,
9898
references::{
@@ -646,6 +646,21 @@ impl EcmascriptChunkPlaceable for EcmascriptModuleAsset {
646646
async fn get_async_module(self: Vc<Self>) -> Result<Vc<OptionAsyncModule>> {
647647
Ok(*self.analyze().await?.async_module)
648648
}
649+
650+
#[turbo_tasks::function]
651+
async fn is_marked_as_side_effect_free(
652+
self: Vc<Self>,
653+
side_effect_free_packages: Vc<Glob>,
654+
) -> Result<Vc<bool>> {
655+
// Check package.json first, so that we can skip parsing the module if it's marked that way.
656+
let pkg_side_effect_free =
657+
is_marked_as_side_effect_free(self.ident().path(), side_effect_free_packages);
658+
Ok(if *pkg_side_effect_free.await? {
659+
pkg_side_effect_free
660+
} else {
661+
Vc::cell(self.analyze().await?.has_side_effect_free_directive)
662+
})
663+
}
649664
}
650665

651666
#[turbo_tasks::value_impl]

turbopack/crates/turbopack-ecmascript/src/references/mod.rs

+38
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use std::{borrow::Cow, collections::BTreeMap, future::Future, mem::take, ops::De
2323
use anyhow::{bail, Result};
2424
use constant_condition::{ConstantConditionCodeGen, ConstantConditionValue};
2525
use constant_value::ConstantValueCodeGen;
26+
use either::Either;
2627
use indexmap::map::Entry;
2728
use lazy_static::lazy_static;
2829
use num_traits::Zero;
@@ -42,6 +43,7 @@ use swc_core::{
4243
},
4344
ecma::{
4445
ast::*,
46+
utils::IsDirective,
4547
visit::{
4648
fields::{AssignExprField, AssignTargetField, SimpleAssignTargetField},
4749
AstParentKind, AstParentNodeRef, VisitAstPath, VisitWithAstPath,
@@ -169,6 +171,7 @@ pub struct AnalyzeEcmascriptModuleResult {
169171
pub code_generation: ResolvedVc<CodeGens>,
170172
pub exports: ResolvedVc<EcmascriptExports>,
171173
pub async_module: ResolvedVc<OptionAsyncModule>,
174+
pub has_side_effect_free_directive: bool,
172175
/// `true` when the analysis was successful.
173176
pub successful: bool,
174177
pub source_map: ResolvedVc<OptionStringifiedSourceMap>,
@@ -221,6 +224,7 @@ pub struct AnalyzeEcmascriptModuleResultBuilder {
221224
async_module: ResolvedVc<OptionAsyncModule>,
222225
successful: bool,
223226
source_map: Option<ResolvedVc<OptionStringifiedSourceMap>>,
227+
has_side_effect_free_directive: bool,
224228
}
225229

226230
impl AnalyzeEcmascriptModuleResultBuilder {
@@ -238,6 +242,7 @@ impl AnalyzeEcmascriptModuleResultBuilder {
238242
async_module: ResolvedVc::cell(None),
239243
successful: false,
240244
source_map: None,
245+
has_side_effect_free_directive: false,
241246
}
242247
}
243248

@@ -297,6 +302,11 @@ impl AnalyzeEcmascriptModuleResultBuilder {
297302
self.async_module = ResolvedVc::cell(Some(async_module));
298303
}
299304

305+
/// Set whether this module is side-efffect free according to a user-provided directive.
306+
pub fn set_has_side_effect_free_directive(&mut self, value: bool) {
307+
self.has_side_effect_free_directive = value;
308+
}
309+
300310
/// Sets whether the analysis was successful.
301311
pub fn set_successful(&mut self, successful: bool) {
302312
self.successful = successful;
@@ -411,6 +421,7 @@ impl AnalyzeEcmascriptModuleResultBuilder {
411421
code_generation: ResolvedVc::cell(self.code_gens),
412422
exports: self.exports.resolved_cell(),
413423
async_module: self.async_module,
424+
has_side_effect_free_directive: self.has_side_effect_free_directive,
414425
successful: self.successful,
415426
source_map,
416427
},
@@ -586,6 +597,33 @@ pub(crate) async fn analyse_ecmascript_module_internal(
586597
return analysis.build(Default::default(), false).await;
587598
};
588599

600+
let has_side_effect_free_directive = match program {
601+
Program::Module(module) => Either::Left(
602+
module
603+
.body
604+
.iter()
605+
.take_while(|i| match i {
606+
ModuleItem::Stmt(stmt) => stmt.directive_continue(),
607+
ModuleItem::ModuleDecl(_) => false,
608+
})
609+
.filter_map(|i| i.as_stmt()),
610+
),
611+
Program::Script(script) => Either::Right(
612+
script
613+
.body
614+
.iter()
615+
.take_while(|stmt| stmt.directive_continue()),
616+
),
617+
}
618+
.any(|f| match f {
619+
Stmt::Expr(ExprStmt { expr, .. }) => match &**expr {
620+
Expr::Lit(Lit::Str(Str { value, .. })) => value == "use turbopack no side effects",
621+
_ => false,
622+
},
623+
_ => false,
624+
});
625+
analysis.set_has_side_effect_free_directive(has_side_effect_free_directive);
626+
589627
let compile_time_info = compile_time_info_for_module_type(
590628
*raw_module.compile_time_info,
591629
eval_context.is_esm(specified_type),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { foo } from './lib/index.js'
2+
3+
it('should respect side effects directive', () => {
4+
expect(foo).toBe(789)
5+
6+
const modules = Object.keys(__turbopack_modules__)
7+
expect(modules).toContainEqual(expect.stringContaining('input/lib/foo'))
8+
expect(modules).not.toContainEqual(expect.stringContaining('input/lib/index'))
9+
expect(modules).not.toContainEqual(expect.stringContaining('input/lib/bar'))
10+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"use turbopack no side effects"
2+
3+
export const bar = 123;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"use turbopack no side effects"
2+
3+
export const foo = 789;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"use turbopack no side effects"
2+
3+
export {foo} from "./foo";
4+
export {bar} from "./bar";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"treeShakingMode": "reexports-only"
3+
}

0 commit comments

Comments
 (0)