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

Hydrate JSX-Templates on the Client #41

Closed
MrAvantiC opened this issue Apr 23, 2019 · 11 comments
Closed

Hydrate JSX-Templates on the Client #41

MrAvantiC opened this issue Apr 23, 2019 · 11 comments
Assignees
Labels

Comments

@MrAvantiC
Copy link

Hi Dennis! :)

Is your feature request related to a problem? Please describe.
We've been using UIEngine with the JSX-Adapter. So far, the Adapter renders the JSX-Code into static html and outputs them.
This is fine for simple components that only receive props and don't offer much interactivity, yet we run into trouble once there is functionality that is supposed to run on the client.

Describe the solution you'd like
To support client-side functionality we would need to move from "using JSX as a templating engine" to also "hydrate the components on the client".
I'm not really sure how to go about this to be honest.

Let's consider the following, simple component:

import React, { Component } from 'react'

class Heading extends Component {
  componentDidMount() {
    console.log('component says hi')
  }

  render() {
    return (
      <h1>Hello World</h1>
    )
  }
}

export default Heading

From what I understand, the JSX-Adapter is applied on every variant, and each variant (or rather the output for it) is separate from all other components/variants in its own html-file.
When reading about this subject it appears that - in order to cause hydration for a component - you'd need to call:

const app = document.getElementById( "app" )
ReactDOM.hydrate( <Element /> , app )

Now, to bring these two points together, my impulse is that the JSX-Adapter is the right place to generate this code since it's the only part that knows which component it is currently rendering and therefore needs to hydrate on the client.

And this is where I begin to wonder.
Of course, If i just put the code into a template-literal and output it as a script-tag on the client, it won't work since the Browser doesn't know about React or JSX.

So, I tried transpiling the template-literal itself with babel:

  let Element = require(filePath)

  const code = `
      const Element = ${Element}
      const app = document.getElementById( "app" )
      ReactDOM.hydrate( <Element /> , app )
  `

  const transformed = await babel.transformAsync(code, {
    filename: filePath,
  })
  const hydration = transformed.code

  return (
    html +
    `
    <script type="text/javascript">
      ${hydration}
    </script>
  `
  )

Unfortunately, the output now contains code like

var _react2 = _interopRequireDefault(require("react"));

...which the browser doesn't understand either because of the require.

At this point it starts to feel like a rabbit hole for me and I'm no longer sure whether this is the right direction to go.

Do you have any opinion on how to tackle this problem?

Regards,
René

@dennisreimann
Copy link
Owner

We started implementing a solution for this in our recent project. I‘ll get back to finalize this when I find the time to do so.

The process looks like this: the adapter will be changed to take the webpack config of the project. Besides having the benefit of using the actual project setup and not some process that‘s implemented separately in the adapter, we can also generate an entry for each variant. This can be used on the client to achieve what you are looking for.

Fun fact: As far as the theory goes this should also lead to a webpack-adapter that can be used with whatever templating language and extensions you like (i.e. styled-components).

@MrAvantiC
Copy link
Author

Cool, good to know you are working on something along those lines already.
Do you have a timeframe for when this could be expected? Is it a matter of days/weeks or rather months? :)

@dennisreimann
Copy link
Owner

I have a proof of concept working for Vue.js and I will publish an early draft once I finish the tests including a React example. This won't be production ready from the get-go, but we can use it to work the kinks out. (i.e. property extraction etc. will need to be added, as the adapter doesn't know anymore which kind of templating engine is used as webpack handles all of that in this case)

I hope to have something up by the end of this week so that we can try and progress it together.

@dennisreimann
Copy link
Owner

Just pushed the first iteration, which works with Vue and React and has sample code for these: e97e624

I'll make a new release as soon as possible, until then you can take a look at the test project code (see the lib and webpack folders) and the docs to learn about the details.

I'm leaving this open so that we can discuss your feedback here. Currently I'm not considering this really production ready, but we can surely progress it there as there are also other projects that will be using this adapter soon.

@dennisreimann
Copy link
Owner

@MrAvantiC Did you find the time to check this out?

@MrAvantiC
Copy link
Author

Hey,
had a couple of days vacation, hope to get to it next week! Sorry!

@dennisreimann
Copy link
Owner

The webpack adapter is working now and features this, so I‘m closing this feature request.

Repository owner deleted a comment from andyman3000 Jul 1, 2019
@MrAvantiC
Copy link
Author

Awesome, thanks Dennis!

@MrAvantiC
Copy link
Author

Hey @dennisreimann ,
I finally got around to check out the test project in your repository (sorry it took that long).
But I think I'm still missing something.
I cloned the repository and modified src/elements/label/label.jsx like this:

class Label extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <label className="label" htmlFor={this.props.for}>
        {this.props.title}

        <div>{this.state.count}</div>

        <button type="button" onClick={() => this.setState({ count: 10 })}>
          Click
        </button>
      </label>
    );
  }
}

But when I click on the button it doesn't update the state.
Am I still missing some config here?

Regards.

@dennisreimann
Copy link
Owner

@MrAvantiC yes, my bad. There has been a switch in the test projects' config to use either the webpack or (old) react adapter for jsx files, which defaulted to the latter.

Removed it here, so that it's working with your example: 518c05f#diff-2434032cd7013177f44dc1f1dd780a8fL16

Please pull the latest master and try again, sorry for the confusion!

@MrAvantiC
Copy link
Author

Great, it's working now, thanks for the quick response!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants