mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
[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:
@@ -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 {
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used-value'
|
||||
exports.unused = 'unused-value'
|
||||
+15
@@ -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')
|
||||
})
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used-value'
|
||||
exports.unused = 'unused-value'
|
||||
+9
@@ -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')
|
||||
})
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used-value'
|
||||
exports.unused = 'unused-value'
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used2-value'
|
||||
exports.unused = 'unused2-value'
|
||||
+5
@@ -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)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used-value'
|
||||
exports.unused = 'unused-value'
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"removeUnusedImports": false,
|
||||
"removeUnusedExports": true,
|
||||
"cjsTreeShaking": true,
|
||||
"treeShakingMode": "reexports-only"
|
||||
}
|
||||
+5
@@ -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
|
||||
+17
@@ -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
|
||||
+7
@@ -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"}}]
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sections": []
|
||||
}
|
||||
+7
@@ -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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used-value'
|
||||
exports.unused = 'unused-value'
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"removeUnusedImports": false,
|
||||
"removeUnusedExports": true,
|
||||
"cjsTreeShaking": true,
|
||||
"treeShakingMode": "reexports-only"
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sections": []
|
||||
}
|
||||
+5
@@ -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
|
||||
+17
@@ -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
|
||||
+7
@@ -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"}}]
|
||||
}
|
||||
+4
@@ -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)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used-value'
|
||||
exports.unused = 'unused-value'
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"removeUnusedImports": false,
|
||||
"removeUnusedExports": true,
|
||||
"cjsTreeShaking": true,
|
||||
"treeShakingMode": "reexports-only"
|
||||
}
|
||||
+5
@@ -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
|
||||
+15
@@ -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
|
||||
+7
@@ -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"}}]
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sections": []
|
||||
}
|
||||
+5
@@ -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)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used-value'
|
||||
exports.unused = 'unused-value'
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"removeUnusedImports": false,
|
||||
"removeUnusedExports": true,
|
||||
"cjsTreeShaking": true,
|
||||
"treeShakingMode": "reexports-only"
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sections": []
|
||||
}
|
||||
+5
@@ -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
|
||||
+16
@@ -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
|
||||
+7
@@ -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"}}]
|
||||
}
|
||||
+3
@@ -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)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
exports.used = 'used-value'
|
||||
exports.unused = 'unused-value'
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"removeUnusedImports": false,
|
||||
"removeUnusedExports": true,
|
||||
"cjsTreeShaking": true,
|
||||
"treeShakingMode": "reexports-only"
|
||||
}
|
||||
+5
@@ -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
|
||||
+15
@@ -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
|
||||
+7
@@ -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"}}]
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sections": []
|
||||
}
|
||||
Reference in New Issue
Block a user