-
Notifications
You must be signed in to change notification settings - Fork 396
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
Proxy elements - or a marriage of decorators, parser transforms, and partials - covers lightweight, dynamic, and async components - RFC #3106
Conversation
Oh yeah... the |
@evs-chris Is there any way to test it out via Playground or something? |
After sleeping on it, I have another question: should proxies also have the ability to contribute to registries e.g. provide decorators, transitions, and friends? My gut says yes, and we may even want to be able to create an inheritance hierarchy with proxies in a similar manner to components at some point. To allow that at some point, I've adjusted the const thing = Ractive.proxy(handle => ({ template: '{{>content}}' }));
new Ractive({
proxies: { thing },
template: '<thing>I will just render this string</thing>'
}); Any options like Ractive.proxy(
handle => {
// do stuff here
},
{
cssId: 'my-proxy',
css: '.my-styles { color: pistachio; }'
}
); Back around to registries, that would also open up the possibility of blocking proxy names from within the proxy to prevent infinite recursion e.g. with a |
If anyone wants to try out the current iteration of this PR in the playground or somewhere similar without having to build it, I've published it as Note that while I have tested it in the sandbox, actual tests are still on my TODO list pending feedback. |
Here's a playground with a moderately featured async component proxy that pulls its loading message from an optionally supplied There's also a synchronous component in place and commented out so you can see how the async proxy behaves if the component is already loaded. Note: I found a few bugs while playing, so the version is now |
3e2b1bd
to
5319089
Compare
including excluding them from the main template, gathering them to be tracked for the intermediary, providing an extra-attributes partial, and firing them on the inital intermediary call and optionally on each update
I've added claimed attribute support for proxies, which means you can specify that the proxy will consume certain attributes by name, and if they're dynamic, you can supply a callback to be called whenever they change. They are also passed in on the intermediary creation call. const Proxy = Ractive.proxy((handle, args) => {
// args is a map of attribute values that are claimed in attributes below
}, {
attributes: ['name']
}); <Proxy name="{{someName}}" class="foo" /> There, Here's the async component example updated to make the loaded component dynamic based on the |
Been off the grid for 3 days 🎉 Here's an example of the minimalistic API I was thinking of based on the comments from previous discussions (like #3089 (comment)). The gist is that an async component should just be like any other component, albeit loaded and rendered differently. Underlying machinations may be different, but at least authoring experience shouldn't cause a drastic change. // AsyncComponent.js - write in vanilla or component file format
export default Ractive.extend({ ... })
// RegularComponent.js - write in vanilla or component file format
export default Ractive.extend({ ... })
// App.js
import RegularComponent from './RegularComponent'
import Loader from 'some-loader'
// Loader method that returns a function that
// 0. Is called by Ractive when it needs the component.
// 1. Returns a promise.
// 2. Loads the component (Ractive doesn't care how).
// 3. Does some pre-processing (Ractive doesn't care if it does).
// 4. Resolves the promise with the constructor.
// 5. Resolution causes any existing usages to refresh/toggle/magically render
// 6. Ractive caches the constructor, as if it were defined in `components`.
const AsyncComponent = Loader.load('AsyncComponent.js')
const App = Ractive.extend({
components: { RegularComponent },
asyncComponents: { AsyncComponent },
template: `
<RegularComponent />
<AsyncComponent/> <!-- this would re-render if it was already rendered -->
`
})
App({ target: '#app' })
In the above case, since the async component is written just like any other component, I would expect that plugins would still be possible and follow existing rules (i.e. plugins defined in components are component-scoped), CSS would be re-evaluated (i.e. async component CSS appended), that sort of stuff. My 2c |
My main issue with async components as a built-in like that is that there is no way to show a loading placeholder while you wait on the promise to resolve. With That's also ignoring the impl details, which would effectively be building an async proxy plugin directly into ractive to handle the impedance mismatch between the sync render and async load. |
I was thinking of using the component innerHTML but I guess we have plans for using it for yielded content. Hmm... conditional based on the registry and the resolution? 😁
(elegance just left the building, hahaha 😁 ) |
What I'm going to say is probably almost off-topic, but I'm concerned about the naming. Will using the name "proxy" create confusion regarding the proxy events? |
Definitely not off topic - that's one of the main questions to resolve before merging this 😀 |
Referencing related implementation of stuff: |
or - second swing at replacing parser transforms with a runtime construct
or - second swing at replacing parser transforms with a runtime construct
Description:
This adds a new VDOM construct tentatively called a proxy. A proxy is simply a placeholder that gets a context handle that is extended with a method,
refresh
that triggers the proxy to unrender and render again. Proxies look like regular elements in the template, kinda like components look like regular elements. They even automatically get access to a content partial that is the content of the proxied element. Since the template on the proxy is effectively dynamic, you can achieve two common requests for components - dynamic and async - with a proxy.Also, parser transforms are removed.
Details
This is outdated... see 4th post below
For this PR Ractive has grown a new registry called
proxies
(open for suggestions on the name). It has also grown a new static method designed to create a proxy element, conveniently namedproxy
, which takes an init hash like a component would withextend
. The only required param in the init has is aproxy
function, which receives a handle object that is a context with a few bonus methods, notablyrefresh
, which causes the proxy element to rerender. Theproxy
method is expected to return an intermediary instance that is used for communication between the proxy element and the proxy class, kinda like a decorator.That is a proxy that does nothing but render the content passed to the proxy. Not particularly useful, but it's about the simplest example of a proxy that could possibly exist. Since components are checked before proxies, here's what a simple async component proxy would look like:
CSS
Since I also want to target lightweight components as proxies, they also support Ractive's scoped CSS, including CSS functions. This means that you can use the ractive-bin to create lightweight components that are roughly as expensive as partials with scoped CSS in the single-file format. This is actually the main reason that there is a constructor function on the Ractive constructor - so that the styling can get set up and to grant access to
thing.styleSet(...)
.Questions
Question 1: I was pondering an
update
method for the intermediary, kinda like decorators get. There's already aninvalidate
, which if supplied, is called any time the bit of VDOM containing the proxy is told to update. In order forupdate
to work, the proxy would have to defineattributes
that it wants to keep to itself e.g.<Async name="ComponentA" />
could sayattributes: ['name']
and get an additional attrs object in theproxy
method and also any time those attributes updated, an optionally suppliedupdate
method in the intermediary would be called, allowing<Dynamic name="{{.type}}" />
.Note, since the handle is a context object, you can already set up context-aware observers and the like. You can't, however, use Ractive's nice expression support to allow just about any computation to be passed to the proxy e.g.
<Something foo="{{.bar * 10 + 2}}" />
.So, should I add
attributes
and support for anupdate
method?Question 2: Should any unclaimed attributes be supplied in an additional
{{>extra-attributes}}
partial like the one provided to components?Question 3: Along those same lines, the content of the proxy tag is supplied as the
{{>content}}
partial (and yielding means nothing in this context, because it's already in the same context as the surrounding template). Should the entire proxy template also be supplied as maybe{{>initial}}
or{{>outer}}
or{{>outer-content}}
or something like that?Question 4: When added to
attributes
andupdate
support, any attributes claimed by the proxy would be removed from the outer template partial. Does that sound useful, or at that point are you going to be messing about with template AST anyway, so no real benefit?Question 5: Any suggestions for better names? I've also considered placeholders, transformers, lightweights, and probably a few more, but I don't know that any were better. Proxies feels a bit... strange, I guess.
Fixes the following issues:
#3099 #3089 and probably some others.
Is breaking:
Yes, in that
proxies
andproxy
are added to Ractive constructors. Parser transforms are also removed in this because proxies provide the same approximate service in a more flexible, runtime-friendly way.TODO