Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 27167e1

Browse files
committed
Bring jsx magic back
1 parent 29eedee commit 27167e1

File tree

7 files changed

+321
-72
lines changed

7 files changed

+321
-72
lines changed

compiler/src/jsx_magic.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use crate::resolver::{Resolver, RE_PROTOCOL_URL};
2+
use std::cell::RefCell;
3+
use std::rc::Rc;
4+
use swc_common::DUMMY_SP;
5+
use swc_ecmascript::ast::*;
6+
use swc_ecmascript::utils::quote_ident;
7+
use swc_ecmascript::visit::{noop_fold_type, Fold, FoldWith};
8+
9+
pub fn jsx_magic_fold(resolver: Rc<RefCell<Resolver>>) -> impl Fold {
10+
JSXMagicFold {
11+
resolver: resolver.clone(),
12+
}
13+
}
14+
15+
/// Aleph JSX magic fold, functions include:
16+
/// - resolve `<a>` to `<Anchor>`
17+
/// - resolve `<head>` to `<Head>`
18+
struct JSXMagicFold {
19+
resolver: Rc<RefCell<Resolver>>,
20+
}
21+
22+
impl JSXMagicFold {
23+
fn fold_jsx_opening_element(&mut self, mut el: JSXOpeningElement) -> JSXOpeningElement {
24+
match &el.name {
25+
JSXElementName::Ident(id) => {
26+
let name = id.sym.as_ref();
27+
match name {
28+
"head" => {
29+
let mut resolver = self.resolver.borrow_mut();
30+
resolver.jsx_magic_tags.insert("Head".into());
31+
el.name = JSXElementName::Ident(quote_ident!("__ALEPH__Head"));
32+
}
33+
34+
"a" => {
35+
let mut resolver = self.resolver.borrow_mut();
36+
let mut tag = "Link";
37+
let mut attrs: Vec<JSXAttrOrSpread> = vec![];
38+
39+
for attr in &el.attrs {
40+
match &attr {
41+
JSXAttrOrSpread::JSXAttr(attr) => {
42+
if let JSXAttr {
43+
name: JSXAttrName::Ident(id),
44+
value: Some(JSXAttrValue::Lit(Lit::Str(Str { value, .. }))),
45+
..
46+
} = attr
47+
{
48+
let key = id.sym.as_ref();
49+
let value = value.as_ref();
50+
if (key == "href" && (value.is_empty() || RE_PROTOCOL_URL.is_match(value)))
51+
|| (key == "target" && value == "_blank")
52+
{
53+
tag = "";
54+
break;
55+
}
56+
}
57+
if let JSXAttr {
58+
name: JSXAttrName::Ident(id),
59+
value,
60+
..
61+
} = attr
62+
{
63+
match id.sym.as_ref() {
64+
"rel" => {
65+
if let Some(JSXAttrValue::Lit(Lit::Str(Str { value, .. }))) = value {
66+
let value = value.as_ref().split(" ").collect::<Vec<&str>>();
67+
if value.contains(&"nav") {
68+
tag = "NavLink";
69+
}
70+
if value.contains(&"replace") {
71+
attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
72+
span: DUMMY_SP,
73+
name: JSXAttrName::Ident(quote_ident!("replace")),
74+
value: None,
75+
}));
76+
}
77+
if value.contains(&"prefetch") {
78+
attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
79+
span: DUMMY_SP,
80+
name: JSXAttrName::Ident(quote_ident!("prefetch")),
81+
value: None,
82+
}));
83+
}
84+
if value.contains(&"exact") {
85+
attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
86+
span: DUMMY_SP,
87+
name: JSXAttrName::Ident(quote_ident!("exact")),
88+
value: None,
89+
}));
90+
}
91+
}
92+
attrs.push(JSXAttrOrSpread::JSXAttr(attr.clone()));
93+
}
94+
"href" => {
95+
attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
96+
span: DUMMY_SP,
97+
name: JSXAttrName::Ident(quote_ident!("to")),
98+
value: value.clone(),
99+
}));
100+
}
101+
"data-active-className" => {
102+
attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
103+
span: DUMMY_SP,
104+
name: JSXAttrName::Ident(quote_ident!("activeClassName")),
105+
value: value.clone(),
106+
}));
107+
}
108+
"data-active-style" => {
109+
attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
110+
span: DUMMY_SP,
111+
name: JSXAttrName::Ident(quote_ident!("activeStyle")),
112+
value: value.clone(),
113+
}));
114+
}
115+
_ => {
116+
attrs.push(JSXAttrOrSpread::JSXAttr(attr.clone()));
117+
}
118+
}
119+
} else {
120+
attrs.push(JSXAttrOrSpread::JSXAttr(attr.clone()))
121+
}
122+
}
123+
_ => attrs.push(attr.clone()),
124+
};
125+
}
126+
127+
if !tag.is_empty() {
128+
resolver.jsx_magic_tags.insert(tag.into());
129+
el.name = JSXElementName::Ident(quote_ident!(format!("__ALEPH__{}", tag)));
130+
el.attrs = attrs;
131+
}
132+
}
133+
134+
_ => {}
135+
}
136+
}
137+
_ => {}
138+
};
139+
140+
el
141+
}
142+
}
143+
144+
impl Fold for JSXMagicFold {
145+
noop_fold_type!();
146+
147+
fn fold_jsx_element(&mut self, el: JSXElement) -> JSXElement {
148+
if el.span == DUMMY_SP {
149+
return el;
150+
}
151+
152+
let mut children: Vec<JSXElementChild> = vec![];
153+
let opening = self.fold_jsx_opening_element(el.opening);
154+
155+
for child in el.children {
156+
children.push(child.fold_children_with(self));
157+
}
158+
159+
JSXElement {
160+
span: DUMMY_SP,
161+
opening,
162+
children,
163+
..el
164+
}
165+
}
166+
}

