Skip to content

🏗 Allow rendering plain DOM using JSX#36640

Merged
alanorozco merged 12 commits intoampproject:mainfrom
alanorozco:jsx-in-stories
Oct 31, 2021
Merged

🏗 Allow rendering plain DOM using JSX#36640
alanorozco merged 12 commits intoampproject:mainfrom
alanorozco:jsx-in-stories

Conversation

@alanorozco
Copy link
Copy Markdown
Member

@alanorozco alanorozco commented Oct 27, 2021

Related to #36459

Provides a replacement for existing rendering methods that's more powerful and convenient:

JSX imperative DOM htmlFor simple-template (Stories)
like HTML 🚫 🚫
dynamic attrs 🚫
dynamic children 🚫 🚫 sort-of
event handlers 🚫 🚫 🚫
nesting 🚫 🚫 🚫

A minimal implementation of JSX is included as #core/dom/jsx.

This library adds ~0.22K of size to any given bundle. By replacing callsites of previous rendering methods, the overall size of a bundle is roughly neutral or smaller.

This change does not change any implementation code. A migration of existing templates and renderers will follow-up.

}

return drawerEl;
return <BlockingNotification logoSrc={logoSrc} />;
Copy link
Copy Markdown
Member Author

@alanorozco alanorozco Oct 27, 2021

Choose a reason for hiding this comment

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

It's optional to enable this syntax by using Capitalized function names and taking a props object. Conversely, renderNotificationElement() is called as a normal function below.

Using <Component> syntax does come with some niceties, like the ability to nest components and receive children.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still reviewing, but do we provide the ESLinting rules that would make these situations super clear and less prone to subtle errors?

e.g someone attempts to return <blockingNotification> instead of <BlockingNotification>.

Not a blocker, but good to enhance quickly if we dont already provide.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Tracking on #36679

@alanorozco alanorozco force-pushed the jsx-in-stories branch 2 times, most recently from 9aa01dd to 8845b51 Compare October 28, 2021 16:13
@alanorozco alanorozco marked this pull request as ready for review October 28, 2021 16:13
@amp-owners-bot amp-owners-bot bot requested a review from jridgewell October 28, 2021 16:13
@amp-owners-bot
Copy link
Copy Markdown

amp-owners-bot bot commented Oct 28, 2021

Hey @erwinmombay! These files were changed:

build-system/eslint-rules/preact.js

Hey @jridgewell! These files were changed:

build-system/eslint-rules/preact.js
src/core/dom/jsx.js

Copy link
Copy Markdown
Contributor

@gmajoulet gmajoulet left a comment

Choose a reason for hiding this comment

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

Thank you for writing an example this is super helpful!

<div class="i-amphtml-story-access-header">
<div
class="i-amphtml-story-access-logo"
style={logoSrc ? `background-image: url(${logoSrc});` : null}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is awesome 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1000
This makes templating so easy/readable.

@gmajoulet
Copy link
Copy Markdown
Contributor

Are we expecting any major diff in bundle size?

Copy link
Copy Markdown
Contributor

@processprocess processprocess left a comment

Choose a reason for hiding this comment

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

This is awesome.
Approved with a couple of nits/questions.

@processprocess
Copy link
Copy Markdown
Contributor

Are we expecting any major diff in bundle size?

A previous comment from @alanorozco :

Tradeoff in libraries is maintenance vs. size.

  • jsx-dom is imported from npm and weighs ~1.8k compressed
  • #utils/jsx-dom is ours (untested) and weighs ~0.3k compressed

We likely don't need most of what jsx-dom provides, particularly attribute tables if we ensure names are correct via linting (we already do this for Preact).

We will go with the jsx-dom util, so it will add ~0.3k compressed.
We will likely be able to refactor and delete code to make up for that. We can remove so many querySelectors now :)

@alanorozco
Copy link
Copy Markdown
Member Author

alanorozco commented Oct 28, 2021

@processprocess We will likely be able to refactor and delete code to make up for that.

More on this:

  • I'll remove changes to amp-story before submitting this PR, so there will be no actual impact. Since this is mainly meant for a new extension bundle, it's a clean slate.

  • If we migrate all templates in amp-story/ to JSX (replacing simple-template, htmlFor and createElementWithAttributes), the total compressed size decreases by ~0.2k. This is while leaving the content of the templates intact, not even refactoring to take advantage of JSX features! There's more room to improve if we refactor them.

In short, bundles that use this get a ~0.3k size penalty. But since it can replace htmlFor and createElementWithAttributes, and generates very compressible trees, size is neutral or smaller in a real world scenario.

@alanorozco alanorozco requested a review from samouri October 28, 2021 17:13
@alanorozco alanorozco changed the title 🏗 Use JSX to render DOM in amp-story 🏗 Render DOM using JSX in directories matching /amp-story* Oct 28, 2021
Copy link
Copy Markdown
Member

@samouri samouri left a comment

Choose a reason for hiding this comment

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

This is super cool. Almost all LGTM, just had a few questions mostly FMI.
Definitely wait for a review from @jridgewell though, since I know he's also implemented his own version of jsx and is a SME here

return;
}
if (name.startsWith('on') && name.length > 2) {
element.addEventListener(name.toLowerCase().substr(2), value);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

note: I double checked to confirm that all event names are lower case 👍

it('attaches event handlers from attributes', () => {
const onFooBar = env.sandbox.spy();
const element = <div onFooBar={onFooBar} />;
expect(element).to.not.have.attribute('onfoobar');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

awesome. I wanted to ensure that <div onclick={onClick}/> wouldn't result in registering two event handlers, but since you don't keep the attribute in the final output, LGTM

Copy link
Copy Markdown
Contributor

@kristoferbaxter kristoferbaxter left a comment

Choose a reason for hiding this comment

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

Seems good to me, but I'd like to see @jridgewell's review too before proceeding.

* @return {!Element}
*/
const getNotificationTemplate = (element) => {
return htmlFor(element)`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we like this approach, a cleanup PR for the entire repo would be a great first task.

}

return drawerEl;
return <BlockingNotification logoSrc={logoSrc} />;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still reviewing, but do we provide the ESLinting rules that would make these situations super clear and less prone to subtle errors?

e.g someone attempts to return <blockingNotification> instead of <BlockingNotification>.

Not a blocker, but good to enhance quickly if we dont already provide.

* These features are omitted for the sake of bundle size:
*
* 🚫 Attribute names are not re-mapped.
* - Use standard HTML attribute names on elements (`class`, not `className`).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we plan to use this long term, a followup could include linting rules to prevent these features from being accidentally relied on.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Tracking: #36679

@@ -1,3 +1,4 @@
import * as Preact from '#core/dom/jsx';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: I'm a bit worried we'll confuse this with actual Preact (and the ability to diff). Can we use a different name and @jsxPragma?

// input
/** @jsx StaticTree.createElement */

<div/>



// output
StaticTree.createElement("div");

Babel

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Using JsxStaticTree, with the rule enforcing Jsx if not Preact for the sake of simplicity. (Without the prefix we'd need to parse the pragma annotation, which would be fragile.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I really wanted to do this but Closure is not playing nice.

WARNING - [JSC_BAD_JSDOC_ANNOTATION] Parse error. illegal use of unknown JSDoc tag "jsx"; ignoring it.
Place another character before the @ to stop JSCompiler from parsing it as an annotation.

Tracking: #36680

@alanorozco alanorozco changed the title 🏗 Render DOM using JSX in directories matching /amp-story* 🏗 Allow rendering plain DOM using JSX Oct 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants