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

Functional Widgets and Render Middleware #352

Merged
merged 4 commits into from
May 30, 2019

Conversation

agubler
Copy link
Member

@agubler agubler commented May 22, 2019

Type: feature

The following has been addressed in the PR:

Description:

The basic implementation to support functional widgets and render middleware. Includes one core middleware invalidator (open to renaming suggestions).

Provides a single function create that returns a factory that can be used to create Dojo widgets and render middleware.

Note: Currently create is in the tsx module in widget-core, this is going to change as the feature is built on.

Creates a simple widget with a properties interface:
const { create } from '@dojo/framework/widget-core/tsx';

const render = create().properties<{ foo: string }>();
const MyWidget = render(() => v('div'));
Creates a render middleware with a properties interface:
const { create } from '@dojo/framework/widget-core/tsx';

const middleware = create().properties<{ bar: string }>();
const myMiddleware = middleware(() => {
    return {
        get() {},
        set() {}
    };
});
Creates a widget with middleware that influences the property typings:
const { create } from '@dojo/framework/widget-core/tsx';

import myMiddleware from './myMiddleware';

const render = create({ myMiddleware }).properties<{ foo: string }>();
const MyWidget = render(({ properties, middleware }) => {
    // can access the injected middleware
    middleware.myMiddleware.get();
    middleware.myMiddleware.set();
    // properties API defined by the widget and the injected middleware
    return v('div', [properties.foo, properties.bar]);
});

The change includes support for calling a widget directly, calling the widget directly has the added advantages to be able to infer complex properties such as a render property, but is also backward compatible for use with w().

Example Usages:

// Supports the existing w() pragma
w(MyWidget, { foo: 'bar' });

// Support being called directly
MyWidget({ foo: 'bar' });

// Supports tsx
<MyWidget foo="bar" />

Related to #349

item.hasOwnProperty('__esModule') &&
item.hasOwnProperty('default') &&
isWidgetBaseConstructor(item.default)
item && item.hasOwnProperty('__esModule') && item.hasOwnProperty('default') && isWidget(item.default)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for code splitting this needs to also check whether it's a widget factory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


const createFactory = create();

export const invalidator = createFactory(({ id }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

starting to wonder whether we should put these base ones in vdom itself? then we don't have to export the function itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good idea, will have more control in vdom itself also.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

export function create<T extends MiddlewareMap<any>, MiddlewareProps = ReturnType<T[keyof T]>['properties']>(
middlewares: T = {} as T
) {
function properties<Props extends {}>() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this read better as something similar to withProperties()? Thinking about it from a perspective of making the create API more fluent, e.g.:

create().withProperties<{]>();

Copy link
Member Author

@agubler agubler May 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to reduce the number of characters but went with properties, I don't actually mind the with prefix tho for it's fluency. @matt-gadd any thoughts?

Copy link
Contributor

@sbinge sbinge May 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's another subtle comprehension hiccup here - this method is describing the property interface as part of the factory creation, not widget creation nor rendering invocation.

Reading it as it currently stands, a user could potentially confuse it with passing actual property values as part of a 'create a widget' call; something that withProperties or hasProperties doesn't help with to resolve.

What about something like withAPI()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My issue with that is that in the future, when typescript generics catch up we’d like to explore the possibility of using the function to define default properties which will leave the external property API with optional properties but enable referencing them from within widget knowing that although the external API is optional the internal API is not. For the moment typescript would prevent a user passing anything to the function as there are no arguments current.

Additionally the API of a widget is not just properties, it’s also children which again in the future is something that we’d consider exposing for the user to define perhaps with a further .children API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

@agubler agubler force-pushed the functional-widget-authoring branch from efaf021 to b30630c Compare May 28, 2019 17:04
@sbinge
Copy link
Contributor

sbinge commented May 28, 2019

Can't comment much on the framework internals of this but the API simplification/consistency gains are great from a user's perspective.

Is there any insight yet on how performance of this compares to an equivalent app structure using existing class-based widgets? Any discernible difference in execution/render timings or memory usage? (hoping this is an overall improvement as a whole - no real trade-offs users would need to consider when looking to develop apps in this new style).

@agubler
Copy link
Member Author

agubler commented May 28, 2019

@sbinge we do have an implementation of a js-framework-benchmark that I have been upgrading and running using functional widgets. The performance is similar and the memory usage slightly less. I will do a side by side comparison of the two different implementation and post onto this issue.

@agubler
Copy link
Member Author

agubler commented May 28, 2019

@sbinge running two implementations of the js-framework-benchmark, one with classes, one with functional widgets but otherwise identical corroborates the suspected performance characteristics.

Interactive_Results

@agubler agubler force-pushed the functional-widget-authoring branch from 8877701 to fd358b3 Compare May 30, 2019 16:48
@sbinge sbinge self-requested a review May 30, 2019 16:57
Copy link
Member

@tomdye tomdye left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍

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

Successfully merging this pull request may close these issues.

4 participants