Skip to content

[WIP] - Preserve JSX by adding extra app flag #7387

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

Draft
wants to merge 24 commits into
base: master
Choose a base branch
from

Conversation

nojaf
Copy link
Collaborator

@nojaf nojaf commented Apr 10, 2025

@cristianoc, this doesn't work yet, but it highlights the idea. Could you point out where the information is not passed? It's a bit overwhelming for me to grasp.

@@ -199,12 +199,13 @@ let expr sub x =
| Texp_function {arg_label; arity; param; case; partial; async} ->
Texp_function
{arg_label; arity; param; case = sub.case sub case; partial; async}
| Texp_apply {funct = exp; args = list; partial} ->
| Texp_apply {funct = exp; args = list; partial; transformed_jsx} ->
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store the potentially transformed jsx node.

@@ -1277,7 +1278,7 @@ let mk_react_jsx (config : Jsx_common.jsx_config) mapper loc attrs
[key_prop; (nolabel, unit_expr ~loc:Location.none)] )
in
let args = [(nolabel, elementTag); (nolabel, props_record)] @ key_and_unit in
Exp.apply ~loc ~attrs jsx_expr args
Exp.apply ~loc ~attrs ~transformed_jsx jsx_expr args
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capture jsx ast

@@ -2401,7 +2401,7 @@ and type_expect_ ?type_clash_context ?in_function ?(recarg = Rejected) env sexp
type_function ?in_function ~arity ~async loc sexp.pexp_attributes env
ty_expected l
[Ast_helper.Exp.case spat sbody]
| Pexp_apply {funct = sfunct; args = sargs; partial} ->
| Pexp_apply {funct = sfunct; args = sargs; partial; transformed_jsx} ->
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From here on, pass it along to the next layer.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it never makes it here. So it must have gotten lost somewhere.
I'd start with a simple test examples and start adding some assert false whenever transformed_jsx is not None, to catch where was the last point where it was present before getting lost.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh looks like it gets lost in bs_ast_mapper.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it seems to take a different route because <div> is an FFI call.

@@ -524,6 +524,10 @@ and expression_desc cxt ~(level : int) f x : cxt =
when Ext_list.length_equal el i
]}
*)
| Call (e, el, {call_transformed_jsx = Some jsx_element}) ->
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope it ends up here and do something meaningful.
Unfortunately, it didn't work for me. It got lost somewhere.

@nojaf nojaf changed the title Extra app data WIP - Extra app data Apr 10, 2025
@nojaf
Copy link
Collaborator Author

nojaf commented Apr 23, 2025

Got an initial result here, was able to transform:

module ReactDOM = {
    @module("react/jsx-runtime")
    external jsx: (string, JsxDOM.domProps) => Jsx.element = "jsx"
}

let _ = <div className="meh"></div>

into

// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';

let JsxRuntime = require("react/jsx-runtime");

let ReactDOM = {};

<div className={"meh"}></div>;

exports.ReactDOM = ReactDOM;

There are still a lot of cases to cover, but it is a start.

@nojaf
Copy link
Collaborator Author

nojaf commented Apr 25, 2025

Made some more progress, was able to transform my entire project.
This is an example of how the playground example looks like:

// Generated by ReScript, PLEASE EDIT WITH CARE

import * as React from "react";
import * as JsxRuntime from "react/jsx-runtime";

function Playground$CounterMessage(props) {
  let username = props.username;
  let count = props.count;
  let times = count !== 1 ? (
      count !== 2 ? String(count) + " times" : "twice"
    ) : "once";
  let name = username !== undefined && username !== "" ? username : "Anonymous";
  return <div>{"Hello " + name + ", you clicked me " + times}</div>;
}

let CounterMessage = {
  make: Playground$CounterMessage
};

function Playground$App(props) {
  let match = React.useState(() => 0);
  let setCount = match[1];
  let match$1 = React.useState(() => "Anonymous");
  let setUsername = match$1[1];
  let username = match$1[0];
  return <div>{[
    "Username: ",
    <input type={"text"} value={username} onChange={event => {
      event.preventDefault();
      let eventTarget = event.target;
      let username = eventTarget.value;
      setUsername(_prev => username);
    }}/>,
    <button onClick={_evt => setCount(prev => prev + 1 | 0)}>{"Click me"}</button>,
    <button onClick={_evt => setCount(param => 0)}>{"Reset"}</button>,
    <Playground$CounterMessage count={match[0]} username={username}/>
  ]}</div>;
}

