Use virtual-dom + main instead of VTree + ports #23

Closed
wants to merge 17 commits into
from

Conversation

2 participants
@yusefnapora
Contributor

yusefnapora commented Mar 6, 2016

Hey, I had some time today to play with this issue I opened yesterday, and managed to get things working. It involves quite a few nasty hacks, but comes with some nice benefits :)

The big change is the replacement of the VTree type with VirtualDom.Node, which is aliased as ReactNative.Node. This allows you to return a signal of ReactNative.Node from main and avoid the ports hack. Since we don't have to go through ports, you can attach properties that are not directly json encodable, so you can attach event handlers directly as properties, using the same technique as in elm-html.

Styles are also attached as a style object property, rather than being special-cased.

Now on to the hacks :)

First, including the virtual-dom package causes the React Native packager to fail, since virtual-dom is built with browserify, which defines a require function. The packager overrides this with its own require implementation and gets confused. So I added a perl one-liner to the postcompile npm run script to replace require with _browserify_require in the compiled elm.js.

Next, to actually get main to render, I defined a super barebones global.document object, so that Elm.fullscreen won't fail with a bunch of undefined references.

In the componentWillMount hook of the main AppWrapper component, I'm patching Elm.Native.VirtualDom.make to override the render and updateAndReplace functions with an implementation that just calls setState on the AppWrapper instance, sending in the new root virtual-dom node.

After patching the runtime, componentWillMount calls Elm.fullscreen.

Then a vdomToReactElement function in AppWrapper.render recursively converts from virtual-dom node to react element, just like vtreeToReactElement did before.

There's still plenty of ways this could be improved, but at least now we know it's possible :)

Hopefully I'll have time to pull the rendering hacks out into their own module and wire up Android support tomorrow.

@yusefnapora

This comment has been minimized.

Show comment
Hide comment
@yusefnapora

yusefnapora Mar 7, 2016

Contributor

The last couple of commits add Android support and limit the scope of the require rename hack to just the VirtualDom.js file in the virtual-dom package. That seems much less invasive than altering the compiled elm.js.

There's still some cleanup stuff to be done; the Native.ReactNative module is actually not used anymore, so it could be removed. And I think that the init port can be removed as well... might not have time to work on this during the week though.

Contributor

yusefnapora commented Mar 7, 2016

The last couple of commits add Android support and limit the scope of the require rename hack to just the VirtualDom.js file in the virtual-dom package. That seems much less invasive than altering the compiled elm.js.

There's still some cleanup stuff to be done; the Native.ReactNative module is actually not used anymore, so it could be removed. And I think that the init port can be removed as well... might not have time to work on this during the week though.

yusefnapora added some commits Mar 7, 2016

modify compiled elm.js atomically
If hot reloading is turned on, the packager gets confused when we
remove elm.js in the precompile phase.  So instead we compile to
an intermediate file first, append the `module.exports = Elm`, then
rename that to elm.js
@yusefnapora

This comment has been minimized.

Show comment
Hide comment
@yusefnapora

yusefnapora Mar 7, 2016

Contributor

Realized over lunch that using VirtualDom.Node as the output type means you can use StartApp directly 😃

Contributor

yusefnapora commented Mar 7, 2016

Realized over lunch that using VirtualDom.Node as the output type means you can use StartApp directly 😃

@yusefnapora

This comment has been minimized.

Show comment
Hide comment
@yusefnapora

yusefnapora Mar 10, 2016

Contributor

man, this is too fun :)

I pulled elm-effects into the mix with the last commit and implemented the random gif viewer from the elm architecture tutorial.

I had to add the addEventListener method to the XMLHttpRequest prototype to get elm-http to work, but now you can request a random cat gif from your phone with elm!

The XHR shim is incomplete; it doesn't handle timeout or progress events. Also, I still haven't figured out a clean solution for local images, since they need to be statically require'd for the react packager to pick them up. So there's no loading spinner.

I'm thinking for bundled images (and custom javascript components, etc) you'll probably have to require them in javascript, maybe in an ElmExports module or something. Then you could have a Native-backed bundledImage function that looks things up in that module, given a string key. vdomToReactElement could look there too, but you'd have to somehow indicate that you want to look in there instead of in the React module.

Maybe the hypothetical ElmExports object could always have a React member, so vtreeToReactElement could look up all component classes in there using something like lodash's get function, which will look up nested object paths, e.g, get(someObject, "foo.bar.baz").

Basically I'm thinking it'd look like this:

export default const ElmExports = {
  React: require('react-native'),

  AppComponents: {
    MySpecialComponent: require('./components/MySpecialComponent')
  },

  Images: {
    'foo.png': require('./images/foo.png'),
    'bar.gif': require('./images/bar.gif'),
  }
};

