Skip to content

Commit c9e8e5b

Browse files
committed
feat: parser support jsx
1 parent e6e2ef0 commit c9e8e5b

File tree

20 files changed

+491
-23
lines changed

20 files changed

+491
-23
lines changed

crates/node_binding/napi-binding.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2337,6 +2337,11 @@ export interface RawJavascriptParserOptions {
23372337
* @experimental
23382338
*/
23392339
typeReexportsPresence?: string
2340+
/**
2341+
* This option is experimental in Rspack only and subject to change or be removed anytime.
2342+
* @experimental
2343+
*/
2344+
jsx?: boolean
23402345
}
23412346

23422347
export interface RawJsonGeneratorOptions {

crates/rspack/src/builder/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,7 @@ impl ModuleOptionsBuilder {
17151715
require_resolve: Some(true),
17161716
import_dynamic: Some(true),
17171717
inline_const: Some(false),
1718+
jsx: Some(false),
17181719
..Default::default()
17191720
}),
17201721
);

crates/rspack/tests/snapshots/defaults__default_options.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,9 @@ CompilerOptions {
14411441
inline_const: Some(
14421442
false,
14431443
),
1444+
jsx: Some(
1445+
false,
1446+
),
14441447
},
14451448
),
14461449
},

crates/rspack_binding_api/src/raw_options/raw_module/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ pub struct RawJavascriptParserOptions {
297297
/// This option is experimental in Rspack only and subject to change or be removed anytime.
298298
/// @experimental
299299
pub type_reexports_presence: Option<String>,
300+
/// This option is experimental in Rspack only and subject to change or be removed anytime.
301+
/// @experimental
302+
pub jsx: Option<bool>,
300303
}
301304

302305
impl From<RawJavascriptParserOptions> for JavascriptParserOptions {
@@ -342,6 +345,7 @@ impl From<RawJavascriptParserOptions> for JavascriptParserOptions {
342345
require_resolve: value.require_resolve,
343346
import_dynamic: value.import_dynamic,
344347
inline_const: value.inline_const,
348+
jsx: value.jsx,
345349
}
346350
}
347351
}

crates/rspack_core/src/concatenated_module.rs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use swc_core::{
3030
ecma::{
3131
ast::{EsVersion, Program},
3232
atoms::Atom,
33-
parser::{Syntax, parse_file_as_module},
33+
parser::{EsSyntax, Syntax, parse_file_as_module},
3434
transforms::base::resolver,
3535
},
3636
};
@@ -47,13 +47,13 @@ use crate::{
4747
IdentCollector, InitFragment, InitFragmentStage, LibIdentOptions,
4848
MaybeDynamicTargetExportInfoHashKey, Module, ModuleArgument, ModuleGraph,
4949
ModuleGraphCacheArtifact, ModuleGraphConnection, ModuleIdentifier, ModuleLayer,
50-
ModuleStaticCacheArtifact, ModuleType, NAMESPACE_OBJECT_EXPORT, PrefetchExportsInfoMode, Resolve,
51-
RuntimeCondition, RuntimeGlobals, RuntimeSpec, SourceType, UsageState, UsedName, UsedNameItem,
52-
define_es_module_flag_statement, escape_identifier, filter_runtime, get_runtime_key,
53-
impl_source_map_config, merge_runtime_condition, merge_runtime_condition_non_false,
54-
module_update_hash, property_access, property_name, reserved_names::RESERVED_NAMES,
55-
returning_function, runtime_condition_expression, subtract_runtime_condition,
56-
to_identifier_with_escaped, to_normal_comment,
50+
ModuleStaticCacheArtifact, ModuleType, NAMESPACE_OBJECT_EXPORT, ParserOptions,
51+
PrefetchExportsInfoMode, Resolve, RuntimeCondition, RuntimeGlobals, RuntimeSpec, SourceType,
52+
UsageState, UsedName, UsedNameItem, define_es_module_flag_statement, escape_identifier,
53+
filter_runtime, get_runtime_key, impl_source_map_config, merge_runtime_condition,
54+
merge_runtime_condition_non_false, module_update_hash, property_access, property_name,
55+
reserved_names::RESERVED_NAMES, returning_function, runtime_condition_expression,
56+
subtract_runtime_condition, to_identifier_with_escaped, to_normal_comment,
5757
};
5858

5959
type ExportsDefinitionArgs = Vec<(String, String)>;
@@ -1989,10 +1989,41 @@ impl ConcatenatedModule {
19891989
let comments = SwcComments::default();
19901990
let mut module_info = concatenation_scope.current_module;
19911991

1992+
let jsx = {
1993+
let get_jsx = |options: &ParserOptions| -> Option<bool> {
1994+
match options {
1995+
ParserOptions::Javascript(js_options)
1996+
| ParserOptions::JavascriptAuto(js_options)
1997+
| ParserOptions::JavascriptEsm(js_options)
1998+
| ParserOptions::JavascriptDynamic(js_options) => js_options.jsx,
1999+
_ => None,
2000+
}
2001+
};
2002+
2003+
module
2004+
.as_ref()
2005+
.as_normal_module()
2006+
.and_then(|normal_module| normal_module.get_parser_options())
2007+
.and_then(get_jsx)
2008+
.or_else(|| {
2009+
compilation
2010+
.options
2011+
.module
2012+
.parser
2013+
.as_ref()
2014+
.and_then(|p| p.get(module.module_type().as_str()))
2015+
.and_then(get_jsx)
2016+
})
2017+
.unwrap_or(false)
2018+
};
2019+
19922020
let mut errors = vec![];
19932021
let program = match parse_file_as_module(
19942022
&fm,
1995-
Syntax::default(),
2023+
Syntax::Es(EsSyntax {
2024+
jsx,
2025+
..Default::default()
2026+
}),
19962027
EsVersion::EsNext,
19972028
Some(&comments),
19982029
&mut errors,

crates/rspack_core/src/options/module.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ pub struct JavascriptParserOptions {
283283
pub require_resolve: Option<bool>,
284284
pub import_dynamic: Option<bool>,
285285
pub inline_const: Option<bool>,
286+
pub jsx: Option<bool>,
286287
}
287288

288289
#[cacheable]

crates/rspack_plugin_javascript/src/parser_and_generator/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use rspack_core::{
55
AsyncDependenciesBlockIdentifier, BuildMetaExportsType, COLLECTED_TYPESCRIPT_INFO_PARSE_META_KEY,
66
ChunkGraph, CollectedTypeScriptInfo, Compilation, DependenciesBlock, DependencyId,
77
DependencyRange, GenerateContext, Module, ModuleGraph, ModuleType, ParseContext, ParseResult,
8-
ParserAndGenerator, SideEffectsBailoutItem, SourceType, TemplateContext, TemplateReplaceSource,
8+
ParserAndGenerator, SideEffectsBailoutItem, SourceType, TemplateContext,
9+
TemplateReplaceSource,
910
diagnostics::map_box_diagnostics_to_module_parse_diagnostics,
1011
remove_bom, render_init_fragments,
1112
rspack_sources::{BoxSource, ReplaceSource, Source, SourceExt},
@@ -179,8 +180,15 @@ impl ParserAndGenerator for JavaScriptParserAndGenerator {
179180
);
180181
let comments = SwcComments::default();
181182
let target = ast::EsVersion::EsNext;
183+
184+
let jsx = module_parser_options
185+
.and_then(|options| options.get_javascript())
186+
.and_then(|options| options.jsx)
187+
.unwrap_or(false);
188+
182189
let parser_lexer = Lexer::new(
183190
Syntax::Es(EsSyntax {
191+
jsx,
184192
allow_return_outside_function: matches!(
185193
module_type,
186194
ModuleType::JsDynamic | ModuleType::JsAuto

crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk.rs

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ use swc_core::{
88
BinExpr, BlockStmt, BlockStmtOrExpr, CallExpr, Callee, CatchClause, Class, ClassExpr,
99
ClassMember, CondExpr, DefaultDecl, DoWhileStmt, ExportDefaultDecl, Expr, ExprOrSpread,
1010
ExprStmt, FnExpr, ForHead, ForInStmt, ForOfStmt, ForStmt, Function, GetterProp, Ident, IfStmt,
11-
KeyValueProp, LabeledStmt, MemberExpr, MemberProp, MetaPropExpr, ModuleDecl, ModuleItem,
12-
NewExpr, ObjectLit, ObjectPat, ObjectPatProp, OptCall, OptChainExpr, Param, Pat, Prop,
13-
PropName, PropOrSpread, RestPat, ReturnStmt, SeqExpr, SetterProp, SimpleAssignTarget, Stmt,
14-
SwitchCase, SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TryStmt, UnaryExpr, UnaryOp,
15-
UpdateExpr, VarDeclOrExpr, WhileStmt, WithStmt, YieldExpr,
11+
JSXAttr, JSXAttrOrSpread, JSXAttrValue, JSXElement, JSXElementChild, JSXElementName, JSXExpr,
12+
JSXExprContainer, JSXFragment, JSXMemberExpr, JSXNamespacedName, JSXObject, KeyValueProp,
13+
LabeledStmt, MemberExpr, MemberProp, MetaPropExpr, ModuleDecl, ModuleItem, NewExpr, ObjectLit,
14+
ObjectPat, ObjectPatProp, OptCall, OptChainExpr, Param, Pat, Prop, PropName, PropOrSpread,
15+
RestPat, ReturnStmt, SeqExpr, SetterProp, SimpleAssignTarget, Stmt, SwitchCase, SwitchStmt,
16+
TaggedTpl, ThisExpr, ThrowStmt, Tpl, TryStmt, UnaryExpr, UnaryOp, UpdateExpr, VarDeclOrExpr,
17+
WhileStmt, WithStmt, YieldExpr, IdentName,
1618
},
1719
};
1820

@@ -431,12 +433,18 @@ impl JavascriptParser<'_> {
431433
Expr::Update(expr) => self.walk_update_expression(expr),
432434
Expr::Yield(expr) => self.walk_yield_expression(expr),
433435
Expr::SuperProp(_) | Expr::Lit(_) | Expr::PrivateName(_) | Expr::Invalid(_) => (),
436+
Expr::JSXMember(_) | Expr::JSXNamespacedName(_) | Expr::JSXEmpty(_) => {
437+
self.ensure_jsx_enabled();
438+
}
439+
Expr::JSXElement(element) => {
440+
self.ensure_jsx_enabled();
441+
self.walk_jsx_element(element);
442+
}
443+
Expr::JSXFragment(fragment) => {
444+
self.ensure_jsx_enabled();
445+
self.walk_jsx_fragment(fragment);
446+
}
434447
Expr::Paren(_)
435-
| Expr::JSXMember(_)
436-
| Expr::JSXNamespacedName(_)
437-
| Expr::JSXEmpty(_)
438-
| Expr::JSXElement(_)
439-
| Expr::JSXFragment(_)
440448
| Expr::TsTypeAssertion(_)
441449
| Expr::TsConstAssertion(_)
442450
| Expr::TsNonNull(_)
@@ -516,6 +524,103 @@ impl JavascriptParser<'_> {
516524
}
517525
}
518526

527+
fn ensure_jsx_enabled(&self) {
528+
if !self.javascript_options.jsx.unwrap_or_default() {
529+
unreachable!();
530+
}
531+
}
532+
533+
fn walk_jsx_element(&mut self, element: &JSXElement) {
534+
self.walk_jsx_element_name(&element.opening.name);
535+
if element.opening.type_args.is_some() {
536+
// JSX type arguments only exist in TSX; this walker assumes pure JSX input.
537+
unreachable!();
538+
}
539+
for attr in &element.opening.attrs {
540+
self.walk_jsx_attr_or_spread(attr);
541+
}
542+
for child in &element.children {
543+
self.walk_jsx_child(child);
544+
}
545+
if let Some(closing) = &element.closing {
546+
self.walk_jsx_element_name(&closing.name);
547+
}
548+
}
549+
550+
fn walk_jsx_fragment(&mut self, fragment: &JSXFragment) {
551+
for child in &fragment.children {
552+
self.walk_jsx_child(child);
553+
}
554+
}
555+
556+
fn walk_jsx_child(&mut self, child: &JSXElementChild) {
557+
match child {
558+
JSXElementChild::JSXElement(element) => self.walk_jsx_element(element),
559+
JSXElementChild::JSXFragment(fragment) => self.walk_jsx_fragment(fragment),
560+
JSXElementChild::JSXExprContainer(container) => self.walk_jsx_expr_container(container),
561+
JSXElementChild::JSXSpreadChild(spread) => self.walk_expression(&spread.expr),
562+
JSXElementChild::JSXText(_) => (),
563+
}
564+
}
565+
566+
fn walk_jsx_expr_container(&mut self, container: &JSXExprContainer) {
567+
match &container.expr {
568+
JSXExpr::Expr(expr) => self.walk_expression(expr),
569+
JSXExpr::JSXEmptyExpr(_) => (),
570+
}
571+
}
572+
573+
fn walk_jsx_attr_or_spread(&mut self, attr: &JSXAttrOrSpread) {
574+
match attr {
575+
JSXAttrOrSpread::JSXAttr(attr) => self.walk_jsx_attr(attr),
576+
JSXAttrOrSpread::SpreadElement(spread) => self.walk_expression(&spread.expr),
577+
}
578+
}
579+
580+
fn walk_jsx_attr(&mut self, attr: &JSXAttr) {
581+
if let Some(value) = &attr.value {
582+
self.walk_jsx_attr_value(value);
583+
}
584+
}
585+
586+
fn walk_jsx_attr_value(&mut self, value: &JSXAttrValue) {
587+
match value {
588+
JSXAttrValue::Lit(_) => (),
589+
JSXAttrValue::JSXExprContainer(container) => self.walk_jsx_expr_container(container),
590+
JSXAttrValue::JSXElement(element) => self.walk_jsx_element(element),
591+
JSXAttrValue::JSXFragment(fragment) => self.walk_jsx_fragment(fragment),
592+
}
593+
}
594+
595+
fn walk_jsx_element_name(&mut self, name: &JSXElementName) {
596+
match name {
597+
JSXElementName::Ident(ident) => self.walk_identifier(ident),
598+
JSXElementName::JSXMemberExpr(member) => self.walk_jsx_member_expr(member),
599+
JSXElementName::JSXNamespacedName(namespaced) => self.walk_jsx_namespaced_name(namespaced),
600+
}
601+
}
602+
603+
fn walk_jsx_member_expr(&mut self, member: &JSXMemberExpr) {
604+
self.walk_jsx_object(&member.obj);
605+
}
606+
607+
fn walk_jsx_object(&mut self, obj: &JSXObject) {
608+
match obj {
609+
JSXObject::Ident(ident) => self.walk_identifier(ident),
610+
JSXObject::JSXMemberExpr(member) => self.walk_jsx_member_expr(member),
611+
}
612+
}
613+
614+
fn walk_jsx_namespaced_name(&mut self, name: &JSXNamespacedName) {
615+
self.walk_ident_name(&name.ns);
616+
self.walk_ident_name(&name.name);
617+
}
618+
619+
fn walk_ident_name(&mut self, name: &IdentName) {
620+
let ident: Ident = name.clone().into();
621+
self.walk_identifier(&ident);
622+
}
623+
519624
fn walk_object_expression(&mut self, expr: &ObjectLit) {
520625
for prop in &expr.props {
521626
self.walk_property_or_spread(prop);

packages/rspack/etc/core.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3379,6 +3379,7 @@ export type JavascriptParserOptions = {
33793379
importDynamic?: boolean;
33803380
inlineConst?: boolean;
33813381
typeReexportsPresence?: "no-tolerant" | "tolerant" | "tolerant-no-check";
3382+
jsx?: boolean;
33823383
};
33833384

33843385
// @public (undocumented)

packages/rspack/src/config/adapter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,8 @@ function getRawJavascriptParserOptions(
585585
requireResolve: parser.requireResolve,
586586
importDynamic: parser.importDynamic,
587587
inlineConst: parser.inlineConst,
588-
typeReexportsPresence: parser.typeReexportsPresence
588+
typeReexportsPresence: parser.typeReexportsPresence,
589+
jsx: parser.jsx
589590
};
590591
}
591592

0 commit comments

Comments
 (0)