compiler/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod css;
55
mod error;
66
mod export_names;
77
mod hmr;
8+
mod jsx_magic;
89
mod resolve_fold;
910
mod resolver;
1011
mod swc;
@@ -58,6 +59,9 @@ pub struct Options {
5859
#[serde(default)]
5960
pub jsx_import_source: Option<String>,
6061

62+
#[serde(default)]
63+
pub jsx_magic: bool,
64+
6165
#[serde(default)]
6266
pub strip_data_export: bool,
6367
}
@@ -163,6 +167,7 @@ pub fn transform(specifier: &str, code: &str, options: JsValue) -> Result<JsValu
163167
resolver.clone(),
164168
&EmitOptions {
165169
strip_data_export: options.strip_data_export,
170+
jsx_magic: options.jsx_magic,
166171
jsx_import_source: options.jsx_import_source,
167172
minify: !options.is_dev,
168173
source_map: options.is_dev,

compiler/src/resolve_fold.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::swc_helpers::{is_call_expr_by_name, new_str};
33
use std::{cell::RefCell, rc::Rc};
44
use swc_common::{Span, DUMMY_SP};
55
use swc_ecmascript::ast::*;
6+
use swc_ecmascript::utils::quote_ident;
67
use swc_ecmascript::visit::{noop_fold_type, Fold, FoldWith};
78

89
pub fn resolve_fold(
@@ -29,6 +30,34 @@ impl Fold for ResolveFold {
2930
// fold&resolve import/export url
3031
fn fold_module_items(&mut self, module_items: Vec<ModuleItem>) -> Vec<ModuleItem> {
3132
let mut items = Vec::<ModuleItem>::new();
33+
let jsx_magic_tags = self.resolver.borrow().jsx_magic_tags.to_owned();
34+
let jsx_runtime = self.resolver.borrow().jsx_runtime.to_owned();
35+
36+
if jsx_magic_tags.len() > 0 {
37+
if let Some(jsx_runtime) = jsx_runtime {
38+
let mut resolver = self.resolver.borrow_mut();
39+
let url = format!("{}/framework/{}/mod.ts", resolver.aleph_pkg_uri, jsx_runtime);
40+
let src = resolver.resolve(&url, false, None);
41+
// import { $TAG as __ALEPH_$TAG } from "$aleph_pkg_uri/framework/$framework/mod.ts"
42+
items.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
43+
span: DUMMY_SP,
44+
specifiers: jsx_magic_tags
45+
.into_iter()
46+
.map(|tag| {
47+
ImportSpecifier::Named(ImportNamedSpecifier {
48+
span: DUMMY_SP,
49+
local: quote_ident!(format!("__ALEPH__{}", tag)),
50+
imported: Some(ModuleExportName::Ident(quote_ident!(tag.as_str()))),
51+
is_type_only: false,
52+
})
53+
})
54+
.collect(),
55+
src: new_str(&src),
56+
type_only: false,
57+
asserts: None,
58+
})));
59+
}
60+
}
3261

3362
for item in module_items {
3463
match item {

compiler/src/resolver.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use import_map::ImportMap;
2+
use indexmap::IndexSet;
23
use path_slash::PathBufExt;
34
use pathdiff::diff_paths;
45
use regex::Regex;
@@ -12,7 +13,7 @@ use url::Url;
1213
lazy_static! {
1314
pub static ref RE_REACT_URL: Regex =
1415
Regex::new(r"^https?://(esm\.sh|cdn\.esm\.sh)(/v\d+)?/react(\-dom)?(@[^/]+)?(/.*)?$").unwrap();
15-
pub static ref RE_PROTOCOL_URL: Regex = Regex::new(r"^(mailto:|[a-z]+://)").unwrap();
16+
pub static ref RE_PROTOCOL_URL: Regex = Regex::new(r"^(data:|mailto:|[a-z]+://)").unwrap();
1617
}
1718

1819
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
@@ -38,23 +39,17 @@ pub struct Resolver {
3839
pub deps: Vec<DependencyDescriptor>,
3940
/// jsx runtime: react | preact
4041
pub jsx_runtime: Option<String>,
42+
/// resolved jsx magic tags: `Head`, 'Anchor'
43+
pub jsx_magic_tags: IndexSet<String>,
4144
/// development mode
4245
pub is_dev: bool,
4346
// internal
4447
import_map: ImportMap,
45-
resolve_remote_deps: bool,
46-
jsx_runtime_version: Option<String>,
47-
jsx_runtime_cdn_version: Option<String>,
4848
graph_versions: HashMap<String, String>,
4949
initial_graph_version: Option<String>,
50-
}
51-
52-
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
53-
#[serde(rename_all = "camelCase")]
54-
pub struct InlineStyle {
55-
pub r#type: String,
56-
pub quasis: Vec<String>,
57-
pub exprs: Vec<String>,
50+
jsx_runtime_cdn_version: Option<String>,
51+
jsx_runtime_version: Option<String>,
52+
resolve_remote_deps: bool,
5853
}
5954

6055
impl Resolver {
@@ -75,6 +70,7 @@ impl Resolver {
7570
specifier: specifier.into(),
7671
specifier_is_remote: is_http_url(specifier),
7772
deps: Vec::new(),
73+
jsx_magic_tags: IndexSet::new(),
7874
jsx_runtime,
7975
jsx_runtime_version,
8076
jsx_runtime_cdn_version,

compiler/src/swc.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::error::{DiagnosticBuffer, ErrorBuffer};
22
use crate::export_names::ExportParser;
33
use crate::hmr::hmr;
4+
use crate::jsx_magic::jsx_magic_fold;
45
use crate::resolve_fold::resolve_fold;
56
use crate::resolver::{DependencyDescriptor, Resolver};
67

@@ -23,6 +24,7 @@ use swc_ecmascript::visit::{Fold, FoldWith};
2324
#[derive(Debug, Clone)]
2425
pub struct EmitOptions {
2526
pub jsx_import_source: Option<String>,
27+
pub jsx_magic: bool,
2628
pub strip_data_export: bool,
2729
pub minify: bool,
2830
pub source_map: bool,
@@ -32,6 +34,7 @@ impl Default for EmitOptions {
3234
fn default() -> Self {
3335
EmitOptions {
3436
jsx_import_source: None,
37+
jsx_magic: false,
3538
strip_data_export: false,
3639
minify: false,
3740
source_map: false,
@@ -152,6 +155,7 @@ impl SWC {
152155
};
153156
let passes = chain!(
154157
swc_ecma_transforms::resolver(unresolved_mark, top_level_mark, is_ts),
158+
Optional::new(jsx_magic_fold(resolver.clone()), options.jsx_magic),
155159
Optional::new(react::jsx_src(is_dev, self.source_map.clone()), is_jsx),
156160
resolve_fold(resolver.clone(), options.strip_data_export, false),
157161
decorators::decorators(decorators::Config {
@@ -201,7 +205,7 @@ impl SWC {
201205
Optional::new(hmr(resolver.clone()), is_dev && !specifier_is_remote),
202206
fixer(Some(&self.comments)),
203207
hygiene()
204-
);
208+
);
205209

206210
let (code, map) = self.emit(passes, options.source_map, options.minify).unwrap();
207211

0 commit comments

Comments
 (0)