Moin,
fusion inconveniences i found for myself:
I do really like Fusion, but sometimes it’s hard to see (from the prototype source code) what part of a fusion prototype is API or should not be touched. As it just comes down to some comments in the code. (I know the pattern of computed properties / nested component props make this clearer but hear me out …)
Often there are things like nodePath = 'to-be-set-by-user'
f.x. in the ContentCollection.fusion.
But to me, this doesn’t feel right. I know one could set it to an empty string or null, but I don’t want to set a value for this, since it should be required and user defined.
When creating a new object and passing properties it feels like subclassing an object every time, and like shooting blind and hoping to define the right propertys, so everything works (a bit overdramatized - but it’s hard for beginners that don’t know the API for some prototypes that well).
Having no way to enforce certain properties will make it hard when fx. spelling ‘renderer’ wrong in the object creation. A new path will be created what you dindt want, and youre getting a at first glance wierd exception like ‘No fusion object was found in path x’. Why would this fail only at such late stage and not before. Issues like that should be caught earlier IMO and so will be easier to fix. ‘Required argument ‘renderer’ is missing for prototype Neos.Fusion:Renderer’ reads better, I think.
Also, Fusion has currently no build in type hinting implementing which would help with a stricter API for objects. (related idea came up on the sprint Neos and Flow Future Notes / Discussion - CodiMD)
idea
I’d like to have a way to interact / instantiate fusion objects, without overriding wrong things and while passing all required arguments with a type check.
A type check functionality only for the arguments would be easier to implement than the posibility to type check every path, that could maybe suffice already as PHP does it similarly.
The following ideas assume that the parser can tell the difference between inside a prototype() declaration, and a normal path declaration/ Object instantiation. That is possible already.
the prototype declaration could look like:
‘this.args’ would be pointing to all the arguments.
prototype(Vendor:Content) < prototype(Neos.Fusion:Component) {
@args {
name[string]
node[?Neos\ContentRepository\Domain\Model\NodeInterface] = null,
untyped = 123
}
name = ${this.args.name}
node = ${this.args.node ? this.args.node : node}
renderer = afx`
<h1>hi {props.name}</h1>
{q(props.node).property('stuff')}
`
}
with syntax sugar
the prototype declaration could look like:
The idea here is that everything in () is parsed into a path like __args
prototype(Vendor:Content) < prototype(Neos.Fusion:Component) (
name[string],
node[?Neos\ContentRepository\Domain\Model\NodeInterface] = null,
untyped = 123
) {
name = ${this.args.name}
node = ${this.args.node ? this.args.node : node}
renderer = afx`
<h1>hi {props.name}</h1>
{q(props.node).property('stuff')}
`
}
instantiation could look like:
value = Vendor:Content {
@args {
name = 'Test'
node = ${node}
}
}
with syntax sugar
instantiation could look like:
value = Vendor:Content(name='Test', node=${node})
the fusion AST could look like:
# simplified
'Vendor:Content' => [
'__args' => [
'name' => [
'__typed_match_one' => [
'scalar' => 'string',
]
],
'node' => [
'value' => null
'__typed_match_one' => [
'constant' => null,
'interface' => 'Neos\ContentRepository\Domain\Model\NodeInterface'
]
],
'untyped' => [
'value' => 123
]
],
'name' => '${this.args.name}'
# further stuff
]
Further notes:
about required arguments
An argument would be required if it wasn’t defined in the prototype. I think the runtime configuration will merge the passed configuration on instantiation with the prototype source code. The runtime would only know if the argument is set, but not if it was set explicitly by the user or has the default value. If there is no value set for an argument, that means it wasn’t set and an exception will be thrown.
Lazy type checking and checking for required args or eagerly?
In the runtime the required arguments and types can be checked on instantiation of the object or when this.args.x is called.
Implementing a lazy way would be harder, but what if ${this.args.something} is not set, because a user used the old way of passing ‘something’ (More detail shown in the compatibility example down below)
possibility for pure components?
fx. only the arguments go in and inside there is no access to the outer @context. This would be helpful for caching. But what about when fx. Neos.Fusion:Tag is modified not globally but only for specific paths. That would lead to unexpected results. So pure components must be evaluated always on global level
how would this affect afx?
return type declaration?
how to handle a real path ‘args’ as this will not be accessible via ‘this’
passing args to renderer?
prototype(Vendor:Content) < prototype(Neos.Fusion:Component) (
foo[string]
) {
args = ${this.args}
renderer = afx`
{props.args.foo}
`
}
prototype(Vendor:Content) < prototype(Neos.Fusion:Component) (
foo[string]
) {
@apply.args = ${this.args}
renderer = afx`
{props.foo}
`
}
what about passing an argument that doesnt exist?
this could be catched when the fusion parser can tell the difference between if its inside a prototype declaration or an instantiation.
compatibility
the idea would be compatible with the current manner of instantiation of objects in example of Neos.Neos:Editable (syntax sugar way):
prototype(Neos.Neos:Editable) < prototype(Neos.Fusion:Component) (
property[string],
node[Neos\ContentRepository\Domain\Model\NodeInterface] = ${node},
block[bool] = true
) {
# The name of the property which should be accessed
node = ${this.args.node}
# The name of the property which should be accessed
property = ${this.args.property}
# Decides if the editable tag should be a block element (`div`) or an inline element (`span`)
block = ${this.args.block}
renderer = afx`...`
}
the above should be usable as currently known via classic override:
but that would mean the required argument ‘property’ is not passed, if argument checks are eager.
editable = Neos.Neos:Editable {
property = 'title'
}
or with specified arguments (syntax sugar way):
editable = Neos.Neos:Editable (
property = 'title'
)