[turbopack] Track usage of modules imported with require() (#95717)

This PR introduces a new method `analyze_require_usage` that analyzes
how something imported with `require()` is used. It has two passes: one
catches simple things like `const { a } = require(...)` and
`require(...).foo`, the next one then handles if you import using `const
x = require(...)`.

This then gets populated into `export_usage_info`, this will expand the
set of exports we can drop through
https://github.com/vercel/next.js/pull/95716.
This commit is contained in:
Sam Poder
2026-07-23 23:12:58 -07:00
committed by GitHub
parent 0950e8ae05
commit 1f04d8ff82
47 changed files with 540 additions and 47 deletions
@@ -1,8 +1,10 @@
use rustc_hash::FxHashMap;
use swc_core::{
atoms::Atom,
common::BytePos,
ecma::{ast::*, visit::VisitWithAstPath},
};
use turbopack_core::resolve::ExportUsage;
pub use crate::analyzer::graph::{
effects::{
@@ -18,7 +20,7 @@ use crate::{
mod effects;
mod eval_context;
mod visitor;
pub(crate) mod visitor;
#[derive(Debug)]
pub struct VarGraph<'a> {
@@ -33,6 +35,10 @@ pub struct VarGraph<'a> {
pub effects: Vec<Effect<'a>>,
// Some unconditional codegens, usually for ESM items.
pub code_gens: Vec<CodeGen>,
/// [`ExportUsage`] per `require("…")` call, keyed by call position; absent
/// calls fall back to `ExportUsage::All`.
pub require_usage: FxHashMap<BytePos, ExportUsage>,
}
impl<'a> VarGraph<'a> {
@@ -63,6 +69,7 @@ pub fn create_graph<'a>(
free_var_ids: Default::default(),
effects: Default::default(),
code_gens: Default::default(),
require_usage: Default::default(),
},
eval_context,
state: Default::default(),
@@ -79,6 +86,10 @@ pub fn create_graph<'a>(
analyzer.enable_cjs_exports();
}
if cjs_tree_shaking && analyze_mode.is_code_gen() {
analyzer.enable_require_usage(&eval_context.imports);
}
m.visit_with_ast_path(&mut analyzer, &mut Default::default());
let mut graph = analyzer.data;
@@ -4,9 +4,10 @@ use std::{
};
use bumpalo::boxed::Box as BumpBox;
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use swc_core::{
common::{Span, Spanned, SyntaxContext, pass::AstNodePath},
common::{BytePos, Span, Spanned, SyntaxContext, pass::AstNodePath},
ecma::{
ast::*,
atoms::atom,
@@ -20,7 +21,7 @@ use turbopack_core::resolve::ExportUsage;
use crate::{
AnalyzeMode,
analyzer::{
Bump, BumpVec, ConstantValue, JsValue, WellKnownFunctionKind,
Bump, BumpVec, ConstantValue, ImportMap, JsValue, WellKnownFunctionKind,
cjs_ast::{
as_exports_define_property, define_property_sets_es_module, is_exports_object,
is_global,
@@ -34,7 +35,7 @@ use crate::{
cjs::{CjsExportsDropCodeGen, DroppableCjsExportAssignment},
esm::EsmModuleItem,
},
utils::{AstPathRange, unparen},
utils::{AstPathRange, extract_name_from_member_prop, extract_names_from_object_pat, unparen},
};
enum EarlyReturn<'a> {
@@ -196,6 +197,8 @@ mod analyzer_state {
cjs_exports: Option<CjsExportsCollector>,
cjs_export_target: bool,
cjs_export_value: bool,
/// Tracked `const x = require(...)` namespace bindings
require_bindings: Option<FxHashMap<Id, BytePos>>,
}
impl<'a> Analyzer<'a, '_> {
@@ -559,6 +562,76 @@ mod analyzer_state {
self.state.cjs_exports = Some(CjsExportsCollector::default());
}
/// Enables `require("…")` export-usage narrowing and seed it
/// with information collected by the `ImportMap` visitor.
pub(in crate::analyzer::graph) fn enable_require_usage(&mut self, imports: &ImportMap) {
let cjs_imports = imports.cjs_imports();
self.data.require_usage = cjs_imports.resolved.clone();
for span in cjs_imports.bindings.values() {
self.data
.require_usage
.insert(*span, ExportUsage::PartialNamespaceObject(SmallVec::new()));
}
self.state.require_bindings = Some(cjs_imports.bindings.clone());
}
pub(super) fn is_tracked_require_binding(&self, id: &Id) -> bool {
self.state
.require_bindings
.as_ref()
.is_some_and(|b| b.contains_key(id))
}
/// Records a tracked `const x = require(...)` is used.
pub(super) fn record_require_usage(&mut self, id: &Id, member: Option<RcStr>) {
let Some(span) = self
.state
.require_bindings
.as_ref()
.and_then(|b| b.get(id).copied())
else {
return;
};
let Some(usage) = self.data.require_usage.get_mut(&span) else {
return;
};
match member {
Some(name) => {
if let ExportUsage::PartialNamespaceObject(names) = usage
&& !names.contains(&name)
{
names.push(name);
}
}
None => *usage = ExportUsage::All,
}
}
/// If this is `export const x = require(...)`, mark the whole of `x` as
/// observable.
pub(super) fn escape_exported_require_bindings(&mut self, node: &ExportDecl) {
if self.state.require_bindings.is_none() {
return;
}
let Decl::Var(var) = &node.decl else {
return;
};
for d in &var.decls {
if let Pat::Ident(binding) = &d.name {
let span = self
.state
.require_bindings
.as_ref()
.and_then(|b| b.get(&binding.id.to_id()).copied());
if let Some(span) = span
&& let Some(usage) = self.data.require_usage.get_mut(&span)
{
*usage = ExportUsage::All;
}
}
}
}
pub(super) fn cjs_exports_enabled(&self) -> bool {
self.state.cjs_exports.is_some()
}
@@ -636,6 +709,17 @@ pub fn as_parent_path_with_in<'a>(
path.into_boxed_slice()
}
/// Returns the [`MemberExpr`] where the current node is the object:
/// `<node>.<prop>` or `<node>[<expr>]`.
fn member_access_parent<'r>(
ast_path: &AstNodePath<AstParentNodeRef<'r>>,
) -> Option<&'r MemberExpr> {
match ast_path.len().checked_sub(2).and_then(|i| ast_path.get(i)) {
Some(AstParentNodeRef::MemberExpr(member, MemberExprField::Obj)) => Some(*member),
_ => None,
}
}
/// Extracts export names from usage patterns on a dynamic import.
///
/// Supports two patterns:
@@ -732,38 +816,6 @@ fn extract_names_from_then_callback(call: &CallExpr) -> Option<SmallVec<[RcStr;
}
}
fn extract_name_from_member_prop(prop: &MemberProp) -> Option<SmallVec<[RcStr; 1]>> {
match prop {
MemberProp::Ident(ident) => Some(SmallVec::from_buf([ident.sym.as_str().into()])),
MemberProp::Computed(ComputedPropName {
expr: box Expr::Lit(Lit::Str(s)),
..
}) => s.value.as_str().map(|v| SmallVec::from_buf([v.into()])),
_ => None,
}
}
fn extract_names_from_object_pat(pat: &Pat) -> Option<SmallVec<[RcStr; 1]>> {
let Pat::Object(obj_pat) = pat else {
return None;
};
let mut names = SmallVec::new();
for prop in &obj_pat.props {
match prop {
ObjectPatProp::KeyValue(kv) => match &kv.key {
PropName::Ident(ident) => names.push(ident.sym.as_str().into()),
PropName::Str(s) => names.push(s.value.as_str()?.into()),
_ => return None, // computed key, can't determine statically
},
ObjectPatProp::Assign(assign) => {
names.push(assign.key.sym.as_str().into());
}
ObjectPatProp::Rest(_) => return None, // rest pattern means all exports needed
}
}
Some(names)
}
pub fn as_parent_path_with(
ast_path: &AstNodePath<AstParentNodeRef<'_>>,
additional: AstParentKind,
@@ -2075,10 +2127,25 @@ impl VisitAstPath for Analyzer<'_, '_> {
}
}
let id = ident.to_id();
// How a `const x = require(...)` binding is consumed: a static member read
// `x.foo` observes that export; anything else observes the whole namespace.
if self.is_tracked_require_binding(&id) {
match member_access_parent(ast_path)
.and_then(|m| extract_name_from_member_prop(&m.prop))
{
Some(names) => {
for name in names {
self.record_require_usage(&id, Some(name));
}
}
None => self.record_require_usage(&id, None),
}
}
// Attempt to add import effects.
if let Some((esm_reference_index, export)) =
self.eval_context.imports.get_binding(&ident.to_id())
{
if let Some((esm_reference_index, export)) = self.eval_context.imports.get_binding(&id) {
// Optimization: Look for a MemberExpr to see if we only access a few members from the
// module, add those specific effects instead of depending on the entire module.
//
@@ -2088,8 +2155,7 @@ impl VisitAstPath for Analyzer<'_, '_> {
.eval_context
.imports
.should_import_all(esm_reference_index)
&& let Some(AstParentNodeRef::MemberExpr(member, MemberExprField::Obj)) =
ast_path.get(ast_path.len() - 2)
&& let Some(member) = member_access_parent(ast_path)
&& let Some(prop) = self.eval_context.eval_member_prop(self.arena, &member.prop)
&& let Some(prop_str) = prop.as_str()
{
@@ -2491,6 +2557,7 @@ impl VisitAstPath for Analyzer<'_, '_> {
node: &'ast ExportDecl,
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
) {
self.escape_exported_require_bindings(node);
self.add_esm_module_item(ast_path);
node.visit_children_with_ast_path(self, ast_path);
}
@@ -22,13 +22,17 @@ use swc_core::{
use turbo_frozenmap::FrozenMap;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc};
use turbopack_core::{loader::WebpackLoaderItem, resolve::ImportUsage};
use turbopack_core::{
loader::WebpackLoaderItem,
resolve::{ExportUsage, ImportUsage},
};
use super::{JsValue, ModuleValue, top_level_await::has_top_level_await};
use crate::{
SpecifiedModuleType,
analyzer::{
Bump, ConstantValue, ObjectPart,
cjs_ast::is_global,
graph::{AssignmentScope, AssignmentScopes, EvalContext},
is_unresolved, is_unresolved_id,
},
@@ -38,6 +42,7 @@ use crate::{
esm::{EsmAssetReference, EsmExport, Liveness},
util::{SpecifiedChunkingType, parse_chunking_type_annotation},
},
utils::{extract_name_from_member_prop, extract_names_from_object_pat, unparen},
};
#[turbo_tasks::value]
@@ -376,9 +381,6 @@ pub enum Export {
}
/// The storage for all kinds of imports.
///
/// Note that when it's initialized by calling `analyze`, it only contains ESM
/// import/exports.
#[derive(Default, Debug)]
pub(crate) struct ImportMap {
/// Map from identifier to (index in references, exported symbol)
@@ -430,6 +432,19 @@ pub(crate) struct ImportMap {
/// Map from exported name to local binding id (includes the syntax context).
pub(crate) exports_ids: FxHashMap<RcStr, Id>,
/// CommonJS imports: stores the "resolved" imports (eg. `const { a } = require("m")`)
/// and the generic whole-module imports (eg. `const x = require("m")`).
cjs_imports: CjsImports,
}
#[derive(Default, Debug)]
pub(crate) struct CjsImports {
/// `require("m").foo` or `const { a } = require("m")`
pub(crate) resolved: FxHashMap<BytePos, ExportUsage>,
/// `const x = require("m")`
pub(crate) bindings: FxHashMap<Id, BytePos>,
}
/// Represents a collection of [webpack-style "magic comments"][magic] that override import
@@ -800,6 +815,10 @@ impl ImportMap {
self.full_star_imports.contains(&r.module_path)
}
pub(crate) fn cjs_imports(&self) -> &CjsImports {
&self.cjs_imports
}
}
mod analyzer_state {
@@ -906,6 +925,37 @@ impl Analyzer<'_> {
}
}
}
/// Records how a `const … = require("…")` declarator consumes the call.
fn record_require_usage_var(&mut self, n: &VarDeclarator) {
let Some(init) = &n.init else {
return;
};
let Some(call) = as_require_call(init, self.unresolved_mark) else {
return;
};
match &n.name {
Pat::Ident(binding) => {
self.data
.cjs_imports
.bindings
.insert(binding.id.to_id(), call.span.lo);
}
Pat::Object(_) => {
let usage = match extract_names_from_object_pat(&n.name) {
Some(names) => ExportUsage::PartialNamespaceObject(names),
None => ExportUsage::All,
};
self.data.cjs_imports.resolved.insert(call.span.lo, usage);
}
_ => {
self.data
.cjs_imports
.resolved
.insert(call.span.lo, ExportUsage::All);
}
}
}
}
impl Visit for Analyzer<'_> {
@@ -1273,6 +1323,15 @@ impl Visit for Analyzer<'_> {
}
fn visit_member_expr(&mut self, node: &MemberExpr) {
// `require("…").foo` — the accessed member is the used export.
if let Some(call) = as_require_call(&node.obj, self.unresolved_mark) {
let usage = match extract_name_from_member_prop(&node.prop) {
Some(names) => ExportUsage::PartialNamespaceObject(names),
None => ExportUsage::All,
};
self.data.cjs_imports.resolved.insert(call.span.lo, usage);
}
if matches!(
&node.prop,
MemberProp::Ident(..) | MemberProp::PrivateName(..)
@@ -1391,6 +1450,11 @@ impl Visit for Analyzer<'_> {
node.visit_children_with(self);
}
fn visit_var_declarator(&mut self, node: &VarDeclarator) {
self.record_require_usage_var(node);
node.visit_children_with(self);
}
fn visit_update_expr(&mut self, node: &UpdateExpr) {
if let Some(key) = node.arg.as_ident() {
// node.arg can also be a member expression
@@ -1512,6 +1576,29 @@ fn get_import_symbol_from_export(specifier: &ExportSpecifier) -> ImportedSymbol
}
}
/// If `expr` is a `require("<string literal>")` call, returns it.
fn as_require_call(expr: &Expr, unresolved_mark: Mark) -> Option<&CallExpr> {
let Expr::Call(call) = unparen(expr) else {
return None;
};
let Callee::Expr(callee) = &call.callee else {
return None;
};
let Expr::Ident(f) = &**callee else {
return None;
};
if !is_global(f, "require", unresolved_mark) {
return None;
}
let [arg] = &call.args[..] else {
return None;
};
if arg.spread.is_some() || !matches!(unparen(&arg.expr), Expr::Lit(Lit::Str(_))) {
return None;
}
Some(call)
}
#[cfg(test)]
mod tests {
use swc_core::{atoms::Atom, common::DUMMY_SP};
@@ -18,7 +18,10 @@ use turbopack_core::{
module::Module,
reference::ModuleReference,
reference_type::CommonJsReferenceSubType,
resolve::{ModuleResolveResult, ResolveErrorMode, origin::ResolveOrigin, parse::Request},
resolve::{
BindingUsage, ExportUsage, ImportUsage, ModuleResolveResult, ResolveErrorMode,
origin::ResolveOrigin, parse::Request,
},
};
use turbopack_resolve::ecmascript::cjs_resolve;
@@ -98,6 +101,7 @@ pub struct CjsRequireAssetReference {
error_mode: ResolveErrorMode,
chunking_type_attribute: Option<SpecifiedChunkingType>,
resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
usage: ExportUsage,
}
impl CjsRequireAssetReference {
@@ -108,6 +112,7 @@ impl CjsRequireAssetReference {
error_mode: ResolveErrorMode,
chunking_type_attribute: Option<SpecifiedChunkingType>,
resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
usage: ExportUsage,
) -> Self {
CjsRequireAssetReference {
origin,
@@ -116,6 +121,7 @@ impl CjsRequireAssetReference {
error_mode,
chunking_type_attribute,
resolve_override,
usage,
}
}
}
@@ -149,6 +155,13 @@ impl ModuleReference for CjsRequireAssetReference {
)
}
fn binding_usage(&self) -> BindingUsage {
BindingUsage {
import: ImportUsage::TopLevel,
export: self.usage.clone(),
}
}
fn source(&self) -> Option<IssueSource> {
Some(self.issue_source)
}
@@ -834,6 +834,8 @@ async fn analyze_ecmascript_module_internal(
async {
analysis.code_gens.extend(take(&mut var_graph.code_gens));
let effects = take(&mut var_graph.effects);
// How each `require("…")` call's result is used, keyed by call position.
let require_binding_usage = take(&mut var_graph.require_usage);
let compile_time_info_ref = compile_time_info.await?;
let mut analysis_state = AnalysisState {
@@ -1082,6 +1084,11 @@ async fn analyze_ecmascript_module_internal(
.link_value(take(&mut *func), eval_context.imports.get_attributes(span))
.await?;
let call_usage = require_binding_usage
.get(&span.lo)
.cloned()
.unwrap_or(ExportUsage::All);
handle_call(
&ast_path,
span,
@@ -1093,6 +1100,7 @@ async fn analyze_ecmascript_module_internal(
in_try,
new,
eval_context.imports.get_attributes(span),
call_usage,
)
.await?;
}
@@ -1191,6 +1199,9 @@ async fn analyze_ecmascript_module_internal(
in_try,
new,
eval_context.imports.get_attributes(span),
// A member call (`obj.method(...)`) result isn't narrowed
// for require export usage.
ExportUsage::All,
)
.await?;
}
@@ -1531,6 +1542,7 @@ async fn handle_call<'a, G: Fn(BumpVec<'a, Effect<'a>>) + Send + Sync>(
in_try: bool,
new: bool,
attributes: &ImportAttributes,
call_usage: ExportUsage,
) -> Result<()> {
let &AnalysisState {
handler,
@@ -1604,6 +1616,7 @@ async fn handle_call<'a, G: Fn(BumpVec<'a, Effect<'a>>) + Send + Sync>(
collect_affecting_sources,
tracing_only,
attributes,
call_usage.clone(),
)
.await?;
}
@@ -1628,6 +1641,7 @@ async fn handle_call<'a, G: Fn(BumpVec<'a, Effect<'a>>) + Send + Sync>(
collect_affecting_sources,
tracing_only,
attributes,
call_usage,
)
.await?;
}
@@ -1814,6 +1828,7 @@ async fn handle_well_known_function_call<'a, 'l, F, Fut>(
collect_affecting_sources: bool,
tracing_only: bool,
attributes: &ImportAttributes,
call_usage: ExportUsage,
) -> Result<()>
where
'a: 'l,
@@ -2123,6 +2138,7 @@ where
error_mode,
attributes.chunking_type,
resolve_override,
call_usage.clone(),
),
ast_path.to_vec().into(),
);
@@ -2175,6 +2191,7 @@ where
error_mode,
attributes.chunking_type,
None,
call_usage.clone(),
),
ast_path.to_vec().into(),
);
@@ -2,10 +2,11 @@ use std::ops::Deref;
use bincode::{Decode, Encode};
use serde::Serialize;
use smallvec::SmallVec;
use swc_core::{
common::{DUMMY_SP, SyntaxContext},
ecma::{
ast::{Expr, Lit, Str},
ast::{ComputedPropName, Expr, Lit, MemberProp, ObjectPatProp, Pat, PropName, Str},
visit::AstParentKind,
},
};
@@ -28,6 +29,42 @@ pub fn unparen(expr: &Expr) -> &Expr {
expr
}
/// The statically-known export name read by a member property (`obj.foo` /
/// `obj["foo"]`), or `None` for a dynamic/computed access.
pub(crate) fn extract_name_from_member_prop(prop: &MemberProp) -> Option<SmallVec<[RcStr; 1]>> {
match prop {
MemberProp::Ident(ident) => Some(SmallVec::from_buf([ident.sym.as_str().into()])),
MemberProp::Computed(ComputedPropName {
expr: box Expr::Lit(Lit::Str(s)),
..
}) => s.value.as_str().map(|v| SmallVec::from_buf([v.into()])),
_ => None,
}
}
/// The statically-known export names bound by an object destructuring pattern
/// (`const { a, b } = …`), or `None` for a rest element or computed key.
pub(crate) fn extract_names_from_object_pat(pat: &Pat) -> Option<SmallVec<[RcStr; 1]>> {
let Pat::Object(obj_pat) = pat else {
return None;
};
let mut names = SmallVec::new();
for prop in &obj_pat.props {
match prop {
ObjectPatProp::KeyValue(kv) => match &kv.key {
PropName::Ident(ident) => names.push(ident.sym.as_str().into()),
PropName::Str(s) => names.push(s.value.as_str()?.into()),
_ => return None, // computed key, can't determine statically
},
ObjectPatProp::Assign(assign) => {
names.push(assign.key.sym.as_str().into());
}
ObjectPatProp::Rest(_) => return None, // rest pattern means all exports needed
}
}
Some(names)
}
/// Converts a js-value into a Pattern for matching resources.
pub fn js_value_to_pattern(value: &JsValue<'_>) -> Pattern {
match value {
@@ -0,0 +1,2 @@
exports.used = 'used-value'
exports.unused = 'unused-value'
@@ -0,0 +1,15 @@
const lib = require('./lib.js')
it('narrows a require binding used only via members', () => {
expect(lib.used).toBe('used-value')
})
// `escaped` is used wholesale, so every export must remain reachable — if the
// escape analysis missed this, `escaped.unused` would have been dropped.
const escaped = require('./escaped.js')
globalThis.__cjs_escaped = escaped
it('keeps all exports when a require binding escapes', () => {
expect(globalThis.__cjs_escaped.used).toBe('used-value')
expect(globalThis.__cjs_escaped.unused).toBe('unused-value')
})
@@ -0,0 +1,2 @@
exports.used = 'used-value'
exports.unused = 'unused-value'
@@ -0,0 +1,9 @@
const { used } = require('./lib.js')
it('reads a destructured CommonJS export (siblings narrowed away)', () => {
expect(used).toBe('used-value')
})
it('reads a member-accessed CommonJS export (siblings narrowed away)', () => {
expect(require('./lib2.js').used).toBe('used2-value')
})
@@ -0,0 +1,2 @@
exports.used = 'used-value'
exports.unused = 'unused-value'
@@ -0,0 +1,2 @@
exports.used = 'used2-value'
exports.unused = 'unused2-value'
@@ -0,0 +1,5 @@
// `require` is aliased, so the require-usage analysis can't recognize the call.
// No usage is recorded for it, which must fail open: lib.js keeps *all* its
// exports (including `unused`) rather than being narrowed to just `.used`.
const myRequire = require
console.log(myRequire('./lib.js').used)
@@ -0,0 +1,2 @@
exports.used = 'used-value'
exports.unused = 'unused-value'
@@ -0,0 +1,6 @@
{
"removeUnusedImports": false,
"removeUnusedExports": true,
"cjsTreeShaking": true,
"treeShakingMode": "reexports-only"
}
@@ -0,0 +1,5 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([
"output/1ece_tests_snapshot_cjs-remove-unused-exports_require-alias_input_index_1964h-n.js",
{"otherChunks":["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-alias_input_1le4zip._.js"],"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-alias/input/index.js [test] (ecmascript)"]}
]);
// Dummy runtime
@@ -0,0 +1,17 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push(["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-alias_input_1le4zip._.js",
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-alias/input/index.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
// `require` is aliased, so the require-usage analysis can't recognize the call.
// No usage is recorded for it, which must fail open: lib.js keeps *all* its
// exports (including `unused`) rather than being narrowed to just `.used`.
const myRequire = /*TURBOPACK member replacement*/ __turbopack_context__.t;
console.log(__turbopack_context__.r("[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-alias/input/lib.js [test] (ecmascript)").used);
}),
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-alias/input/lib.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
exports.used = 'used-value';
exports.unused = 'unused-value';
}),
]);
//# sourceMappingURL=1jsg_tests_snapshot_cjs-remove-unused-exports_require-alias_input_1le4zip._.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sections": [
{"offset": {"line": 3, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-alias/input/index.js"],"sourcesContent":["// `require` is aliased, so the require-usage analysis can't recognize the call.\n// No usage is recorded for it, which must fail open: lib.js keeps *all* its\n// exports (including `unused`) rather than being narrowed to just `.used`.\nconst myRequire = require\nconsole.log(myRequire('./lib.js').used)\n"],"names":["myRequire","console","log","used"],"mappings":"AAAA,gFAAgF;AAChF,4EAA4E;AAC5E,2EAA2E;AAC3E,MAAMA;AACNC,QAAQC,GAAG,CAAC,8JAAsBC,IAAI"}},
{"offset": {"line": 11, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-alias/input/lib.js"],"sourcesContent":["exports.used = 'used-value'\nexports.unused = 'unused-value'\n"],"names":["exports","used","unused"],"mappings":"AAAAA,QAAQC,IAAI,GAAG;AACfD,QAAQE,MAAM,GAAG"}}]
}
@@ -0,0 +1,7 @@
const lib = require('./lib.js')
console.log(lib.used)
// `lib` also escapes wholesale, so the whole namespace is observable and
// nothing may be dropped.
globalThis.leaked = lib
@@ -0,0 +1,2 @@
exports.used = 'used-value'
exports.unused = 'unused-value'
@@ -0,0 +1,6 @@
{
"removeUnusedImports": false,
"removeUnusedExports": true,
"cjsTreeShaking": true,
"treeShakingMode": "reexports-only"
}
@@ -0,0 +1,5 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([
"output/17f0_snapshot_cjs-remove-unused-exports_require-binding-escape_input_index_0rhscqt.js",
{"otherChunks":["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-binding-escape_input_0cyfb_1._.js"],"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding-escape/input/index.js [test] (ecmascript)"]}
]);
// Dummy runtime
@@ -0,0 +1,17 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push(["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-binding-escape_input_0cyfb_1._.js",
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding-escape/input/index.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
const lib = __turbopack_context__.r("[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding-escape/input/lib.js [test] (ecmascript)");
console.log(lib.used);
// `lib` also escapes wholesale, so the whole namespace is observable and
// nothing may be dropped.
globalThis.leaked = lib;
}),
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding-escape/input/lib.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
exports.used = 'used-value';
exports.unused = 'unused-value';
}),
]);
//# sourceMappingURL=1jsg_tests_snapshot_cjs-remove-unused-exports_require-binding-escape_input_0cyfb_1._.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sections": [
{"offset": {"line": 3, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding-escape/input/index.js"],"sourcesContent":["const lib = require('./lib.js')\n\nconsole.log(lib.used)\n\n// `lib` also escapes wholesale, so the whole namespace is observable and\n// nothing may be dropped.\nglobalThis.leaked = lib\n"],"names":["lib","console","log","used","globalThis","leaked"],"mappings":"AAAA,MAAMA;AAENC,QAAQC,GAAG,CAACF,IAAIG,IAAI;AAEpB,yEAAyE;AACzE,0BAA0B;AAC1BC,WAAWC,MAAM,GAAGL"}},
{"offset": {"line": 11, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding-escape/input/lib.js"],"sourcesContent":["exports.used = 'used-value'\nexports.unused = 'unused-value'\n"],"names":["exports","used","unused"],"mappings":"AAAAA,QAAQC,IAAI,GAAG;AACfD,QAAQE,MAAM,GAAG"}}]
}
@@ -0,0 +1,4 @@
// `lib` is only ever read via `.used`, so `exports.unused` in lib.js should drop.
const lib = require('./lib.js')
console.log(lib.used)
@@ -0,0 +1,2 @@
exports.used = 'used-value'
exports.unused = 'unused-value'
@@ -0,0 +1,6 @@
{
"removeUnusedImports": false,
"removeUnusedExports": true,
"cjsTreeShaking": true,
"treeShakingMode": "reexports-only"
}
@@ -0,0 +1,5 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([
"output/1ece_tests_snapshot_cjs-remove-unused-exports_require-binding_input_index_1pac7qj.js",
{"otherChunks":["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-binding_input_17m3k8c._.js"],"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding/input/index.js [test] (ecmascript)"]}
]);
// Dummy runtime
@@ -0,0 +1,15 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push(["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-binding_input_17m3k8c._.js",
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding/input/index.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
// `lib` is only ever read via `.used`, so `exports.unused` in lib.js should drop.
const lib = __turbopack_context__.r("[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding/input/lib.js [test] (ecmascript)");
console.log(lib.used);
}),
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding/input/lib.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
exports.used = 'used-value';
'unused-value';
}),
]);
//# sourceMappingURL=1jsg_tests_snapshot_cjs-remove-unused-exports_require-binding_input_17m3k8c._.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sections": [
{"offset": {"line": 3, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding/input/index.js"],"sourcesContent":["// `lib` is only ever read via `.used`, so `exports.unused` in lib.js should drop.\nconst lib = require('./lib.js')\n\nconsole.log(lib.used)\n"],"names":["lib","console","log","used"],"mappings":"AAAA,kFAAkF;AAClF,MAAMA;AAENC,QAAQC,GAAG,CAACF,IAAIG,IAAI"}},
{"offset": {"line": 9, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-binding/input/lib.js"],"sourcesContent":["exports.used = 'used-value'\nexports.unused = 'unused-value'\n"],"names":["exports","used"],"mappings":"AAAAA,QAAQC,IAAI,GAAG;AACE"}}]
}
@@ -0,0 +1,5 @@
// Only `used` is destructured from the require result, so `exports.unused` in
// lib.js should be dropped.
const { used } = require('./lib.js')
console.log(used)
@@ -0,0 +1,2 @@
exports.used = 'used-value'
exports.unused = 'unused-value'
@@ -0,0 +1,6 @@
{
"removeUnusedImports": false,
"removeUnusedExports": true,
"cjsTreeShaking": true,
"treeShakingMode": "reexports-only"
}
@@ -0,0 +1,5 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([
"output/17f0_snapshot_cjs-remove-unused-exports_require-destructure_input_index_0de5rz2.js",
{"otherChunks":["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-destructure_input_12ec97s._.js"],"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-destructure/input/index.js [test] (ecmascript)"]}
]);
// Dummy runtime
@@ -0,0 +1,16 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push(["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-destructure_input_12ec97s._.js",
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-destructure/input/index.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
// Only `used` is destructured from the require result, so `exports.unused` in
// lib.js should be dropped.
const { used } = __turbopack_context__.r("[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-destructure/input/lib.js [test] (ecmascript)");
console.log(used);
}),
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-destructure/input/lib.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
exports.used = 'used-value';
'unused-value';
}),
]);
//# sourceMappingURL=1jsg_tests_snapshot_cjs-remove-unused-exports_require-destructure_input_12ec97s._.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sections": [
{"offset": {"line": 3, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-destructure/input/index.js"],"sourcesContent":["// Only `used` is destructured from the require result, so `exports.unused` in\n// lib.js should be dropped.\nconst { used } = require('./lib.js')\n\nconsole.log(used)\n"],"names":["used","console","log"],"mappings":"AAAA,8EAA8E;AAC9E,4BAA4B;AAC5B,MAAM,EAAEA,IAAI,EAAE;AAEdC,QAAQC,GAAG,CAACF"}},
{"offset": {"line": 10, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-destructure/input/lib.js"],"sourcesContent":["exports.used = 'used-value'\nexports.unused = 'unused-value'\n"],"names":["exports","used"],"mappings":"AAAAA,QAAQC,IAAI,GAAG;AACE"}}]
}
@@ -0,0 +1,3 @@
// Only `.used` is read off the require result, so `exports.unused` in lib.js
// should be dropped.
console.log(require('./lib.js').used)
@@ -0,0 +1,2 @@
exports.used = 'used-value'
exports.unused = 'unused-value'
@@ -0,0 +1,6 @@
{
"removeUnusedImports": false,
"removeUnusedExports": true,
"cjsTreeShaking": true,
"treeShakingMode": "reexports-only"
}
@@ -0,0 +1,5 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push([
"output/1ece_tests_snapshot_cjs-remove-unused-exports_require-member_input_index_1i44r8q.js",
{"otherChunks":["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-member_input_0l-3bsz._.js"],"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-member/input/index.js [test] (ecmascript)"]}
]);
// Dummy runtime
@@ -0,0 +1,15 @@
(globalThis["TURBOPACK"] || (globalThis["TURBOPACK"] = [])).push(["output/1jsg_tests_snapshot_cjs-remove-unused-exports_require-member_input_0l-3bsz._.js",
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-member/input/index.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
// Only `.used` is read off the require result, so `exports.unused` in lib.js
// should be dropped.
console.log(__turbopack_context__.r("[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-member/input/lib.js [test] (ecmascript)").used);
}),
"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-member/input/lib.js [test] (ecmascript)", ((__turbopack_context__, module, exports) => {
exports.used = 'used-value';
'unused-value';
}),
]);
//# sourceMappingURL=1jsg_tests_snapshot_cjs-remove-unused-exports_require-member_input_0l-3bsz._.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sections": [
{"offset": {"line": 3, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-member/input/index.js"],"sourcesContent":["// Only `.used` is read off the require result, so `exports.unused` in lib.js\n// should be dropped.\nconsole.log(require('./lib.js').used)\n"],"names":["console","log","used"],"mappings":"AAAA,6EAA6E;AAC7E,qBAAqB;AACrBA,QAAQC,GAAG,CAAC,+JAAoBC,IAAI"}},
{"offset": {"line": 9, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cjs-remove-unused-exports/require-member/input/lib.js"],"sourcesContent":["exports.used = 'used-value'\nexports.unused = 'unused-value'\n"],"names":["exports","used"],"mappings":"AAAAA,QAAQC,IAAI,GAAG;AACE"}}]
}