let App = {
  make: Playground$App
};

export {
  CounterMessage,
  App,
}
/* react Not a pure module */

@nojaf nojaf changed the title WIP - Extra app data WIP - Preserve JSX by adding extra app flag Apr 28, 2025
@nojaf nojaf mentioned this pull request Apr 28, 2025
@nojaf nojaf changed the title WIP - Preserve JSX by adding extra app flag [WIP] - Preserve JSX by adding extra app flag Apr 30, 2025
@nojaf nojaf force-pushed the extra-app-data branch from a58f8c9 to d0bfedc Compare May 3, 2025 10:38
Copy link

pkg-pr-new bot commented May 3, 2025

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript-lang/rescript@7387

@rescript/darwin-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-x64@7387

@rescript/linux-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-arm64@7387

@rescript/win32-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/win32-x64@7387

@rescript/linux-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-x64@7387

@rescript/darwin-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-arm64@7387

commit: 90b28f0

@nojaf
Copy link
Collaborator Author

nojaf commented May 3, 2025

@cknitt you can test this by:

npm i "https://pkg.pr.new/rescript-lang/rescript@aa94f6fdbed6156c46ac4511fa1029a8fbcd553d"

rescript.json:

  "suffix": ".res.jsx",
  "jsx": {
    "version": 4
  },
 "bsc-flags": [
    "-bs-jsx-preserve"
  ]

(suffix not strictly required)

@nojaf
Copy link
Collaborator Author

nojaf commented May 3, 2025

@cristianoc, this is now functioning effectively. Could you please review this approach? If it seems reasonable, we can refine it.

@nojaf nojaf requested a review from cristianoc May 3, 2025 14:05
Copy link
Collaborator

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good.
Main question is if there's a way to avoid extending every Lprim with a boolean, and the scope of changes can be restricted somehow.

@@ -255,6 +255,9 @@ let buckle_script_flags : (string * Bsc_args.spec * string) array =
( "-bs-jsx-mode",
string_call ignore,
"*internal* Set jsx mode, this is no longer used and is a no-op." );
( "-bs-jsx-preserve",
unit_call (fun _ -> Js_config.jsx_preserve := true),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just use set instead of unit_call

@@ -294,6 +297,7 @@ let rec exprToContextPathInner ~(inJsxContext : bool) (e : Parsetree.expression)
funct = {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes};
args = [(Nolabel, lhs)];
partial;
transformed_jsx;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps give a name to the inline record passed to Pexp_apply in the pattern match, and here only mention the changed cases (so no mention of partial and transformed_jsx).

@@ -524,6 +524,48 @@ and expression_desc cxt ~(level : int) f x : cxt =
when Ext_list.length_equal el i
]}
*)
| Call
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps add comment to explain what this does

let fields = ("key", key) :: fields in
print_jsx cxt ~level f fnName tag fields
| _ ->
expression_desc cxt ~level f
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this code branch ever executed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is, it means we missed something. Could we add some sort of assert in this case?

let translate_ffi (cxt : Lam_compile_context.t) arg_types
(ffi : External_ffi_types.external_spec) (args : J.expression list)
~dynamic_import =
let translate_ffi ?(transformed_jsx = false) (cxt : Lam_compile_context.t)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we get transformed_jsx true sometimes when compiling an external call?
Can't remember if the jsx ppx produced external calls.

(E.js_global "import")
[E.str path]

let wrap_then import value =
let arg = Ident.create "m" in
E.call
~info:{arity = Full; call_info = Call_na}
~info:{arity = Full; call_info = Call_na; call_transformed_jsx = false}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible cleanup: define some common info values to avoid all these repetitions.

@nojaf
Copy link
Collaborator Author

nojaf commented May 4, 2025

@cristianoc could you check e30cab3 and 773124f as well. Had to write some new to deal with prop spreading.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants