Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use swc_atoms::Atom;
use swc_experimental_ecma_ast::{
AssignExpr, AssignOp, ClassMember, DefaultDecl, Expr, GetSpan, Ident, MemberExpr, ModuleDecl,
Pat, Program, Span, ThisExpr, VarDeclarator,
Pat, Program, Prop, PropOrSpread, Span, ThisExpr, VarDeclarator,
};

use super::state::{
Expand All @@ -27,7 +27,7 @@ use crate::{
},
visitors::{
ExportedVariableInfo, JavascriptParser, Statement, TagInfoData, VariableDeclaration,
scope_info::VariableInfoFlags,
VariableDeclarationKind, scope_info::VariableInfoFlags,
},
};

Expand All @@ -44,6 +44,20 @@ fn class_member_is_static(member: &ClassMember<'_>) -> bool {
}
}

fn object_has_deferred_property(expr: &Expr<'_>) -> bool {
expr.as_object().is_some_and(|object| {
object.props.iter().any(|property| {
let PropOrSpread::Prop(property) = property else {
return false;
};
let Prop::KeyValue(property) = &**property else {
return false;
};
matches!(&property.value, Expr::Arrow(_) | Expr::Fn(_))
})
})
}

#[derive(Debug)]
pub struct InnerGraphParserPlugin {
analyze_pure_annotation: bool,
Expand Down Expand Up @@ -572,6 +586,15 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for InnerGraphParserPlugin {
// Webpack using estree types, which treats all `export default ...` as ExportDefaultDeclaration type
// https://github.com/estree/estree/blob/master/es2015.md#exportdefaultdeclaration
// but SWC using ExportDefaultExpr to represent `export default 1`
if let ModuleDecl::ExportDefaultExpr(default_expr) = export_decl
&& object_has_deferred_property(&default_expr.expr)
{
let variable = Self::tag_top_level_symbol(parser, &DEFAULT_STAR_JS_WORD);
parser
.inner_graph
.add_object_literal(default_expr.expr.span(), variable);
}

let mut callees = vec![];
if let ModuleDecl::ExportDefaultExpr(default_expr) = export_decl
&& is_pure_expression(
Expand Down Expand Up @@ -610,9 +633,15 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for InnerGraphParserPlugin {
&self,
parser: &mut crate::visitors::JavascriptParser,
decl: &VarDeclarator,
_stmt: VariableDeclaration<'_>,
stmt: VariableDeclaration<'_>,
) -> Option<bool> {
if !parser.inner_graph.is_enabled() || !parser.is_top_level_scope() {
if !parser.inner_graph.is_enabled()
|| !parser.is_top_level_scope()
|| matches!(
stmt.kind(),
VariableDeclarationKind::Using | VariableDeclarationKind::AwaitUsing
)
{
return None;
}

Expand Down Expand Up @@ -660,6 +689,11 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for InnerGraphParserPlugin {
parser.inner_graph.pure_declarators.insert(decl.span());
}
}

if object_has_deferred_property(init) {
Comment thread
LingyuCoder marked this conversation as resolved.
let symbol = Self::tag_top_level_symbol(parser, &name);
parser.inner_graph.add_object_literal(init.span(), symbol);
Comment thread
LingyuCoder marked this conversation as resolved.
}
}

None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub(crate) struct InnerGraphState {
pub(super) class_with_top_level_symbol: HashMap<Span, TopLevelSymbol>,
pub(super) decl_with_top_level_symbol: HashMap<Span, TopLevelSymbol>,
pub(super) pure_declarators: HashSet<Span>,
object_literal_symbols: Option<Box<HashMap<Span, TopLevelSymbol>>>,
}

impl InnerGraphState {
Expand Down Expand Up @@ -145,6 +146,24 @@ impl InnerGraphState {
}
}

pub(crate) fn add_object_literal(&mut self, span: Span, symbol: TopLevelSymbol) {
self
.object_literal_symbols
.get_or_insert_with(Default::default)
.insert(span, symbol);
}

pub(crate) fn get_object_literal_symbol(&self, span: &Span) -> Option<TopLevelSymbol> {
if !self.is_enabled() {
return None;
}
self
.object_literal_symbols
.as_ref()
.and_then(|symbols| symbols.get(span))
.copied()
}

pub(crate) fn add_usage(&mut self, symbol: TopLevelSymbol, usage: InnerGraphMapUsage) {
if !self.is_enabled() {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ use crate::{
dependency::DependencyBranchGuard,
parser_plugin::{
CREATE_REQUIRE_EVALUATED_TAG, CREATE_REQUIRE_SPECIFIER_TAG, CREATED_REQUIRE_IDENTIFIER_TAG,
CreatedRequireTagData, JavascriptParserPlugin, is_create_require_namespace_member,
CreatedRequireTagData, JavascriptParserPlugin, inner_graph::state::TopLevelSymbol,
is_create_require_namespace_member,
},
visitors::{
AtomMembers, ExportedVariableInfo, ExprRef, VariableDeclaration, VariableInfo,
Expand Down Expand Up @@ -967,7 +968,16 @@ impl JavascriptParser<'_> {
}

fn walk_object_expression(&mut self, expr: &ObjectLit) {
let object_symbol = self.inner_graph.get_object_literal_symbol(&expr.span());
for prop in &expr.props {
if let Some(object_symbol) = object_symbol
&& let PropOrSpread::Prop(prop) = prop
&& let Prop::KeyValue(kv) = &**prop
&& matches!(&kv.value, Expr::Arrow(_) | Expr::Fn(_))
{
self.walk_key_value_prop_with_owner(kv, object_symbol);
continue;
}
self.walk_property_or_spread(prop);
}
}
Expand All @@ -987,6 +997,19 @@ impl JavascriptParser<'_> {
self.walk_expression(&kv.value);
}

fn walk_key_value_prop_with_owner(&mut self, kv: &KeyValueProp, object_symbol: TopLevelSymbol) {
if kv.key.is_computed() {
// Computed keys execute while the object is created, outside the deferred property value.
self.walk_prop_name(&kv.key);
}
let previous_top_level_symbol = self.inner_graph.get_top_level_symbol();
self.inner_graph.set_top_level_symbol(Some(object_symbol));
self.walk_expression(&kv.value);
self
.inner_graph
.set_top_level_symbol(previous_top_level_symbol);
}

fn walk_getter_prop(&mut self, getter: &GetterProp) {
self.walk_prop_name(&getter.key);
let was_top_level = self.top_level_scope;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = "resource disposed";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import fs from "fs";
import path from "path";
import { disposalImport } from "./module";

it("should keep dynamic imports used by implicit resource disposal", async () => {
const disposed = await disposalImport;
expect(disposed.value).toBe("resource disposed");
expect(fs.existsSync(path.join(__dirname, "dispose.js"))).toBe(true);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export let disposalImport;

using resource = {
[Symbol.dispose]: () => {
disposalImport = import(
/* webpackChunkName: "dispose" */ "./dispose"
);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** @type {import("@rspack/core").Configuration} */
module.exports = {
target: 'node',
output: {
chunkFilename: '[name].js',
},
optimization: {
innerGraph: true,
minimize: false,
providedExports: true,
sideEffects: true,
usedExports: true,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const {
supportsUsing,
} = require("@rspack/test-tools/helper/legacy/supportsUsing");

module.exports = () => supportsUsing();
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = "unused default dynamic import";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { effects } from "./module";

export default {
id: effects.push("unused default"),
loader: () =>
import(
/* webpackChunkName: "default-unused" */ "./default-unused-lazy"
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = "used default dynamic import";
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { effects } from "./module";

export default {
id: effects.push("used default"),
loader: () =>
import(/* webpackChunkName: "default-used" */ "./default-used-lazy"),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = "eager dynamic import";
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import fs from "fs";
import path from "path";
import { effects, live, usedFeature } from "./module";
import "./default-unused";
import usedDefaultFeature from "./default-used";

it("should omit dynamic imports in unused object property functions", async () => {
expect(live).toBe("live");
expect(effects).toEqual([
"unused arrow",
"unused function",
"unused nested loader",
"used loader",
"eager import",
"unused default",
"used default",
]);

const used = await usedFeature.loader();
expect(used.value).toBe("used dynamic import");
const usedDefault = await usedDefaultFeature.loader();
expect(usedDefault.value).toBe("used default dynamic import");

expect(fs.existsSync(path.join(__dirname, "unused.js"))).toBe(false);
expect(fs.existsSync(path.join(__dirname, "unused-nested.js"))).toBe(false);
expect(fs.existsSync(path.join(__dirname, "default-unused.js"))).toBe(false);
expect(fs.existsSync(path.join(__dirname, "used.js"))).toBe(true);
expect(fs.existsSync(path.join(__dirname, "default-used.js"))).toBe(true);
expect(fs.existsSync(path.join(__dirname, "eager.js"))).toBe(true);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const effects = [];

function createId(id) {
effects.push(id);
return id;
}

export const unusedArrowFeature = {
id: createId("unused arrow"),
loader: () => import(/* webpackChunkName: "unused" */ "./unused"),
};

export const unusedFunctionFeature = {
id: createId("unused function"),
loader: function () {
return import(/* webpackChunkName: "unused" */ "./unused");
},
};

export const unusedNestedFeature = {
id: createId("unused nested loader"),
loader: () => () =>
import(/* webpackChunkName: "unused-nested" */ "./unused"),
};

export const usedFeature = {
id: createId("used loader"),
loader: () => import(/* webpackChunkName: "used" */ "./used"),
};

export const unusedEagerFeature = {
id: createId("eager import"),
value: import(/* webpackChunkName: "eager" */ "./eager"),
};

export const live = "live";
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** @type {import("@rspack/core").Configuration} */
module.exports = {
target: 'node',
output: {
chunkFilename: '[name].js',
},
optimization: {
innerGraph: true,
minimize: false,
providedExports: true,
sideEffects: true,
usedExports: true,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = "unused dynamic import";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = "used dynamic import";
Loading