swc-project / swc

Rust-based platform for the Web
https://swc.rs
Apache License 2.0
31k stars 1.21k forks source link

side-effect-free decorators #8233

Open billowz opened 10 months ago

billowz commented 10 months ago

Describe the feature

1. side-effect-free decorators

Currently, the decorator code generated by swc has side effects, which will cause classes with decorators to Unable to support Tree shaking (I use swc with rollup). In fact, our decorators are side-effect-free in most cases. Is it possible to add decorator's side effect configuration in swc?

For example, I have this source code:

@Component
export class Dialog<P> extends Vue {
    @Prop({ required: true })
    params!: P;
}

compiled code:

export let Dialog = class Dialog extends Vue {
};
_ts_decorate([
    Prop({
        required: true
    })
], Dialog.prototype, "params", void 0);
Dialog = _ts_decorate([
    Component
], Dialog);

side-effect-free code:

let Dialog = /* @__PURE__ */ (function(){
    let Dialog = class Dialog extends Vue {
    };
    _ts_decorate([
        Prop({
            required: true
        })
    ], Dialog.prototype, "params", void 0);
    Dialog = _ts_decorate([
        Component
    ], Dialog);
    return Dialog
})()

2. wasm plugin for decorators

I try to write a wasm plugin that converts decorated classes into closures with /* @__PURE__ */ annotations. But I can't get the decorators of the class in the plugin (the decorators are transformed before the wasm plugin is executed, it only works properly when disableBuiltinTransformsForInternalTesting = true). Is there any way to make it work? The following is the source code of the plugin:

pub struct TransformVisitor<C>
where
    C: Comments,
{
    config: Config,
    comments: Option<C>,
}

impl<C> TransformVisitor<C>
where
    C: Comments,
{
    fn check_decorators(&mut self, decorators: &Vec<Decorator>, out: &mut usize) -> bool {
        let len = decorators.len();
        if len == 0 {
            return true;
        }
        *out += len;

        // TODO check side-effect decorators by config
        return true;
    }

    fn check_class(&mut self, cls: &Class) -> bool {
        let mut count: usize = 0;
        if !self.check_decorators(&cls.decorators, &mut count) {
            return false;
        }

        for item in cls.body.iter() {
            if let Some(prop) = item.as_class_prop() {
                if !self.check_decorators(&prop.decorators, &mut count) {
                    return false;
                }
            } else if let Some(prop) = item.as_private_prop() {
                if !self.check_decorators(&prop.decorators, &mut count) {
                    return false;
                }
            } else if let Some(method) = item.as_method() {
                if !self.check_decorators(&method.function.decorators, &mut count) {
                    return false;
                }
            } else if let Some(method) = item.as_private_method() {
                if !self.check_decorators(&method.function.decorators, &mut count) {
                    return false;
                }
            } else if let Some(accessor) = item.as_auto_accessor() {
                if !self.check_decorators(&accessor.decorators, &mut count) {
                    return false;
                }
            }
        }
        return count > 0;
    }

    /**
     *  Input:
     *             cls  ident
     *              |     |      
     *            class [name] {}
     *  Output:   const name = (function(){ cls_decl })()
     */
    fn wrap_class_expr(&mut self, cls: &Class, ident: &Ident) -> Box<Expr> {
        let span = &cls.span;

        self.comments.add_pure_comment(span.lo);

        return Box::new(Expr::Call(CallExpr {
            span: *span,
            args: Vec::new(),
            type_args: None,
            callee: Callee::Expr(Box::new(Expr::Paren(ParenExpr {
                span: *span,
                expr: Box::new(Expr::Fn(FnExpr {
                    ident: None,
                    function: Box::new(Function {
                        params: Vec::new(),
                        decorators: Vec::new(),
                        span: *span,
                        body: Some(BlockStmt {
                            span: *span,
                            stmts: vec![
                                Stmt::Decl(Decl::Class(ClassDecl {
                                    ident: ident.clone(),
                                    declare: false,
                                    class: Box::new(cls.clone()),
                                })),
                                Stmt::Return(ReturnStmt {
                                    span: *span,
                                    arg: Some(Box::new(Expr::Ident(ident.clone()))),
                                }),
                            ],
                        }),
                        is_generator: false,
                        is_async: false,
                        type_params: None,
                        return_type: None,
                    }),
                })),
            }))),
        }));
    }

    /**
     *  Input:
     *            cls_decl ident
     *              |      |      
     *              class  name {}
     *  Output:     const name = (function(){ cls_decl })()
     */
    fn wrap_var_class(&mut self, cls_decl: &ClassDecl) -> Box<VarDecl> {
        let class = &cls_decl.class;
        let ident = &cls_decl.ident;
        return Box::new(VarDecl {
            span: DUMMY_SP,
            kind: VarDeclKind::Const,
            declare: false,
            decls: vec![VarDeclarator {
                span: DUMMY_SP,
                definite: false,
                name: Pat::Ident(BindingIdent {
                    id: ident.clone(),
                    type_ann: None,
                }),
                init: Some(self.wrap_class_expr(class, ident)),
            }],
        });
    }

    fn create_class_ident(&mut self, span: &Span) -> Ident {
        return Ident {
            span: span.clone(),
            sym: Atom::new("Anonymous"),
            optional: false,
        };
    }
}

impl<C> VisitMut for TransformVisitor<C>
where
    C: Comments,
{
    fn visit_mut_var_declarator(&mut self, var: &mut VarDeclarator) {
        if let Some(cls_expr) = var.init.as_ref().and_then(|expr| expr.as_class()) {
            if self.check_class(&cls_expr.class) {
                /*
                 *  Input:
                 *           var_decl   cls_expr   ident
                 *              |          |         |
                 *              var name = @x class [name] {}
                 *  Output:     var name = (function(){ cls_expr })()
                 *
                 *  Input:
                 *               var_decl    cls_expr  ident
                 *                  |          |         |
                 *           export var name = @x class [name] {}
                 *  Output:  export var name = (function(){ cls_expr })()
                 */
                let ident = &cls_expr
                    .ident
                    .clone()
                    .or(var.name.as_ident().and_then(|id| Some(id.id.clone())))
                    .or(Some(self.create_class_ident(&cls_expr.span())))
                    .unwrap();

                var.init = Some(self.wrap_class_expr(&cls_expr.class, ident));
            }
        }
    }

    fn visit_mut_module_item(&mut self, item: &mut ModuleItem) {
        if let Some(decl) = item.as_mut_module_decl() {
            if let Some(exp_decl) = decl.as_export_default_decl() {
                if let Some(cls_expr) = exp_decl.decl.as_class() {
                    if self.check_class(&cls_expr.class) {
                        /*
                         *  Input:
                         *          exp_decl     cls_expr  ident
                         *            |              |      |
                         *            export default class [name] {}
                         *  Output:   export default (function(){ cls_expr })()
                         */
                        let ident = &cls_expr
                            .ident
                            .clone()
                            .or(Some(self.create_class_ident(&cls_expr.span())))
                            .unwrap();

                        *decl = ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
                            span: exp_decl.span().clone(),
                            expr: self.wrap_class_expr(&cls_expr.class, ident),
                        });
                    }
                }
            } else if let Some(exp_decl) = decl.as_export_decl() {
                if let Some(cls_decl) = exp_decl.decl.as_class() {
                    if self.check_class(&cls_decl.class) {
                        /*
                         *  Input:
                         *           exp_decl cls_decl ident
                         *                |      |      |
                         *                export class  name {}
                         *  Output:       export const name = (function(){ cls_decl })()
                         */
                        *decl = ModuleDecl::ExportDecl(ExportDecl {
                            span: exp_decl.span().clone(),
                            decl: Decl::Var(self.wrap_var_class(cls_decl)),
                        });
                    }
                } else {
                    item.visit_mut_children_with(self);
                }
            }
        } else if let Some(stmt) = item.as_stmt() {
            if let Some(decl) = stmt.as_decl() {
                if let Some(cls_decl) = decl.as_class() {
                    if self.check_class(&cls_decl.class) {
                        /*
                         *  Input:
                         *           cls_decl  ident
                         *               |      |
                         *               class  name {}
                         *  Output:      const name = (function(){ cls_decl })()
                         */
                        *item =
                            ModuleItem::Stmt(Stmt::Decl(Decl::Var(self.wrap_var_class(cls_decl))));
                    }
                } else {
                    item.visit_mut_children_with(self);
                }
            }
        }
    }

    fn visit_mut_block_stmt(&mut self, _stmt: &mut BlockStmt) {}
}

Babel plugin or link to the feature description

No response

Additional context

No response

kdy1 commented 10 months ago

No. Wasm plugins are invoked after

So it can never access decorators.

billowz commented 10 months ago

@kdy1 Is it possible to add decorator's side effect option in swc?