Skip to content

Commit

Permalink
feat(next-swc): Add a transform adding function names (#68056)
Browse files Browse the repository at this point in the history
### What?

Implements a transform that adds readable names to anonymous function
expressions in react component functions.

Adding `{ debugFunctionName: true }` to swcOptions will do the trick.

### Why?

x-ref: https://vercel.slack.com/archives/C04TR3U872A/p1721702161121619


### How?
  • Loading branch information
kdy1 authored Jul 24, 2024
1 parent 24a811f commit af6aa78
Show file tree
Hide file tree
Showing 19 changed files with 323 additions and 0 deletions.
4 changes: 4 additions & 0 deletions crates/next-custom-transforms/src/chain_transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ pub struct TransformOptions {

#[serde(default)]
pub optimize_server_react: Option<crate::transforms::optimize_server_react::Config>,

#[serde(default)]
pub debug_function_name: bool,
}

pub fn custom_before_pass<'a, C>(
Expand Down Expand Up @@ -297,6 +300,7 @@ where
},
None => Either::Right(noop()),
},
Optional::new(crate::transforms::debug_fn_name::debug_fn_name(), opts.debug_function_name),
as_folder(crate::transforms::pure::pure_magic(comments)),
)
}
Expand Down
185 changes: 185 additions & 0 deletions crates/next-custom-transforms/src/transforms/debug_fn_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use std::fmt::Write;

use swc_core::{
atoms::Atom,
common::{util::take::Take, DUMMY_SP},
ecma::{
ast::{
CallExpr, Callee, ExportDefaultExpr, Expr, FnDecl, FnExpr, KeyValueProp, MemberProp,
ObjectLit, PropOrSpread, VarDeclarator,
},
utils::ExprFactory,
visit::{as_folder, Fold, VisitMut, VisitMutWith},
},
};

pub fn debug_fn_name() -> impl VisitMut + Fold {
as_folder(DebugFnName::default())
}

#[derive(Default)]
struct DebugFnName {
path: String,
in_target: bool,
in_var_target: bool,
in_default_export: bool,
}

impl VisitMut for DebugFnName {
fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
if self.in_var_target || (self.path.is_empty() && !self.in_default_export) {
n.visit_mut_children_with(self);
return;
}

if let Some(target) = is_target_callee(&n.callee) {
let old_in_target = self.in_target;
self.in_target = true;
let orig_len = self.path.len();
if !self.path.is_empty() {
self.path.push('.');
}
self.path.push_str(&target);

n.visit_mut_children_with(self);

self.path.truncate(orig_len);
self.in_target = old_in_target;
} else {
n.visit_mut_children_with(self);
}
}

fn visit_mut_export_default_expr(&mut self, n: &mut ExportDefaultExpr) {
let old_in_default_export = self.in_default_export;
self.in_default_export = true;

n.visit_mut_children_with(self);

self.in_default_export = old_in_default_export;
}

fn visit_mut_expr(&mut self, n: &mut Expr) {
n.visit_mut_children_with(self);

if self.in_target {
match n {
Expr::Arrow(..) | Expr::Fn(FnExpr { ident: None, .. }) => {
// useLayoutEffect(() => ...);
//
// becomes
//
//
// useLayoutEffect({'MyComponent.useLayoutEffect': () =>
// ...}['MyComponent.useLayoutEffect']);

let orig = n.take();
let key = Atom::from(&*self.path);

*n = Expr::Object(ObjectLit {
span: DUMMY_SP,
props: vec![PropOrSpread::Prop(Box::new(
swc_core::ecma::ast::Prop::KeyValue(KeyValueProp {
key: swc_core::ecma::ast::PropName::Str(key.clone().into()),
value: Box::new(orig),
}),
))],
})
.computed_member(key)
.into();
}

_ => {}
}
}
}

fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
let orig_len = self.path.len();
if !self.path.is_empty() {
self.path.push('.');
}
self.path.push_str(n.ident.sym.as_str());

n.visit_mut_children_with(self);

self.path.truncate(orig_len);
}

fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) {
if let Some(Expr::Call(call)) = n.init.as_deref() {
let name = is_target_callee(&call.callee).and_then(|target| {
let name = n.name.as_ident()?;

Some((name.sym.clone(), target))
});

if let Some((name, target)) = name {
let old_in_var_target = self.in_var_target;
self.in_var_target = true;

let old_in_target = self.in_target;
self.in_target = true;
let orig_len = self.path.len();
if !self.path.is_empty() {
self.path.push('.');
}
let _ = write!(self.path, "{target}({name})");

n.visit_mut_children_with(self);

self.path.truncate(orig_len);
self.in_target = old_in_target;
self.in_var_target = old_in_var_target;
return;
}
}

if let Some(Expr::Arrow(..) | Expr::Fn(FnExpr { ident: None, .. }) | Expr::Call(..)) =
n.init.as_deref()
{
let name = n.name.as_ident();

if let Some(name) = name {
let orig_len = self.path.len();
if !self.path.is_empty() {
self.path.push('.');
}
self.path.push_str(name.sym.as_str());

n.visit_mut_children_with(self);

self.path.truncate(orig_len);
return;
}
}

n.visit_mut_children_with(self);
}
}

fn is_target_callee(e: &Callee) -> Option<Atom> {
match e {
Callee::Expr(e) => match &**e {
Expr::Ident(i) => {
if i.sym.starts_with("use") {
Some(i.sym.clone())
} else {
None
}
}
Expr::Member(me) => match &me.prop {
MemberProp::Ident(i) => {
if i.sym.starts_with("use") {
Some(i.sym.clone())
} else {
None
}
}
_ => None,
},
_ => None,
},
_ => None,
}
}
1 change: 1 addition & 0 deletions crates/next-custom-transforms/src/transforms/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod amp_attributes;
pub mod cjs_finder;
pub mod cjs_optimizer;
pub mod debug_fn_name;
pub mod disallow_re_export_all_in_page;
pub mod dynamic;
pub mod fonts;
Expand Down
22 changes: 22 additions & 0 deletions crates/next-custom-transforms/tests/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{
use next_custom_transforms::transforms::{
amp_attributes::amp_attributes,
cjs_optimizer::cjs_optimizer,
debug_fn_name::debug_fn_name,
dynamic::{next_dynamic, NextDynamicMode},
fonts::{next_font_loaders, Config as FontLoaderConfig},
named_import_transform::named_import_transform,
Expand Down Expand Up @@ -630,3 +631,24 @@ fn next_transform_strip_page_exports_fixture_default(output: PathBuf) {

run_stip_page_exports_test(&input, &output, ExportFilter::StripDataExports);
}

#[fixture("tests/fixture/debug-fn-name/**/input.js")]
fn test_debug_name(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");

test_fixture(
syntax(),
&|_| {
let top_level_mark = Mark::fresh(Mark::root());
let unresolved_mark = Mark::fresh(Mark::root());

chain!(
swc_core::ecma::transforms::base::resolver(unresolved_mark, top_level_mark, true),
debug_fn_name()
)
},
&input,
&output,
Default::default(),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const Component = () => {
useLayoutEffect(() => {})
useEffect(() => {})
const onClick = useCallback(() => [])
const computed = useMemo(() => {})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const Component = ()=>{
useLayoutEffect({
"Component.useLayoutEffect": ()=>{}
}["Component.useLayoutEffect"]);
useEffect({
"Component.useEffect": ()=>{}
}["Component.useEffect"]);
const onClick = useCallback({
"Component.useCallback(onClick)": ()=>[]
}["Component.useCallback(onClick)"]);
const computed = useMemo({
"Component.useMemo(computed)": ()=>{}
}["Component.useMemo(computed)"]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function useSomething() {
useLayoutEffect(() => {})
useEffect(() => {})
const onClick = useCallback(() => [])
const computed = useMemo(() => {})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function useSomething() {
useLayoutEffect({
"useSomething.useLayoutEffect": ()=>{}
}["useSomething.useLayoutEffect"]);
useEffect({
"useSomething.useEffect": ()=>{}
}["useSomething.useEffect"]);
const onClick = useCallback({
"useSomething.useCallback(onClick)": ()=>[]
}["useSomething.useCallback(onClick)"]);
const computed = useMemo({
"useSomething.useMemo(computed)": ()=>{}
}["useSomething.useMemo(computed)"]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default () => {
useLayoutEffect(() => {})
useEffect(() => {})
const onClick = useCallback(() => [])
const computed = useMemo(() => {})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default (()=>{
useLayoutEffect({
"useLayoutEffect": ()=>{}
}["useLayoutEffect"]);
useEffect({
"useEffect": ()=>{}
}["useEffect"]);
const onClick = useCallback({
"useCallback(onClick)": ()=>[]
}["useCallback(onClick)"]);
const computed = useMemo({
"useMemo(computed)": ()=>{}
}["useMemo(computed)"]);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const Component = someHoC(() => {
useLayoutEffect(() => {})
useEffect(() => {})
const onClick = useCallback(() => [])
const computed = useMemo(() => {})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const Component = someHoC(()=>{
useLayoutEffect({
"Component.useLayoutEffect": ()=>{}
}["Component.useLayoutEffect"]);
useEffect({
"Component.useEffect": ()=>{}
}["Component.useEffect"]);
const onClick = useCallback({
"Component.useCallback(onClick)": ()=>[]
}["Component.useCallback(onClick)"]);
const computed = useMemo({
"Component.useMemo(computed)": ()=>{}
}["Component.useMemo(computed)"]);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
useMemo(() => {})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
useMemo(()=>{});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function MyComponent() {
useLayoutEffect(() => {})
useEffect(() => {})
const onClick = useCallback(() => [])
const computed = useMemo(() => {})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function MyComponent() {
useLayoutEffect({
"MyComponent.useLayoutEffect": ()=>{}
}["MyComponent.useLayoutEffect"]);
useEffect({
"MyComponent.useEffect": ()=>{}
}["MyComponent.useEffect"]);
const onClick = useCallback({
"MyComponent.useCallback(onClick)": ()=>[]
}["MyComponent.useCallback(onClick)"]);
const computed = useMemo({
"MyComponent.useMemo(computed)": ()=>{}
}["MyComponent.useMemo(computed)"]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function C() {
function handleClick() {}
const onClick = useCallback(handleClick, [])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function C() {
function handleClick() {}
const onClick = useCallback(handleClick, []);
}
1 change: 1 addition & 0 deletions crates/next-custom-transforms/tests/full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ fn test(input: &Path, minify: bool) {
optimize_barrel_exports: None,
optimize_server_react: None,
prefer_esm: false,
debug_function_name: false,
};

let unresolved_mark = Mark::new();
Expand Down

0 comments on commit af6aa78

Please sign in to comment.