Then from elm, components that are included in React could be defined with e.g.

image : List Property -> List Node -> Node
image =
  node "React.Image" 

and you could get to MySpecialComponent with

special : List RN.Property -> List RN.Node -> Node
special =
  RN.node "AppComponents.MySpecialComponent"

then when we map the vdom nodes to react elements you'd just do

let componentClass = _.get(ElmExports, vdomNode.tagName);
return React.createElement(componentClass, props, children);

getting the bundled images out might require a Native-backed elm function... not sure yet.

Contributor

yusefnapora commented Mar 10, 2016

man, this is too fun :)

I pulled elm-effects into the mix with the last commit and implemented the random gif viewer from the elm architecture tutorial.

I had to add the addEventListener method to the XMLHttpRequest prototype to get elm-http to work, but now you can request a random cat gif from your phone with elm!

The XHR shim is incomplete; it doesn't handle timeout or progress events. Also, I still haven't figured out a clean solution for local images, since they need to be statically require'd for the react packager to pick them up. So there's no loading spinner.

I'm thinking for bundled images (and custom javascript components, etc) you'll probably have to require them in javascript, maybe in an ElmExports module or something. Then you could have a Native-backed bundledImage function that looks things up in that module, given a string key. vdomToReactElement could look there too, but you'd have to somehow indicate that you want to look in there instead of in the React module.

Maybe the hypothetical ElmExports object could always have a React member, so vtreeToReactElement could look up all component classes in there using something like lodash's get function, which will look up nested object paths, e.g, get(someObject, "foo.bar.baz").

Basically I'm thinking it'd look like this:

export default const ElmExports = {
  React: require('react-native'),

  AppComponents: {
    MySpecialComponent: require('./components/MySpecialComponent')
  },

  Images: {
    'foo.png': require('./images/foo.png'),
    'bar.gif': require('./images/bar.gif'),
  }
};

Then from elm, components that are included in React could be defined with e.g.

image : List Property -> List Node -> Node
image =
  node "React.Image" 

and you could get to MySpecialComponent with

special : List RN.Property -> List RN.Node -> Node
special =
  RN.node "AppComponents.MySpecialComponent"

then when we map the vdom nodes to react elements you'd just do

let componentClass = _.get(ElmExports, vdomNode.tagName);
return React.createElement(componentClass, props, children);

getting the bundled images out might require a Native-backed elm function... not sure yet.

@ohanhi

This comment has been minimized.

Show comment
Hide comment
@ohanhi

ohanhi Mar 12, 2016

Owner

Wow, @yusefnapora, you've really put some effort into this! I really appreciate the work you've done here.

I think I will go with the modified compiler thing however, since that's what Evan suggested when I talked with him. Most of your work should be very easily adaptable to that solution, though, since it would also use the main directly. So if you don't mind, I will pursue that goal for now.

I would love it if you can take your work on the modified compiler main version -- there's a lot of great stuff on here!

Owner

ohanhi commented Mar 12, 2016

Wow, @yusefnapora, you've really put some effort into this! I really appreciate the work you've done here.

I think I will go with the modified compiler thing however, since that's what Evan suggested when I talked with him. Most of your work should be very easily adaptable to that solution, though, since it would also use the main directly. So if you don't mind, I will pursue that goal for now.

I would love it if you can take your work on the modified compiler main version -- there's a lot of great stuff on here!

@yusefnapora

This comment has been minimized.

Show comment
Hide comment
@yusefnapora

yusefnapora Mar 12, 2016

Contributor

Thanks! I think the modified compiler + elm-core route is probably the way to go for the long term. I'm not crazy about the hacks involved in this branch, and using virtual-dom kind of feels like lying to the compiler :) I'll start experimenting with that and see if I can come up with a sensible rendering method for VTree.

Contributor

yusefnapora commented Mar 12, 2016

Thanks! I think the modified compiler + elm-core route is probably the way to go for the long term. I'm not crazy about the hacks involved in this branch, and using virtual-dom kind of feels like lying to the compiler :) I'll start experimenting with that and see if I can come up with a sensible rendering method for VTree.

@yusefnapora

This comment has been minimized.

Show comment
Hide comment
@yusefnapora

yusefnapora Mar 13, 2016

Contributor

I'm going to close this and keep chipping away at helping implement #24. I'll leave the branch up on my fork for reference if anyone wants to check it out.

Contributor

yusefnapora commented Mar 13, 2016

I'm going to close this and keep chipping away at helping implement #24. I'll leave the branch up on my fork for reference if anyone wants to check it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment