From 8a012b071c233adca9c4479a82a81a2086612629 Mon Sep 17 00:00:00 2001 From: mantou132 <709922234@qq.com> Date: Thu, 28 Nov 2024 17:15:53 +0800 Subject: [PATCH] Fix memo & import --- crates/swc-plugin-gem/README.md | 6 + crates/swc-plugin-gem/package.json | 2 +- crates/swc-plugin-gem/src/lib.rs | 1 + crates/swc-plugin-gem/src/visitors/import.rs | 89 ++++++----- crates/swc-plugin-gem/src/visitors/memo.rs | 140 ++++++++++-------- .../fixture/auto-import/members/input.ts | 1 + .../fixture/auto-import/members/output.ts | 3 +- .../tests/fixture/memo/output.ts | 30 ++-- packages/language-service/package.json | 2 +- packages/vscode-gem-plugin/package.json | 6 +- pnpm-lock.yaml | 2 +- 11 files changed, 167 insertions(+), 115 deletions(-) create mode 100644 crates/swc-plugin-gem/README.md diff --git a/crates/swc-plugin-gem/README.md b/crates/swc-plugin-gem/README.md new file mode 100644 index 00000000..82ef50a0 --- /dev/null +++ b/crates/swc-plugin-gem/README.md @@ -0,0 +1,6 @@ +# Gem plugin for SWC + +- auto import +- support [memo getter](https://github.com/tc39/proposal-decorators/issues/509#issuecomment-2226967170) +- support minify style +- resolve full path (for esm) diff --git a/crates/swc-plugin-gem/package.json b/crates/swc-plugin-gem/package.json index 849dc7fc..1d1b70c5 100644 --- a/crates/swc-plugin-gem/package.json +++ b/crates/swc-plugin-gem/package.json @@ -1,6 +1,6 @@ { "name": "swc-plugin-gem", - "version": "0.1.3", + "version": "0.1.4", "description": "swc plugin for Gem", "keywords": [ "swc-plugin", diff --git a/crates/swc-plugin-gem/src/lib.rs b/crates/swc-plugin-gem/src/lib.rs index 1258d04c..a77005d9 100644 --- a/crates/swc-plugin-gem/src/lib.rs +++ b/crates/swc-plugin-gem/src/lib.rs @@ -46,6 +46,7 @@ pub fn process_transform(mut program: Program, data: TransformPluginProgramMetad program.visit_mut_with(&mut ( Optional { + // 只支持原生装饰器或 `runPluginFirst`,不然被转译了,改写不了 enabled: true, visitor: memo_transform(), }, diff --git a/crates/swc-plugin-gem/src/visitors/import.rs b/crates/swc-plugin-gem/src/visitors/import.rs index 7a98c14d..a1e18fe9 100644 --- a/crates/swc-plugin-gem/src/visitors/import.rs +++ b/crates/swc-plugin-gem/src/visitors/import.rs @@ -2,15 +2,16 @@ use indexmap::{IndexMap, IndexSet}; use once_cell::sync::Lazy; use regex::Regex; use serde::Deserialize; -use std::{ - collections::{HashMap, HashSet}, - fs, -}; +use std::{collections::HashMap, fs}; use swc_common::{SyntaxContext, DUMMY_SP}; -use swc_core::ecma::visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; +use swc_core::{ + atoms::Atom, + ecma::visit::{noop_visit_mut_type, VisitMut, VisitMutWith}, +}; use swc_ecma_ast::{ - Callee, Class, ClassDecl, ClassExpr, Decorator, Ident, ImportDecl, ImportNamedSpecifier, - ImportSpecifier, JSXElementName, ModuleDecl, ModuleItem, Str, TaggedTpl, + Callee, Class, ClassDecl, ClassExpr, Decorator, FnDecl, FnExpr, Id, Ident, ImportDecl, + ImportNamedSpecifier, ImportSpecifier, JSXElementName, ModuleDecl, ModuleItem, Str, TaggedTpl, + VarDeclarator, }; static CUSTOM_ELEMENT_REGEX: Lazy = @@ -68,30 +69,20 @@ static GEM_AUTO_IMPORT_CONFIG: Lazy = Lazy::new(|| { } }); -trait IdentString { - fn to_name(&self) -> String; -} - -impl IdentString for Ident { - fn to_name(&self) -> String { - self.sym.as_str().into() - } -} - #[derive(Default)] pub struct TransformVisitor { - used_members: IndexMap, - imported_members: HashSet, + used_members: IndexSet, + defined_members: IndexSet, used_elements: IndexSet, } impl TransformVisitor { fn inset_used_member(&mut self, ident: &Ident) { - let name = ident.to_name(); - // 只保存顶层使用成员,会复用 SyntaxContext - if !self.used_members.contains_key(&name) { - self.used_members.insert(name, ident.ctxt); - } + self.used_members.insert(ident.to_id()); + } + + fn inset_defined_member(&mut self, ident: &Ident) { + self.defined_members.insert(ident.to_id()); } fn visit_mut_class(&mut self, node: &Box) { @@ -99,6 +90,13 @@ impl TransformVisitor { if let Some(ident) = expr.as_ident() { self.inset_used_member(ident); } + // support decorators transform + // class PageHomeElement extends (_GemElement = GemElement) {} + if let Some(assign) = expr.as_assign() { + if let Some(ident) = assign.right.as_ident() { + self.inset_used_member(ident); + } + } } } } @@ -107,7 +105,7 @@ impl VisitMut for TransformVisitor { noop_visit_mut_type!(); fn visit_mut_import_specifier(&mut self, node: &mut ImportSpecifier) { - self.imported_members.insert(node.local().to_name()); + self.inset_defined_member(node.local()); } fn visit_mut_callee(&mut self, node: &mut Callee) { @@ -150,41 +148,66 @@ impl VisitMut for TransformVisitor { node.visit_mut_children_with(self); self.visit_mut_class(&node.class); + self.inset_defined_member(&node.ident); } fn visit_mut_class_expr(&mut self, node: &mut ClassExpr) { node.visit_mut_children_with(self); self.visit_mut_class(&node.class); + if let Some(ident) = &node.ident { + self.inset_defined_member(ident); + } + } + + fn visit_mut_fn_decl(&mut self, node: &mut FnDecl) { + node.visit_mut_children_with(self); + + self.inset_defined_member(&node.ident); + } + + fn visit_mut_fn_expr(&mut self, node: &mut FnExpr) { + node.visit_mut_children_with(self); + + if let Some(ident) = &node.ident { + self.inset_defined_member(ident); + } + } + + fn visit_mut_var_declarator(&mut self, node: &mut VarDeclarator) { + node.visit_mut_children_with(self); + + if let Some(ident) = &node.name.as_ident() { + self.inset_defined_member(&ident); + } } // https://swc.rs/docs/plugin/ecmascript/cheatsheet#inserting-new-nodes // 只处理模块 fn visit_mut_module_items(&mut self, node: &mut Vec) { - // TODO: 收集顶层声明, 防止重复声明 node.visit_mut_children_with(self); let mut out: Vec = vec![]; - let mut available_import: HashMap> = + let mut available_import: HashMap> = HashMap::new(); - for (used_member, ctx) in self.used_members.iter() { - if !self.imported_members.contains(used_member) { - let pkg = GEM_AUTO_IMPORT_CONFIG.member_map.get(used_member); + for id in self.used_members.iter() { + if !self.defined_members.contains(id) { + let pkg = GEM_AUTO_IMPORT_CONFIG.member_map.get(id.0.as_str()); if let Some(pkg) = pkg { let set = available_import .entry(pkg.into()) .or_insert(Default::default()); - set.insert(used_member.into(), (used_member.into(), ctx)); + set.insert(&id.0, (&id.0, &id.1)); } } } for (pkg, set) in available_import { let mut specifiers: Vec = vec![]; - for (member, (_, ctx)) in set { + for (member, (_member_as, ctx)) in set { specifiers.push(ImportSpecifier::Named(ImportNamedSpecifier { - local: Ident::new(member.into(), DUMMY_SP, ctx.clone()), + local: Ident::new(member.clone(), DUMMY_SP, ctx.clone()), span: DUMMY_SP, imported: None, is_type_only: false, diff --git a/crates/swc-plugin-gem/src/visitors/memo.rs b/crates/swc-plugin-gem/src/visitors/memo.rs index 2a179a1d..7e324103 100644 --- a/crates/swc-plugin-gem/src/visitors/memo.rs +++ b/crates/swc-plugin-gem/src/visitors/memo.rs @@ -4,40 +4,11 @@ use swc_core::{ ecma::visit::{noop_visit_mut_type, VisitMut, VisitMutWith}, }; use swc_ecma_ast::{ - AssignExpr, AssignOp, AssignTarget, Callee, Class, ClassDecl, ClassExpr, ClassMember, Expr, - MemberExpr, MemberProp, MethodKind, PrivateMethod, PrivateName, PrivateProp, ReturnStmt, - SimpleAssignTarget, ThisExpr, + AssignExpr, AssignOp, AssignTarget, BlockStmt, Callee, Class, ClassDecl, ClassExpr, + ClassMember, Decorator, Expr, ExprStmt, Function, MemberExpr, MemberProp, MethodKind, + PrivateMethod, PrivateName, PrivateProp, SimpleAssignTarget, Stmt, ThisExpr, }; -#[derive(Default)] -struct TransformVisitor { - private_props: Vec, - current_index: usize, -} - -impl TransformVisitor { - fn current_method(&self) -> &Atom { - self.private_props.get(self.current_index).unwrap() - } - - fn start_visit_mut_class(&mut self) { - self.private_props = Vec::new(); - self.current_index = 0; - } - - fn visit_mut_class(&mut self, node: &mut Box) { - for prop in self.private_props.iter() { - node.body.push(ClassMember::PrivateProp(PrivateProp { - key: PrivateName { - span: DUMMY_SP, - name: prop.as_str().into(), - }, - ..Default::default() - })); - } - } -} - fn is_memo_getter(node: &mut PrivateMethod) -> bool { if node.kind != MethodKind::Getter { return false; @@ -55,57 +26,100 @@ fn is_memo_getter(node: &mut PrivateMethod) -> bool { }) } +#[derive(Default)] +struct TransformVisitor { + private_props: Vec<(Atom, Vec, String)>, +} + +impl TransformVisitor { + fn append_private_field(&mut self, node: &mut Box) { + while let Some((prop, decorators, getter_name)) = self.private_props.pop() { + node.body.push(ClassMember::PrivateMethod(PrivateMethod { + span: DUMMY_SP, + kind: MethodKind::Method, + key: PrivateName { + span: DUMMY_SP, + name: format!("_{}", getter_name).into(), + }, + function: Box::new(Function { + span: DUMMY_SP, + params: vec![], + decorators, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: AssignOp::Assign, + left: AssignTarget::Simple(SimpleAssignTarget::Member( + MemberExpr { + span: DUMMY_SP, + obj: ThisExpr { span: DUMMY_SP }.into(), + prop: MemberProp::PrivateName(PrivateName { + span: DUMMY_SP, + name: prop.clone(), + }), + }, + )), + right: Box::new(Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: ThisExpr { span: DUMMY_SP }.into(), + prop: MemberProp::PrivateName(PrivateName { + span: DUMMY_SP, + name: getter_name.as_str().into(), + }), + })), + })), + })], + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + })); + node.body.push(ClassMember::PrivateProp(PrivateProp { + key: PrivateName { + span: DUMMY_SP, + name: prop.as_str().into(), + }, + ..Default::default() + })); + } + } +} + impl VisitMut for TransformVisitor { noop_visit_mut_type!(); fn visit_mut_class_decl(&mut self, node: &mut ClassDecl) { - self.start_visit_mut_class(); - node.visit_mut_children_with(self); - self.visit_mut_class(&mut node.class); + self.append_private_field(&mut node.class); } fn visit_mut_class_expr(&mut self, node: &mut ClassExpr) { - self.start_visit_mut_class(); - node.visit_mut_children_with(self); - self.visit_mut_class(&mut node.class); + self.append_private_field(&mut node.class); } fn visit_mut_private_method(&mut self, node: &mut PrivateMethod) { if is_memo_getter(node) { - self.current_index = self.private_props.len(); - self.private_props.push(node.key.name.clone()); - - node.visit_mut_children_with(self); + let name = node.key.name.clone(); + let getter_name = format!("_{}", name); node.key = PrivateName { span: DUMMY_SP, - name: format!("_{}", node.key.name).into(), - } - } - } + name: getter_name.clone().into(), + }; + + let mut decorators = Vec::new(); + decorators.append(&mut node.function.decorators); - fn visit_mut_return_stmt(&mut self, node: &mut ReturnStmt) { - // why? 类表达式会直接走到这里来? - if self.private_props.is_empty() { - return; + self.private_props + .push((name.clone(), decorators, getter_name)); } - node.arg = Some(Box::new(Expr::Assign(AssignExpr { - span: DUMMY_SP, - op: AssignOp::Assign, - left: AssignTarget::Simple(SimpleAssignTarget::Member(MemberExpr { - span: DUMMY_SP, - obj: ThisExpr { span: DUMMY_SP }.into(), - prop: MemberProp::PrivateName(PrivateName { - span: DUMMY_SP, - name: self.current_method().clone(), - }), - })), - right: node.arg.clone().unwrap_or(Expr::undefined(DUMMY_SP)), - }))); } } diff --git a/crates/swc-plugin-gem/tests/fixture/auto-import/members/input.ts b/crates/swc-plugin-gem/tests/fixture/auto-import/members/input.ts index 5f639369..80c49b97 100644 --- a/crates/swc-plugin-gem/tests/fixture/auto-import/members/input.ts +++ b/crates/swc-plugin-gem/tests/fixture/auto-import/members/input.ts @@ -13,3 +13,4 @@ class MyElement extends GemElement { return html`
`; } } +class MyElement1 extends (_GemElement = GemElement) {} diff --git a/crates/swc-plugin-gem/tests/fixture/auto-import/members/output.ts b/crates/swc-plugin-gem/tests/fixture/auto-import/members/output.ts index ea60a8e8..be71526e 100644 --- a/crates/swc-plugin-gem/tests/fixture/auto-import/members/output.ts +++ b/crates/swc-plugin-gem/tests/fixture/auto-import/members/output.ts @@ -13,4 +13,5 @@ class MyElement extends GemElement { render() { return html`
`; } -} \ No newline at end of file +} +class MyElement1 extends (_GemElement = GemElement) {} \ No newline at end of file diff --git a/crates/swc-plugin-gem/tests/fixture/memo/output.ts b/crates/swc-plugin-gem/tests/fixture/memo/output.ts index 95c9421b..5580cf37 100644 --- a/crates/swc-plugin-gem/tests/fixture/memo/output.ts +++ b/crates/swc-plugin-gem/tests/fixture/memo/output.ts @@ -1,22 +1,28 @@ // @ts-nocheck class MyElement {} class MyElement1 { - @memo(['src']) get #_src() { - if (bool) return this.#src = '#src'; - return this.#src = '#src'; + if (bool) return '#src'; + return '#src'; + } + @memo(['src']) + #__src() { + this.#src = this.#_src; } #src; } class MyElement2 { - get #src() { - if (bool) return '#src'; - return '#src'; - } - @memo(['src']) - get #_src2() { - return this.#src2 = '#src'; - } - #src2; + get #src() { + if (bool) return '#src'; + return '#src'; + } + get #_src2() { + return '#src'; + } + @memo(['src']) + #__src2() { + this.#src2 = this.#_src2; + } + #src2; } \ No newline at end of file diff --git a/packages/language-service/package.json b/packages/language-service/package.json index 55806cb7..0a343a69 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -1,6 +1,6 @@ { "name": "vscode-gem-languageservice", - "version": "0.0.4", + "version": "0.0.5", "description": "Language service for Gem", "keywords": [ "gem", diff --git a/packages/vscode-gem-plugin/package.json b/packages/vscode-gem-plugin/package.json index 8d662334..81e90db5 100644 --- a/packages/vscode-gem-plugin/package.json +++ b/packages/vscode-gem-plugin/package.json @@ -4,7 +4,7 @@ "displayName": "Gem", "description": "Gem plugin for VS Code", "icon": "logo.png", - "version": "1.0.2", + "version": "1.0.3", "engines": { "vscode": "^1.94.0" }, @@ -156,11 +156,11 @@ "pretest": "tsc -p . --outDir out && pnpm compile", "test": "vscode-test", "vscode:prepublish": "pnpm package", - "publish": "vsce publish --no-dependencies --skip-license" + "publish": "vsce publish --no-dependencies" }, "dependencies": { "duoyun-ui": "^2.2.0", - "vscode-gem-languageservice": "^0.0.4", + "vscode-gem-languageservice": "^0.0.5", "vscode-languageclient": "^9.0.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2dc9a7bb..07af9689 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -372,7 +372,7 @@ importers: specifier: ^2.2.0 version: link:../duoyun-ui vscode-gem-languageservice: - specifier: ^0.0.4 + specifier: ^0.0.5 version: link:../language-service vscode-languageclient: specifier: ^9.0.1