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

proposal: await dependencies inside component function #31

Open
pemrouz opened this issue Dec 1, 2016 · 7 comments
Open

proposal: await dependencies inside component function #31

pemrouz opened this issue Dec 1, 2016 · 7 comments

Comments

@pemrouz
Copy link
Collaborator

pemrouz commented Dec 1, 2016

Automatic lazy loading of resources during the rendering pipeline works pretty well using the backpressure module. The declarative usage uses the imperative form ripple.pull() under the hood. This is very similar to the dynamic import proposal import() which looks pretty neat, and I think it would be better to align the API with that. In addition, I think this means we should be able to keep everything in one function rather than define the component's dependencies in the needs header (aka getInitialProps). Proposal:

export default async function component(node, state){
  const { import, subscribe } = ripple 
      , moment = await import('moment')
      , events = await subscribe(node)('events')
}
  • import would just load from the core cache, or fetch and load.
  • subscribe would do the same, but also redraw that component when there are updates to the resource.

The spec only mentions how to resolve module specifiers that start with /, ./ or ../ and leaves resolving "bare" import specifiers for later. However, I'm not sure how these paths can ever be interpreted in a universal context (i.e. as files in Node, but URLs in browser?). I think starting with bare specifiers would be a small change, more useful and make the most sense (e.g. import("moment")).

The core is also the equivalent of what the standard calls module map. The placeholder object whilst a fetch is in-flight just needs to be changed to a Promise however.

/cc @rauchg @developit @jordwalke for thoughts following on from this twitter discussion.. do you think this would work?

@pemrouz
Copy link
Collaborator Author

pemrouz commented Dec 1, 2016

Obviously, since we can't use import, perhaps keep pull, and just have one function for both usages?:

  ..
  await pull('moment')        // just load
  await pull('events', node)  // subscribe
  ..

Eventually people could trivially replace (the former) with the real import when it lands..

@rauchg
Copy link

rauchg commented Dec 3, 2016

This is similar to the async render discussion. If you either fetch data or code, you still need to render something back to the user, like a loading spinner.

How do you handle that here?

@pemrouz
Copy link
Collaborator Author

pemrouz commented Dec 4, 2016

Conceptually, just think of the component being able to flush/pipe partial updates to the DOM early, rather than having to do it all at once at the end using the return keyword. Simple example:

export default async (node, data) => {
  const o = once(node)
      , { pull } = ripple
 
  o.classed('is-loading', true)

  const posts = await pull('posts')

  o.classed('is-loading', false)

  o('h1', 1)
    .text('HN')
  
  o('li', posts)
    .text(d => d.title)
}

The component is still declarative and the contract is still a function of data. It just doesn't have to be instantaneous (although it can be too).

I should add: the original reason I didn't like using return was that it necessitates the existence of some reconciler, so you can't use the component standalone. In this case, you can simply require any component, invoke it on any node with some data.


What's even more awesome, is that I just realised using await is a very natural way to pause/resume a component à la Fiber 😱 I added an arbitrary 2 second delay on serving the posts, and another fast ticking counter parallel to the component. The other ticker ticks whilst the main component is paused on loading!

ripple-fiber

Resources are cached offline, so in subsequent refreshes, or on the server where it's always a local cache lookup, you don't see the loading state and the ticker never ticks once.

@3liv
Copy link
Collaborator

3liv commented Dec 4, 2016 via email

@pemrouz
Copy link
Collaborator Author

pemrouz commented Dec 8, 2016

So data dependencies would no longer be passed through data argument of component put declared within using pull/subscribe?

I think we'd keep the data argument, as the parent may pass down data via that. But I think subscribed dependencies should also still be available there so it's the one place the component can get everything it needs. This also means you can skip await pull(resource[, node]) for example if you already have resource from the data parameter.

Subscribe would use generators/yield to cause redraw.

I'm thinking to use the existing mechanism actually: just tag the node with the dependency. Then whenever there is an update to the dependency, it redraws the node. This way, it's just one render function and the component is like the sink of the stream, rather than trying to manage the lifecycle of the stream in the component.

@pemrouz
Copy link
Collaborator Author

pemrouz commented Dec 22, 2016

Fixed in 0.7.0.

/cc @domenic if he has any thoughts/advice on how imports should work in a universal context, i.e: what would happen if a component that is rendered on both server and client uses import?

The spec only mentions how to resolve module specifiers that start with /, ./ or ../ and leaves resolving "bare" import specifiers for later. However, I'm not sure how these paths can ever be interpreted in a universal context (i.e. as files in Node, but URLs in browser?). I think starting with bare specifiers would be a small change, more useful and make the most sense (e.g. import("moment")).

@domenic
Copy link

domenic commented Dec 27, 2016

I'm not sure what the question is exactly. For the foreseeable future, only absolute URLs and specifiers starting with /, ./. and ../ will work on the client (and will be interpreted as relative URLs).

My understanding is that in Node it is still up in the air how specifiers will be interpreted; environments like Babel have picked one interpretation that will probably diverge from whatever is eventually implemented in Node core.

It sounds like this might a transpiler repo, though, in which case the usual course I've seen is for people to just make up semantics for specifiers (like Babel does) instead of using the native ones that are implemented in the browser/will be implemented in Node.

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

No branches or pull requests

4 participants