Skip to content
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

Support JSX fragments with jsxFragmentFactory compiler option and @jsxFrag pragma #38720

Merged
merged 1 commit into from Jun 18, 2020

Conversation

nojvek
Copy link
Contributor

@nojvek nojvek commented May 21, 2020

Reviving: #35392 (which was closed due to inactivity)

Checklist

Problem:

Currently <><Foo /></> only works with "jsx": "react". Using an inline pragma for jsxFactory /** @jsx dom */ or config defined "jsxFactory": "h" throws an error that JSX fragment is not supported when using --jsxFactory

The issue has been open for almost 3 years now.

Proposal Fix:

Very much inspired from babel to keep ecosystem consistent.

https://babeljs.io/docs/en/babel-plugin-transform-react-jsx

  1. jsxFragmentFactory compiler option.

As suggested and reviewed by @weswigham in previous PR.

Babel plugin-transform-react-jsx supports following options

{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "Preact.h", // default pragma is React.createElement
      "pragmaFrag": "Preact.Fragment", // default is React.Fragment
      "throwIfNamespace": false // defaults to true
    }]
  ]
}

Typescript already supports jsxFactory compiler option, this PR adds another optional jsxFragmentFactory compiler option for similar developer UX.

{
  "compilerOptions": {
    "target": "esnext", 
    "module": "commonjs",
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}
  1. support for @jsxFrag pragma. This code would work in both typescript and babel without changes. TS will need jsx: react for emit though.
/** @jsx Preact.h */
/** @jsxFrag Preact.Fragment */

import Preact from 'preact';

var descriptions = items.map(item => (
  <>
    <dt>{item.name}</dt>
    <dd>{item.value}</dd>
  </>
));

@nojvek nojvek force-pushed the nojvek-jsx-fragment-factory branch from 4010afc to 975e7b8 Compare May 22, 2020 00:38
@nojvek
Copy link
Contributor Author

nojvek commented May 22, 2020

@weswigham - would appreciate a review when you get a chance.

Thanks!

@nojvek
Copy link
Contributor Author

nojvek commented May 22, 2020

@weswigham I added a type elision test like you mentioned. Did a bit of debugging but still have little idea how to make the emmiter emit jsxFrag import so we don't end up with this, where snabbdom doesn't have a corresponding require

//// [mix-n-match.js]
"use strict";
exports.__esModule = true;
/* @jsx h */
/* @jsxFrag Frag */
var preact_1 = require("./preact");
preact_1.h(snabbdom_1.Frag, null,
    preact_1.h("span", null));

@weswigham
Copy link
Member

You'll need to mark it as referenced when a fragment that uses it is check'ed in the checker - checkJsxOpeningLikeElementOrOpeningFragment does this. It needs to swap the name it resolves and marks when it's looking at a fragment.

@nojvek nojvek force-pushed the nojvek-jsx-fragment-factory branch from 30a8f17 to df5a194 Compare May 22, 2020 15:48
@nojvek
Copy link
Contributor Author

nojvek commented May 22, 2020

Sweet! I have the type elision scenario passing as a test.

@weswigham, ready for a review 👀 when you are.

@nojvek
Copy link
Contributor Author

nojvek commented May 22, 2020

@typescript-bot pack this

@nojvek
Copy link
Contributor Author

nojvek commented May 22, 2020

I guess @typescript-bot only listens to typescript team 🤷‍♂️

@weswigham
Copy link
Member

@typescript-bot pack this :P

@typescript-bot
Copy link
Collaborator

typescript-bot commented May 23, 2020

Heya @weswigham, I've started to run the tarball bundle task on this PR at df5a194. You can monitor the build here.

@nojvek
Copy link
Contributor Author

nojvek commented May 23, 2020

Please tell me that when a TS team member types @typescript-bot sudo make me a 🥪, something really cool happens. If not, you need to seriously evaluate priorities in the roadmap. Ha!

@typescript-bot
Copy link
Collaborator

typescript-bot commented May 23, 2020

Hey @weswigham, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/74777/artifacts?artifactName=tgz&fileId=4E617F10A295CC8B5DD8371C8F44C745356B0A4C8893BB6AD7964773498EA80F02&fileName=/typescript-4.0.0-insiders.20200523.tgz"
    }
}

and then running npm install.


There is also a playground for this build.

@nojvek
Copy link
Contributor Author

