-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
✨ Initial PreactBaseElement #25969
✨ Initial PreactBaseElement #25969
Conversation
Hey @rsimha, these files were changed:
Hey @erwinmombay, these files were changed:
|
This pull request introduces 1 alert when merging 9cbb946 into 67d1415 - view on LGTM.com new alerts:
|
build-system/babel-plugins/babel-plugin-transform-simple-object-destructure/index.js
Outdated
Show resolved
Hide resolved
extensions/amp-date-display/0.2/test/validator-amp-date-display.html
Outdated
Show resolved
Hide resolved
0c8d1ea
to
8dc9452
Compare
const data = /** @type {!JsonObject} */ (getDataForTemplate(props)); | ||
useResourcesNotify(); | ||
|
||
return render(data, props['children']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, what are the children
in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a <slot>
JSX node in AMP/Bento mode, and whatever JSX nodes the dev passes in React mode.
); | ||
host.dispatchEvent(event); | ||
|
||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, this does not causes Preact to clear out all DOM parent's children, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct.
* @return {*} TODO | ||
*/ | ||
function AmpDateDisplayComponent(props) { | ||
const render = props['render']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't JsonObject
there to be able to just use dot property access, object pattern, etc? This might matter I think also because there sometimes d.ts
files used to define the props and thus they could be open to minification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsonObject
is the opposite, if forces computed foo['bar']
access. As a public interface, I think it's required that these never be minified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But props are really meant to be compilable symbols. Even further, I've recently seen mainly the following form used and find it very helpful:
- function AmpDateDisplayComponent(props) {
- const { render } = props;
+ function AmpDateDisplayComponent( { render } ) {
It really helps to get the shape of the component at a glance.
We don't necessarily need to "get there" on the first PR. But I think we need to have a general agreement on the direction.
*/ | ||
function AsyncRender(props) { | ||
const children = props['children']; | ||
const {0: state, 1: set} = useState(children); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just array form?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array destructuring requires the iterator protocol (which also requires symbols). Using object destructuring will be smaller for legacy builds, and faster all the time.
* @param {!JsonObject} props | ||
* @return {*} TODO | ||
*/ | ||
function AmpDateDisplayComponent(props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nit, but I'd like the React-only stuff to go into a separate module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/preact-base-element.js
Outdated
* limitations under the License. | ||
*/ | ||
|
||
import {Deferred} from './utils/promise'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about this one?
src/preact-base-element.js
Outdated
/** @override */ | ||
isLayoutSupported(layout) { | ||
const layoutSupported = opts.isLayoutSupported; | ||
return layoutSupported ? layoutSupported.call(this, layout) : true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. It's ok to delay this to followup PRs, but let's touch base one why this shouldn't just be a simple class extends way. Is it only because of props?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just haven't converted it yet. We can immediately change it in a follow up.
export function getAmpContext() { | ||
return ( | ||
context || | ||
(context = createContext({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense for us to do an exception for side-effects in this case? I'm thinking this class is part of the AMP's React base that could be exportable as it's own library. So the closer we could get to the canonical state for React the better for these modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For use internally, this will be no different. If we export it externally, we could do a code mod? But either way, they'll be different context instances in different modules, and changing it doesn't help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. So I'm wondering why not just allow side-effects in this case to make this more-or-less a classical Preact/React-style declaration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of my goals this year is to eliminate all side-effects, so that transitive dependencies don't accidentally cause a bunch of unintentional headaches. So, I'd rather we leave this as a function call.
src/preact/utils.js
Outdated
/** | ||
* @return {function()} | ||
*/ | ||
export function useRerenderer() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove this for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
f3257a5
to
c9a65e9
Compare
{current: element.getRootNode().getElementById(value)} | ||
: value; | ||
props[name] = v; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: What do you think about flattening the ternary into a series of else-ifs?
if (value == null) {
props[name] = def.default;
} else if (def.type == 'number') {
props[name] = Number(value)
} else if (def.type == 'Element') {
// TBD: what's the best way for element referencing compat between
// React and AMP? Currently modeled as a Ref.
props[name] = {current: element.getRootNode().getElementById(value)}
} else {
props[name] = value;
}
export function getAmpContext() { | ||
return ( | ||
context || | ||
(context = createContext({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. So I'm wondering why not just allow side-effects in this case to make this more-or-less a classical Preact/React-style declaration?
src/preact/slot.js
Outdated
* @param {string} attr | ||
* @param {boolean} on | ||
*/ | ||
function toggleAttribute(element, attr, on) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should already be in dom.js
. I think I stole it from there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/preact/slot.js
Outdated
* @param {*} o2 | ||
* @return {boolean} | ||
*/ | ||
function objectsEqualShallow(o1, o2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move to object.js
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
export function DateDisplay(props) { | ||
const render = props['render']; | ||
const data = /** @type {!JsonObject} */ (getDataForTemplate(props)); | ||
useResourcesNotify(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still struggling this this call. I feel like if we constraint it strictly to only be in the RenderDomTree
that should be sufficient? Then we have a nice separation of concerns: DateDisplay
functions more or less strictly within the React-style. Whole RenderDomTree
adopts it to AMP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, yes.
But calling useResourcesNotify()
will be a requirement for every Component, whether or not they actually rendering anything. It's because we'll be adding a lint rule for it, and there's no way for me to tell from the linter whether an DOM mutation could occur.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is ok to start this way. But I'll still be asking and looking for something more composite here. We have pretty clear touchpoints where we deal with actual light/shadow DOMs between Slot
and RenderDomTree
. Also, per our React prototype, it's very likely that we will decide to wrap all/most of AMP elements into some form of a Root
component to preserve the common layout semantics. The bottom line: let's roll with it for now, but we'll likely come back to it again.
* @param {!JsonObject} props | ||
* @return {null} | ||
*/ | ||
export function RenderDomTree(props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we move this to src/preact/
somewhere along with AsyncRender
? These seem to be important classes to support a particular type of our components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to wait until we had other components to move it. Yes, it'll likely end up there.
* @return {*} TODO | ||
*/ | ||
function AmpDateDisplayComponent(props) { | ||
const render = props['render']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But props are really meant to be compilable symbols. Even further, I've recently seen mainly the following form used and find it very helpful:
- function AmpDateDisplayComponent(props) {
- const { render } = props;
+ function AmpDateDisplayComponent( { render } ) {
It really helps to get the shape of the component at a glance.
We don't necessarily need to "get there" on the first PR. But I think we need to have a general agreement on the direction.
export function DateDisplay(props) { | ||
const render = props['render']; | ||
const data = /** @type {!JsonObject} */ (getDataForTemplate(props)); | ||
useResourcesNotify(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, yes.
But calling useResourcesNotify()
will be a requirement for every Component, whether or not they actually rendering anything. It's because we'll be adding a lint rule for it, and there's no way for me to tell from the linter whether an DOM mutation could occur.
* @param {!JsonObject} props | ||
* @return {null} | ||
*/ | ||
export function RenderDomTree(props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to wait until we had other components to move it. Yes, it'll likely end up there.
export function getAmpContext() { | ||
return ( | ||
context || | ||
(context = createContext({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of my goals this year is to eliminate all side-effects, so that transitive dependencies don't accidentally cause a bunch of unintentional headaches. So, I'd rather we leave this as a function call.
src/preact/slot.js
Outdated
* @param {*} o2 | ||
* @return {boolean} | ||
*/ | ||
function objectsEqualShallow(o1, o2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/preact/slot.js
Outdated
* @param {string} attr | ||
* @param {boolean} on | ||
*/ | ||
function toggleAttribute(element, attr, on) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* @return {Preact.Renderable} | ||
*/ | ||
export function DateDisplay(props) { | ||
const render = props['render']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continuing #25969 (comment):
But props are really meant to be compilable symbols.
We cannot change this. If we allow non-string access, then mangling will break the components between the amp-to-react boundary. Eg, we retrieve the display-in
attribute, and set it to props['displayIn']
as part of collect props. But Closure will mangle the const { displayIn } = props
that it receives in the Preact component (to QD
, in my case). So we get undefined
instead of the value we needed.
// Collect props does this:
const props = { ['displayIn']: el.getAttribute('display-in')};
// Preact does this due to mangling:
const displayIn = props.QD;
Even further, I've recently seen mainly the following form used and find it very helpful:
We can still do destructuring, if you'd like:
const { 'render': render } = props
It just has to use string keys and not the shorthand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I see your point. But it's so unidiomatic. What's our long-term approach here? Can we instruct closure to ignore minification for props? For these modules in their entirety? Should we transform these modules to {render}
to {'render': render}
before feeding them to Closure? I.e. let's take the step we need now, but what's our north star here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see two solutions:
-
We transform any
props
access to string form before closure. This has the downside that anyfoo(props)
call must also useprops
as the parameter name, which is extremely difficult to lint for. We may be able to get away with it if we force the type to still be a!JsonObject
, but then our devs will sometimes have to write string keys forJsonObject
s and sometimes not. -
We extern the props definition for every component. I think this is actually the better solution. It just requires us to locate the extern file somewhere else, which is annoying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO, I agree. I think either solution (even maybe converting d.ts to externs) are good for what we want. React-style is a bit of a different domain language, especially if we allow jsx. So special transformers are not a crazy idea for certain.
But any major concerns about moving too far away from the "canonical" React style for this? E.g. we could explicitly allow this use for contexts and nothing else. |
For this, I don't see |
This plugin transforms the collquial name into the fully qualified import path. This is necessary for the new preact integrations, since `preact/hooks` actually does `import * from 'preact'`. We can't directly import `preact` like that, it has to be the `preact/dist/preact.module.js` path so Closure will work correctly.
7e74e2e
to
5b60af5
Compare
Certainly in this case it doesn't matter. We should even simply create a static hook
|
No description provided.