-
Notifications
You must be signed in to change notification settings - Fork 483
[PoC]: Rewrite nested JSX component paths to direct hoisted exports #8293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
fhammerschmidt
wants to merge
59
commits into
rescript-lang:master
Choose a base branch
from
fhammerschmidt:poc-jsx-component-nested-exports
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 50 commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
add21c2
Rewrite nested JSX component paths to direct hoisted exports
fhammerschmidt 97bafbb
Fix syntax snapshots
fhammerschmidt fb1a22f
Format
fhammerschmidt 27bd27c
Fix gentype tests
fhammerschmidt e20ac3b
Fix rewatch test
fhammerschmidt f78664b
Another fixed snapshot
fhammerschmidt 2bdb0e5
Fix namespace handling
fhammerschmidt 0b45ebb
Fix possible race condition in rewatch test
fhammerschmidt 22003b4
Copilot comment fixes
fhammerschmidt 01e5f43
Update syntax tests
fhammerschmidt f0c3ccd
Update tests
fhammerschmidt 15ded2d
Add more regression tests
fhammerschmidt a6a5dd1
fix syntax tests
tsnobip 18c14c7
add repro where direct component function doesn't get exported
tsnobip e3ad8b0
fix export of JSX components inside module with regular functions
tsnobip 04a5846
update analysis/gentype tests
tsnobip 573dd37
fix functors
tsnobip f0ebf0d
add failing test with interface file
tsnobip 26cf5e2
fix signature of @jsx.component
tsnobip 69603a7
repro: private JSX components raise warning 32
tsnobip 88e45fa
remove warning -32 when emitting the namespaced function and jsx status
tsnobip 5d4b296
Even more regression tests
fhammerschmidt b8ba01b
@react.componentWithProps regression tests
fhammerschmidt 1287542
Make this a breaking change and only export the modules alone
fhammerschmidt 97d37f1
Only export namespaced component
fhammerschmidt 40f4633
Merge remote-tracking branch 'upstream/master' into poc-jsx-component…
fhammerschmidt e2d06ee
Format
fhammerschmidt 8c00e4c
Gentype fix
fhammerschmidt a9bbed8
Fix jsx props regression
fhammerschmidt b630257
Cleanup
fhammerschmidt 137e8fc
Changelog
fhammerschmidt a5aae31
Merge branch 'master' into poc-jsx-component-nested-exports
fhammerschmidt 8484a7f
Remove bool marker again
fhammerschmidt 450959b
Fix changelog
fhammerschmidt 36e6edd
More test fixes
fhammerschmidt 467fcf5
Fix absurd preserved JSX
fhammerschmidt 2353066
Refactor
fhammerschmidt 7f0d42c
Some more refactoring
fhammerschmidt 668d01a
merge
fhammerschmidt c89fd5b
Fix tests
fhammerschmidt f683a84
Fix snapshots
fhammerschmidt 1f66b1d
More snapshots
fhammerschmidt 3bdd0fe
Even more snapshots
fhammerschmidt 76e188f
Remove unrelated rewatch changes
fhammerschmidt 58fb998
Don't hardcode file extensions
fhammerschmidt fb754b6
Reuse some more logic
fhammerschmidt fbddf47
Reuse nested component separator literal
fhammerschmidt 761069a
Extract nested component compilation logic into separate module
fhammerschmidt 88e55f9
Get rid of some local wrapper functions
fhammerschmidt 3302728
Fix nested component plain make access rewrite
fhammerschmidt 0c0a77f
Fix segment order bugs
fhammerschmidt 67080d8
Fix nested JSX path segment handling
fhammerschmidt f0c5dff
Fix componentWithProps interfaces and same-name nested JSX components
fhammerschmidt 2821909
Whoops
fhammerschmidt 61129c4
Simplify nested JSX component lowering
fhammerschmidt 8f7f1e1
Update JSX component export snapshots
fhammerschmidt af68c1f
Use hoisted value metadata for nested JSX components
fhammerschmidt 9d3d4fe
Resolve hoisted component paths in value lowering
fhammerschmidt aac7e2f
Revert "Resolve hoisted component paths in value lowering"
fhammerschmidt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| (* Copyright (C) 2026 - Authors of ReScript | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Lesser General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * In addition to the permissions granted to you by the LGPL, you may combine | ||
| * or link a "work that uses the Library" with a publicly distributed version | ||
| * of this file to produce a combined library or application, then distribute | ||
| * that combined work under the terms of your choosing, with no requirement | ||
| * to comply with the obligations normally placed on you by section 4 of the | ||
| * LGPL version 3 (or the corresponding section of a later version of the LGPL | ||
| * should you choose to use a later version). | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public License | ||
| * along with this program; if not, write to the Free Software | ||
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) | ||
|
|
||
| module E = Js_exp_make | ||
|
|
||
| module StringSet = Set.Make (String) | ||
|
|
||
| type candidate = {module_ident: Ident.t} | ||
|
|
||
| let hidden_component_suffix (module_ident : Ident.t) = | ||
| Ext_modulename.nested_component_suffix (Ident.name module_ident) | ||
|
|
||
| let is_hidden_component_name_for module_ident ident = | ||
| Ext_string.ends_with (Ident.name ident) (hidden_component_suffix module_ident) | ||
|
|
||
| let find_hidden_alias_by_value block module_ident value_ident = | ||
| List.find_map | ||
| (fun (st : J.statement) -> | ||
| match st.statement_desc with | ||
| | Variable | ||
| { | ||
| ident; | ||
| value = Some {expression_desc = Var (Id target); _}; | ||
| property = _; | ||
| ident_info = _; | ||
| } | ||
| when Ident.same target value_ident | ||
| && is_hidden_component_name_for module_ident ident -> | ||
| Some ident | ||
| | _ -> None) | ||
| block | ||
|
|
||
| let has_export_name exports name = | ||
| List.exists | ||
| (fun (ident : Ident.t) -> String.equal (Ident.name ident) name) | ||
| exports | ||
|
|
||
| let candidate_of_statement block exports (st : J.statement) = | ||
| match st.statement_desc with | ||
| | Variable | ||
| { | ||
| ident = module_ident; | ||
| value = | ||
| Some | ||
| { | ||
| expression_desc = | ||
| Caml_block | ||
| ( [{expression_desc = Var (Id value_ident); _}], | ||
| Immutable, | ||
| _, | ||
| Blk_module ["make"] ); | ||
| _; | ||
| }; | ||
| property = _; | ||
| ident_info = _; | ||
| } -> ( | ||
| let hidden_ident = | ||
| if is_hidden_component_name_for module_ident value_ident then | ||
| Some value_ident | ||
| else find_hidden_alias_by_value block module_ident value_ident | ||
| in | ||
| match hidden_ident with | ||
| | Some hidden_ident when has_export_name exports hidden_ident.name -> | ||
| Some {module_ident} | ||
| | Some _ | None -> None) | ||
| | _ -> None | ||
|
|
||
| let collect_candidates block exports = | ||
| Ext_list.filter_map block (candidate_of_statement block exports) | ||
|
|
||
| let hidden_export_names_to_remove candidates = | ||
| Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> | ||
| StringSet.add (Ident.name candidate.module_ident) acc) | ||
|
|
||
| let chop_js_suffix basename = | ||
| match String.index_opt basename '.' with | ||
| | Some index -> String.sub basename 0 index | ||
| | None -> basename | ||
|
|
||
| let dynamic_import_module_root (expr : J.expression) = | ||
| match expr.expression_desc with | ||
| | Await | ||
| { | ||
| expression_desc = | ||
| Call ({expression_desc = Var (Id import_ident); _}, [arg], _); | ||
| _; | ||
| } | ||
| when String.equal import_ident.name "import" -> ( | ||
| match arg.expression_desc with | ||
| | Str {txt; _} -> Some (Filename.basename txt |> chop_js_suffix) | ||
| | _ -> None) | ||
| | _ -> None | ||
|
|
||
| let known_hidden_component_exports block = | ||
| let hidden_exports = ref StringSet.empty in | ||
| let mapper = | ||
| { | ||
| Js_record_map.super with | ||
| expression = | ||
| (fun self expr -> | ||
| (match expr.expression_desc with | ||
| | Var (Qualified (_, Some name)) -> | ||
| hidden_exports := StringSet.add name !hidden_exports | ||
| | Static_index (_, name, _) | ||
| when String.contains name | ||
| Ext_modulename.nested_component_separator_char -> | ||
| hidden_exports := StringSet.add name !hidden_exports | ||
| | _ -> ()); | ||
| Js_record_map.super.expression self expr); | ||
| } | ||
| in | ||
| ignore (mapper.block mapper block); | ||
| !hidden_exports | ||
|
|
||
| let dynamic_import_aliases block = | ||
| List.fold_left | ||
| (fun aliases (st : J.statement) -> | ||
| match st.statement_desc with | ||
| | Variable {ident; value = Some expr; _} -> ( | ||
| match dynamic_import_module_root expr with | ||
| | Some module_root -> Map_ident.add aliases ident module_root | ||
| | None -> aliases) | ||
| | _ -> aliases) | ||
| Map_ident.empty block | ||
|
|
||
| let rewrite_dynamic_import_component_access aliases known_hidden_exports | ||
| (expr : J.expression) = | ||
| let rec collect_segments segments (expr : J.expression) = | ||
| match expr.expression_desc with | ||
| | Static_index (inner, field, _) -> | ||
| collect_segments (field :: segments) inner | ||
| | Var (Id id) -> ( | ||
| match Map_ident.find_opt aliases id with | ||
| | Some module_root when segments <> [] -> Some (id, module_root, segments) | ||
| | Some _ | None -> None) | ||
| | _ -> None | ||
| in | ||
| match expr.expression_desc with | ||
| | Static_index (inner, "make", _) -> ( | ||
| match collect_segments [] inner with | ||
| | Some (id, module_root, segments) -> | ||
| let segments = List.rev segments in | ||
| let hidden_name = | ||
| match segments with | ||
| | first :: _ | ||
| when Ext_string.starts_with first | ||
| (Ext_modulename.nested_component_prefix module_root) -> | ||
| Ext_modulename.concat_nested_component_name segments | ||
| | _ -> | ||
| Ext_modulename.concat_nested_component_name (module_root :: segments) | ||
| in | ||
| if StringSet.mem hidden_name known_hidden_exports then | ||
| {expr with expression_desc = Static_index (E.var id, hidden_name, None)} | ||
| else expr | ||
| | None -> expr) | ||
| | _ -> expr | ||
|
|
||
| let rewrite_dynamic_import_block block = | ||
| let aliases = dynamic_import_aliases block in | ||
| if Map_ident.is_empty aliases then block | ||
| else | ||
| let known_hidden_exports = known_hidden_component_exports block in | ||
| let mapper = | ||
| { | ||
| Js_record_map.super with | ||
| expression = | ||
| (fun self expr -> | ||
| let expr = Js_record_map.super.expression self expr in | ||
| rewrite_dynamic_import_component_access aliases known_hidden_exports | ||
| expr); | ||
| } | ||
| in | ||
| mapper.block mapper block | ||
|
|
||
| let program (js : J.program) : J.program = | ||
| let candidates = collect_candidates js.block js.exports in | ||
| let removed_export_names = hidden_export_names_to_remove candidates in | ||
| let exports = | ||
| Ext_list.filter js.exports (fun (ident : Ident.t) -> | ||
| not (StringSet.mem (Ident.name ident) removed_export_names)) | ||
| in | ||
| let export_set = Set_ident.of_list exports in | ||
| let block = rewrite_dynamic_import_block js.block in | ||
| {J.block; exports; export_set} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| (* Copyright (C) 2026 - Authors of ReScript | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Lesser General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * In addition to the permissions granted to you by the LGPL, you may combine | ||
| * or link a "work that uses the Library" with a publicly distributed version | ||
| * of this file to produce a combined library or application, then distribute | ||
| * that combined work under the terms of your choosing, with no requirement | ||
| * to comply with the obligations normally placed on you by section 4 of the | ||
| * LGPL version 3 (or the corresponding section of a later version of the LGPL | ||
| * should you choose to use a later version). | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public License | ||
| * along with this program; if not, write to the Free Software | ||
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) | ||
|
|
||
| (* Rewrite nested JSX component module exports from `{make: fn}` wrappers to | ||
| direct component exports, while keeping `.make` as a self-reference for | ||
| compatibility with existing generated call sites. *) | ||
| val program : J.program -> J.program |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.