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
Crank Native [and other custom Crank renderers] #11
Comments
Exciting! Unfortunately, I decided not to follow the React renderer API in its current form. Rather I use generators to keep track of stateful DOM nodes and update them per commit. Essentially you need to create a subclass of If that was unclear, check out the implementation of the DOM and HTML renderers. Also, use TypeScript and follow along with the types I guess. I have to warn you, this is the API that I’m least sure about, especially because it’s crucial to performance, and I may end up going back to a React-like API. But I really enjoy the flexibility of the current approach with generators, so if you’d like to give a try with NativeScript I’d love to see what you produce. |
@brainkim Thank you for the thorough details! 😊 I've completed the renderer by producing an equivalent to However, I've noticed that the main library, Does Here's the renderer as it stands – should help frame the discussion: |
@brainkim I've had another look at the DOM shim just now and it's not too scary. If it would save you a whole bunch of refactoring, then I'll migrate to that instead. Of course, if the renderer is all you need, then we may already be good to go! That's all for tonight, however! |
Haha
I’m not sure I understand. The |
@brainkim Thank you very much 🙏 I feel that it's very hard to turn the heads of the React and React Native communities (to say the least). Calling it Crank Native is a conscious effort to turn heads from React Native devs as people may simply not know what NativeScript is. They'll find out soon enough though 😎
That's a good sign. Cmd-clicking the Element typing takes me to I'll try starting up a Crank Native sample app and just seeing what happens... if it works first try, we'll have some champagne to pop open 😄 |
Ahaha @brainkim I got it working Going off for dinner now... |
Nothing other to say that this is very darn impressive @shirakaba ! Amazing work 🙌 |
@marvinhagemeister Thank you very much 😊 @brainkim How is ancestor context implemented? Some examples of what I mean by that follow.
Crank Native will need to implement the following ancestor context: interface AncestorContext {
isInAParentText: boolean,
isInAParentSpan: boolean,
isInAParentFormattedString: boolean,
isInADockLayout: boolean,
isInAGridLayout: boolean,
isInAnAbsoluteLayout: boolean,
isInAFlexboxLayout: boolean,
} The renderer can diagnose and set what the ancestry is each time a child is added; but I think it might be Crank Native's job to pass this information to the renderer at the time of prop updating (this is how React Reconciler does it). Open to any other solutions, and haven't looked too deeply into it yet – just leaving a quick message about this before I go out! |
@brainkim Hold fire; I think I can manage without host context. At the time of setting props on a child, React DOM doesn't pass you the parent instance, whereas Crank does. I never found a use for 'cascading' ancestry context (e.g. knowledge of anything further back than the immediate parent) in React NativeScript, so I'll be fine without an ancestor context system. At best, there may be an argument for memory efficiency (seems a shame to re-evaluate |
@shirakaba I’m super impressed at how much progress you’ve made and how much you’ve figured out on your own! It’s all undocumented so your progress is inspiring! As far as ancestor contexts for intrinsics go, early on I had intrinsic elements participate in the Crank context tree, but this seemed like a waste, insofar as 1. DOM elements have their own ways to communicate up and down the tree and 2. Portal elements mean that the Crank and DOM trees don’t match up exactly. My hope is that intrinsic elements can just figure out the tree on their own without any help from Crank. Additionally, one important thing to think about when writing renderers is that component and intrinsic elements execute in the tree in a fundamentally different manner. Component elements execute in a pre-order traversal of the tree; in other words, parents execute before children. This makes sense because before the component is executed, we don’t actually know what the component‘s children will be, because the component’s children is the values yielded or returned. By contrast, intrinsic elements execute in a post-order traversal of the tree. They wait for their children to finish rendering before performing their work. At the level of the DOM renderer, this means that it actually creates child nodes before parent nodes, and the parent is responsible for managing the children nodes as provided by the hostContext’s children prop. Lemme know if this helps, I’ve been meaning to dive deeper into your implementation but I’m suddenly having to field a lot of questions from a lot of people! Again, thanks for working on this! |
Thank you! You've managed to architect Crank Native as an extraordinarily well-decoupled framework with an incredibly small surface area, so after reading your explanation, actually I was pleasantly surprised at how clear it was to implement all the necessary APIs. And thanks for that explanation on order of rendering! Following that, it's evident that each child is instantiated, and has props set on it, before any parent has been associated with it. This means that implementing ancestor context would be, although maybe not totally impossible, certainly fiddly. I found in the end that my assumptions about needing ancestor context at all were misplaced, however, so luckily I've been able to sidestep the problem. Works for me!
I can see just from the GitHub stars that Crank is really taking off and have enjoyed reading the really thorough responses written out by you on Reddit to the React community so it's all good! |
@brainkim What would be the idiomatic equivalents of |
@brainkim Crank Native now supports event handlers! It also supports CSS (even SCSS), and Font Icons!
Once I know how to handle this, I'll be able to support native iOS/Android navigation 😃 |
What on Earth is going on this is incredible.
Are you talking about intrinsics? Cuz for intrinsic generator functions you can just do work inside the generator exactly where you expect: const env = {
*myIntrinsic(props) {
let el = createElement("whatever");
// “component did mount”
try {
yield el;
for (const newProps of this) {
yield el;
props = newProps;
}
} finally {
// “component will unmount”
}
},
} Crank will call return on intrinsic generators when they unmount so that’s pretty much how you’d do it. If you’re asking about component props, it’d be pretty much the same. |
@brainkim I think I'm not talking about intrinsics – the However, I'm rather wondering how I could translate this particular scenario. Here's a snippet of React NativeScript, using standard React concepts to pass a reference of our rendered Page instance to a Frame upon React NativeScriptimport * as React from "react";
import { $Page } from "react-nativescript";
import { Frame, Page } from "@nativescript/core";
class AppContainer extends React.Component<{ frame: Frame }, {}> {
private readonly pageRef: React.RefObject<Page> = React.createRef<Page>();
componentDidMount(){
this.props.frame.navigate({
create: () => {
const page: Page = this.pageRef.current!;
page.addCssFile("./components/AppContainer.scss");
return page;
}
});
}
render(){
return (
<$Page ref={this.pageRef}>
</$Page>
);
}
} Crank Native/** @jsx createElement */
import { createElement } from "@bikeshaving/crank/cjs/index";
import { Frame } from "@nativescript/core";
export default function AppContainer({ rootView }: { rootView: Frame }) {
console.log(`AppContainer got rootView:`, rootView);
return (
<page>
</page>
);
} ... I'm not really sure what to write to call an API on that EDIT: Note that I am essentially new to generators. I have a vague recognition of them, but have never got on well with them. Hoping that getting used to Crank idioms improves my understanding. |
Some news on another front: I investigated what would happen if I ... And, well, it works! The typings might stop you from writing anything but the simplest of cases (for one thing, it's expecting child elements to be of https://play.nativescript.org/?template=play-react&id=GtKudF&v=10 |
Yeah this was an architecture pattern that was taken from another project I got, that’s why
I guess you saw the issue on refs, but basically one of the solutions there (make it an async generator or yield, refresh, copy). I know it can seem a little hacky but that’s what I got for now and we can discuss there. My one question though is why you pass the Frame as a prop rather than implement it as an intrinsic? I’m looking at the docs and I think it could be a part of the Intrinsic element tree (https://docs.nativescript.org/ui/components/frame) but I’m not too sure about how it would work.
Do you have a contact on the NativeScript I could reach out to? It would be awesome if we could get this working. |
Through this thread I have learned that native script works with React, made by this man I think nobody knows it because there is no doc, not on the official site, nowhere. It is very cool what you do here! |
Thanks, I'm in good contact with the NativeScript team so I'll mention it. Turnaround on changes to Playground has historically been pretty slow, however.
@lishine yup, that's me! 😊
The repo is here and the docs are here. The official site only mentions the officially supported flavours (Core, Angular, and Vue), so the community-made ones (React, Svelte, Crank, and Preact – although the latter one is unfinished) aren't given much visibility beyond blog posts. Hopefully they'll give some more visibility to the community integrations in time.
Thank you! Crank is a very cool renderer and I believe solves a lot of the pain points I've faced in both React NativeScript and React Native (lack of synchronous rendering, for one thing), so I'm interested to explore what a "Crank Native" would feel like in terms of developer experience. |
To keep everyone in the loop about my earlier statement:
I ended up solving this in #33 (comment) thanks to @brainkim's code snippet and a preliminary implementation of React-style refs. |
Updated Playground template now working on Android thanks to @rigor789: https://play.nativescript.org/?template=play-react&id=lwOLY2&v=1 |
I've just finished the documentation site! https://crank-native.netlify.app Once I understand more about usage patterns, it can be fleshed out with tutorials. For now, it just details the capabilities of the renderer. |
@shirakaba Just FYI I’m looking into performance and the renderer API is probably going to have to change at some point unless I can come up with a better algorithm for the If anyone has algorithm chops, this is the stackoverflow issue I created about the problem with |
The Renderer API has been changed in 0.2.0. I need to document it, but essentially, the main takeaway is that creating a generator object for each DOM node is a lot of overhead in terms of memory. Each of the suspended generator objects was a couple 100 bytes for both the iterator and the retained state of the generator, and for a table of 1000 rows in the js-framework-benchmark test, this meant maybe 1 extra MB of memory. Sorry about the breaking changes! I tried to preserve the generator API but I realized that 1. the Renderer API isn’t where I want to do novel things and 2. it was actually suboptimal as an API design, insofar as reducing all the possible work that a renderer needs to do to a single iterator is too coarse grained. Sometimes Crank will need to arrange the children of a parent, sometimes it will need to patch a node, and encapsulating this all within the same iterator was less optimal than just having these be separate methods. Leaving this issue open so I can make sure to document the Renderer API. @shirakaba If you’re not interested in continuing to work on crank-native I can probably update it for 0.2.0 myself. I have some thoughts on the API but haven’t really had a chance to figure out nativescript yet. |
Hi @brainkim, thanks for the heads-up! Sorry for the delay. Sadly I haven't seen much interest in Crank Native (to be fair, there's not exactly a flood of interest even in its big brother, React NativeScript), so I'll be focusing on other projects. No obligation to update it for v0.2.0! |
The custom renderer API is now kinda documented. I’m not happy with the docs, but it should at least provide a reference for building custom renderers. Hopefully, the API shouldn’t change much more in the future, but it might because of performance reasons. @shirakaba No worries about delays or anything haha. You are under no obligations whatsoever. I’m sorry to hear about lack of interest in NativeScript. I’m kinda curious about why the Vue community has seemed to pivot to a wrapper around React Native, for instance. Ultimately, I don’t stress about adoption because at the end of the day, I wrote the framework for myself, and my personal belief is that Crank provides a real competitive advantage, so if people don’t want to use it, it’s their loss. Also I think browser APIs have significantly firmed up in the past couple years, so the benifits that you get from the scale of adoption in terms of finding subtle cross-browser bugs has diminished somewhat. That being said, I do plan on publishing more blog posts eventually, so keep an eye out for that.
I do plan to update crank-native(script) if you don’t get around to it. I just haven’t been doing any mobile development of late and I’m too lazy to install NativeScript stuff right now. Closing for housekeeping purposes, but feel free to use this issue for more thoughts on custom renderers. |
EDIT – Original title: "Is there an API for creating custom renderers?"
If there is one, and provided it's reasonably similar to that of React, then I could probably port React NativeScript (https://github.com/shirakaba/react-nativescript) to it to make an iOS + Android framework (Crank NativeScript).
The text was updated successfully, but these errors were encountered: