Skip to content

Commit 67ec3ad

Browse files
authored
Turbopack: expose used export info in tests (#86037)
This will allow us to reuse more Webpack tests soon ```js module.exports = [ "[project]/turbopack/crates/turbopack-tests/tests/execution/turbopack/code-gen/exports-info/input/index.js [test] (ecmascript)", ((__turbopack_context__) => { "use strict"; // MERGED MODULE: [project]/turbopack/crates/turbopack-tests/tests/execution/turbopack/code-gen/exports-info/input/module.js [test] (ecmascript) ; const __TURBOPACK__$5f$_webpack_exports_info_$5f$__ = { "bar": { used: false }, "foo": { used: true }, "usage": { used: true } }; const foo = 'foo'; const bar = 'bar'; const usage = __TURBOPACK__$5f$_webpack_exports_info_$5f$__; // MERGED MODULE: [project]/turbopack/crates/turbopack-tests/tests/execution/turbopack/code-gen/exports-info/input/index.js [test] (ecmascript) ; ; it('should expose __webpack_exports_info__ in tests', ()=>{ expect(foo).toBe('foo'); expect(usage.foo.used).toBe(true); expect(usage.bar.used).toBe(false); expect(usage.usage.used).toBe(true); }); __turbopack_context__.s([], "[project]/turbopack/crates/turbopack-tests/tests/execution/turbopack/code-gen/exports-info/input/index.js [test] (ecmascript)"); }), ]; ```
1 parent 291aefd commit 67ec3ad

File tree

10 files changed

+179
-1
lines changed

10 files changed

+179
-1
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use turbopack_core::{chunk::ChunkingContext, reference::ModuleReference};
1616

1717
use crate::{
1818
ScopeHoistingContext,
19+
chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
1920
references::{
2021
AstPath,
2122
amd::AmdDefineWithDependenciesCodeGen,
@@ -31,6 +32,7 @@ use crate::{
3132
dynamic::EsmAsyncAssetReferenceCodeGen, module_id::EsmModuleIdAssetReferenceCodeGen,
3233
url::UrlAssetReferenceCodeGen,
3334
},
35+
exports_info::{ExportsInfoBinding, ExportsInfoRef},
3436
ident::IdentReplacement,
3537
member::MemberReplacement,
3638
require_context::RequireContextAssetReferenceCodeGen,
@@ -181,6 +183,8 @@ pub enum CodeGen {
181183
DynamicExpression(DynamicExpression),
182184
EsmBinding(EsmBinding),
183185
EsmModuleItem(EsmModuleItem),
186+
ExportsInfoBinding(ExportsInfoBinding),
187+
ExportsInfoRef(ExportsInfoRef),
184188
IdentReplacement(IdentReplacement),
185189
ImportMetaBinding(ImportMetaBinding),
186190
ImportMetaRef(ImportMetaRef),
@@ -200,6 +204,8 @@ impl CodeGen {
200204
&self,
201205
ctx: Vc<Box<dyn ChunkingContext>>,
202206
scope_hoisting_context: ScopeHoistingContext<'_>,
207+
module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
208+
exports: ResolvedVc<EcmascriptExports>,
203209
) -> Result<CodeGeneration> {
204210
match self {
205211
Self::AmdDefineWithDependenciesCodeGen(v) => v.code_generation(ctx).await,
@@ -209,6 +215,8 @@ impl CodeGen {
209215
Self::DynamicExpression(v) => v.code_generation(ctx).await,
210216
Self::EsmBinding(v) => v.code_generation(ctx, scope_hoisting_context).await,
211217
Self::EsmModuleItem(v) => v.code_generation(ctx).await,
218+
Self::ExportsInfoBinding(v) => v.code_generation(ctx, module, exports).await,
219+
Self::ExportsInfoRef(v) => v.code_generation(ctx).await,
212220
Self::IdentReplacement(v) => v.code_generation(ctx).await,
213221
Self::ImportMetaBinding(v) => v.code_generation(ctx).await,
214222
Self::ImportMetaRef(v) => v.code_generation(ctx).await,

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ pub struct EcmascriptOptions {
270270
// node_modules.
271271
/// Whether to replace `typeof window` with some constant value.
272272
pub enable_typeof_window_inlining: Option<TypeofWindow>,
273+
/// Whether to allow accessing exports info via `__webpack_exports_info__`.
274+
pub enable_exports_info_inlining: bool,
273275

274276
pub inline_helpers: bool,
275277
}
@@ -1025,7 +1027,14 @@ impl EcmascriptModuleContentOptions {
10251027
let code_gens = code_generation
10261028
.await?
10271029
.iter()
1028-
.map(|c| c.code_generation(**chunking_context, scope_hoisting_context))
1030+
.map(|c| {
1031+
c.code_generation(
1032+
**chunking_context,
1033+
scope_hoisting_context,
1034+
*module,
1035+
*exports,
1036+
)
1037+
})
10291038
.try_join()
10301039
.await?;
10311040

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use anyhow::Result;
2+
use serde::{Deserialize, Serialize};
3+
use swc_core::{
4+
common::DUMMY_SP,
5+
ecma::ast::{Expr, Ident, KeyValueProp, ObjectLit, PropName, PropOrSpread},
6+
quote,
7+
};
8+
use turbo_rcstr::rcstr;
9+
use turbo_tasks::{NonLocalValue, ResolvedVc, Vc, debug::ValueDebugFormat, trace::TraceRawVcs};
10+
use turbopack_core::chunk::ChunkingContext;
11+
12+
use crate::{
13+
chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
14+
code_gen::{CodeGen, CodeGeneration},
15+
create_visitor, magic_identifier,
16+
references::AstPath,
17+
};
18+
19+
/// Responsible for initializing the `ExportsInfoBinding` object binding, so that it may be
20+
/// referenced in the the file.
21+
///
22+
/// There can be many references, and they appear at any nesting in the file. But we must only
23+
/// initialize the binding a single time.
24+
///
25+
/// This singleton behavior must be enforced by the caller!
26+
#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
27+
pub struct ExportsInfoBinding {}
28+
29+
impl ExportsInfoBinding {
30+
#[allow(clippy::new_without_default)]
31+
pub fn new() -> Self {
32+
ExportsInfoBinding {}
33+
}
34+
35+
pub async fn code_generation(
36+
&self,
37+
chunking_context: Vc<Box<dyn ChunkingContext>>,
38+
module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
39+
exports: ResolvedVc<EcmascriptExports>,
40+
) -> Result<CodeGeneration> {
41+
let export_usage_info = chunking_context
42+
.module_export_usage(*ResolvedVc::upcast(module))
43+
.await?;
44+
let export_usage_info = export_usage_info.export_usage.await?;
45+
46+
let props = if let EcmascriptExports::EsmExports(exports) = &*exports.await? {
47+
exports
48+
.await?
49+
.exports
50+
.keys()
51+
.map(|e| {
52+
let used: Expr = export_usage_info.is_export_used(e).into();
53+
PropOrSpread::Prop(Box::new(swc_core::ecma::ast::Prop::KeyValue(
54+
KeyValueProp {
55+
key: PropName::Str(e.as_str().into()),
56+
value: quote!("{ used: $v }" as Box<Expr>, v: Expr = used),
57+
},
58+
)))
59+
})
60+
.collect()
61+
} else {
62+
vec![]
63+
};
64+
65+
let data = Expr::Object(ObjectLit {
66+
props,
67+
span: DUMMY_SP,
68+
});
69+
70+
Ok(CodeGeneration::hoisted_stmt(
71+
rcstr!("__webpack_exports_info__"),
72+
quote!(
73+
"const $name = $data;" as Stmt,
74+
name = exports_ident(),
75+
data: Expr = data
76+
),
77+
))
78+
}
79+
}
80+
81+
impl From<ExportsInfoBinding> for CodeGen {
82+
fn from(val: ExportsInfoBinding) -> Self {
83+
CodeGen::ExportsInfoBinding(val)
84+
}
85+
}
86+
87+
/// Handles rewriting `__webpack_exports_info__` references into the injected binding created by
88+
/// ExportsInfoBinding.
89+
///
90+
/// There can be many references, and they appear at any nesting in the file. But all references
91+
/// refer to the same mutable object.
92+
#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
93+
pub struct ExportsInfoRef {
94+
ast_path: AstPath,
95+
}
96+
97+
impl ExportsInfoRef {
98+
pub fn new(ast_path: AstPath) -> Self {
99+
ExportsInfoRef { ast_path }
100+
}
101+
102+
pub async fn code_generation(
103+
&self,
104+
_chunking_context: Vc<Box<dyn ChunkingContext>>,
105+
) -> Result<CodeGeneration> {
106+
let visitor = create_visitor!(self.ast_path, visit_mut_expr, |expr: &mut Expr| {
107+
*expr = Expr::Ident(exports_ident());
108+
});
109+
110+
Ok(CodeGeneration::visitors(vec![visitor]))
111+
}
112+
}
113+
114+
impl From<ExportsInfoRef> for CodeGen {
115+
fn from(val: ExportsInfoRef) -> Self {
116+
CodeGen::ExportsInfoRef(val)
117+
}
118+
}
119+
120+
fn exports_ident() -> Ident {
121+
Ident::new(
122+
magic_identifier::mangle("__webpack_exports_info__").into(),
123+
DUMMY_SP,
124+
Default::default(),
125+
)
126+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod constant_condition;
55
pub mod constant_value;
66
pub mod dynamic_expression;
77
pub mod esm;
8+
pub mod exports_info;
89
pub mod external_module;
910
pub mod ident;
1011
pub mod member;
@@ -149,6 +150,7 @@ use crate::{
149150
EsmBinding, UrlRewriteBehavior, base::EsmAssetReferences,
150151
module_id::EsmModuleIdAssetReference,
151152
},
153+
exports_info::{ExportsInfoBinding, ExportsInfoRef},
152154
ident::IdentReplacement,
153155
member::MemberReplacement,
154156
node::{FilePathModuleReference, PackageJsonReference},
@@ -441,6 +443,9 @@ struct AnalysisState<'a> {
441443
// There can be many references to import.meta, but only the first should hoist
442444
// the object allocation.
443445
first_import_meta: bool,
446+
// There can be many references to __webpack_exports_info__, but only the first should hoist
447+
// the object allocation.
448+
first_webpack_exports_info: bool,
444449
tree_shaking_mode: Option<TreeShakingMode>,
445450
import_externals: bool,
446451
ignore_dynamic_requests: bool,
@@ -978,6 +983,7 @@ async fn analyze_ecmascript_module_internal(
978983
fun_args_values: Default::default(),
979984
var_cache: Default::default(),
980985
first_import_meta: true,
986+
first_webpack_exports_info: true,
981987
tree_shaking_mode: options.tree_shaking_mode,
982988
import_externals: options.import_externals,
983989
ignore_dynamic_requests: options.ignore_dynamic_requests,
@@ -1305,6 +1311,15 @@ async fn analyze_ecmascript_module_internal(
13051311
"unexpected Effect::FreeVar in tracing mode"
13061312
);
13071313

1314+
if options.enable_exports_info_inlining && var == "__webpack_exports_info__" {
1315+
if analysis_state.first_webpack_exports_info {
1316+
analysis_state.first_webpack_exports_info = false;
1317+
analysis.add_code_gen(ExportsInfoBinding::new());
1318+
}
1319+
analysis.add_code_gen(ExportsInfoRef::new(ast_path.into()));
1320+
continue;
1321+
}
1322+
13081323
// FreeVar("require") might be turbopackIgnore-d
13091324
if !analysis_state
13101325
.link_value(

turbopack/crates/turbopack-tests/tests/execution.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ async fn run_test_operation(prepared_test: ResolvedVc<PreparedTest>) -> Result<V
403403
TypescriptTransformOptions::default().resolved_cell(),
404404
),
405405
import_externals: true,
406+
enable_exports_info_inlining: true,
406407
..Default::default()
407408
},
408409
environment: Some(env),
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { foo, usage } from './module.js'
2+
3+
it('should expose __webpack_exports_info__ in tests', () => {
4+
expect(foo).toBe('foo')
5+
6+
expect(usage.foo.used).toBe(true)
7+
expect(usage.bar.used).toBe(false)
8+
expect(usage.usage.used).toBe(true)
9+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const foo = 'foo'
2+
export const bar = 'bar'
3+
4+
export const usage = __webpack_exports_info__

turbopack/crates/turbopack-tests/tests/snapshot.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ async fn run_test_operation(resource: RcStr) -> Result<Vc<FileSystemPath>> {
355355
..Default::default()
356356
})),
357357
ignore_dynamic_requests: true,
358+
enable_exports_info_inlining: true,
358359
..Default::default()
359360
},
360361
environment: Some(env),

turbopack/crates/turbopack/src/module_options/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ impl ModuleOptions {
208208
import_externals,
209209
esm_url_rewrite_behavior,
210210
enable_typeof_window_inlining,
211+
enable_exports_info_inlining,
211212
source_maps: ecmascript_source_maps,
212213
inline_helpers,
213214
..
@@ -283,6 +284,7 @@ impl ModuleOptions {
283284
keep_last_successful_parse,
284285
analyze_mode,
285286
enable_typeof_window_inlining,
287+
enable_exports_info_inlining,
286288
inline_helpers,
287289
..Default::default()
288290
};

turbopack/crates/turbopack/src/module_options/module_options_context.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ pub struct EcmascriptOptionsContext {
239239
/// Specifies how Source Maps are handled.
240240
pub source_maps: SourceMapsType,
241241

242+
/// Whether to allow accessing exports info via `__webpack_exports_info__`.
243+
pub enable_exports_info_inlining: bool,
244+
242245
// TODO should this be a part of Environment instead?
243246
pub inline_helpers: bool,
244247

0 commit comments

Comments
 (0)