nojvek commented May 28, 2020

@weswigham do you have any ETA when you'll review this or whether it's likely to land in next TS version ?

@weswigham weswigham closed this May 28, 2020
@weswigham weswigham reopened this May 28, 2020
src/compiler/checker.ts Outdated Show resolved Hide resolved
Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

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

I think this looks pretty good now (small note above). I'd like to get another pair of eyes, just to be prudent, and confirm this is OK to merge with @DanielRosenwasser.

@nojvek
Copy link
Contributor Author

nojvek commented May 29, 2020

Kind ping @DanielRosenwasser for 👀

@nojvek
Copy link
Contributor Author

nojvek commented Jun 4, 2020

Kind ping again @DanielRosenwasser for review 👀 ^

@nojvek
Copy link
Contributor Author

nojvek commented Jun 11, 2020

Another kind ping @DanielRosenwasser ^

@weswigham is there anyone else in the TS team that can review this for merge? I have a feeling that this could be a PR that sits for months and eventually ends up being closed. This is my 3rd try trying to make a PR for jsxFragments over the last 2 years.

Please please please don't leave this hanging in limbo.

@DanielRosenwasser
Copy link
Member

Hey, I checked in with a couple of other people on the team. I'm going to raise it for the design meeting agenda tomorrow, but if there's nothing else controversial I think we can get it in by next week for the release.

@DanielRosenwasser DanielRosenwasser added this to Agenda in Design Meeting Docket via automation Jun 11, 2020
@nojvek
Copy link
Contributor Author

nojvek commented Jun 11, 2020

You are the best @DanielRosenwasser. Thank you 🙏

@weswigham
Copy link
Member

@andrewbranch you wanna give this a second review and help get this merged?

Copy link
Member

@andrewbranch andrewbranch left a comment

Choose a reason for hiding this comment

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

Approved, with grammar note. Thanks @nojvek!

"category": "Error",
"code": 17016
},
"JSX fragment is not supported when using an inline JSX factory pragma": {
"An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments.": {
Copy link
Member

Choose a reason for hiding this comment

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

Hopefully this doesn’t turn into the GIF pronunciation debate, but I don’t pronounce the @ character so I think these articles should be “a,” not “an” 🙃

Copy link
Member

Choose a reason for hiding this comment

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

Pretty glad we moved away from "working @microsoft" to "working at @microsoft"

Copy link
Member

Choose a reason for hiding this comment

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

... I pronounce the @. 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When @weswigham first mentioned this as PR comment, I didn't fully agree with him, but when I read it over many times I was internally conflicted which way is the right way. In the end I chose to appease @weswigham 🤷

const jsxFactoryNamespace = getJsxNamespace(node);
const jsxFactoryLocation = isNodeOpeningLikeElement ? (<JsxOpeningLikeElement>node).tagName : node;

// allow null as jsxFragmentFactory
Copy link
Member

Choose a reason for hiding this comment

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

Just curious, what’s the utility of this? I couldn’t find anything about using null in the babel transform docs.

Copy link
Member

@weswigham weswigham Jun 15, 2020

Choose a reason for hiding this comment

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

I think the idea is that some libraries may use a null tag name to indicate a fragment, rather than some specific fragment object. Mithril actually uses '[' as the fragment sentinel nowadays, so we should actually consider supporting arbitrary simple expressions here...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

snabbdom-jsx-lite uses null. https://github.com/nojvek/snabbdom-jsx-lite/blob/master/tsconfig.json#L8

Mostly because if

is compiled to h('div', null). null meaning no attrs,

<> compiles to h(null, null) meaning it's no named tag i.e Fragment. This avoids an extra function call for evaluation of fragment function, which in most cases convert the children to an array.

@weswigham
Copy link
Member

@nojvek would you be able to do a quick resync with master?

@nojvek nojvek force-pushed the nojvek-jsx-fragment-factory branch from 6a7a8c7 to 2d7372a Compare June 16, 2020 02:08
@nojvek nojvek force-pushed the nojvek-jsx-fragment-factory branch from 2d7372a to b950086 Compare June 18, 2020 02:07
@nojvek
Copy link
Contributor Author

nojvek commented Jun 18, 2020

FYI @weswigham, merged with latest master and ensured tests pass locally.

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.

Support JSX-Fragments with custom jsxFactory
5 participants