Hi
i have thought lately again of how to make Fusion component rendering easier to write.
Fusion computed
properties and component API
As far as i see there are currently a few things that can be improved.
- how to archive
computed
variables? Should i use@process
,@context
,this
orprops
? - how to declare a
public
API for your component. Should i use the nested component patter, prefix private paths with_
or use@propTypes
?
Fusion patterns and beginners
Over the time some patterns arise, but one needs to master them to become a Fusion pro. For beginners Fusion is often hard to understand as there is no top to bottom control flow like in a scripting language. And without knowing the patters/ actually understanding Fusion, its far to easy to shot oneself into the foot. (by randomly accessing props
even though its not a component or thinking this
refers to some other component etc…)
Simplicity of Reacts functional components
To tackle the mentioned problems i took the simplicity of Reacts functional components as an example. They offer a simple API for the props, and one can access those props everywhere in the function body and create computed variables and use them everywhere too:
function demo({ variable: "hello" }) {
const computedExpression = variable + " world"
const computedJsx = (
<p>{computedExpression}</p>
)
return (
<>{variable} {computedExpression} {computedJsx}</>
)
}
Solution? Fusion:FunctionalComponent
(with transpilation magic )
I want to have this same simplicity, so one doesnt need to fully understand Fusions context, but can start already building simple frontend components.
Presenting the idea of a FunctionalComponent
in Fusion:
all variables starting with $
are ‘magic’ as they are scoped to the component they were written.
(this is just to make regexing/parsing easier xD)
prototype(MHS.Site:Demo) < prototype(MHS.Fusion:FunctionalComponent) (
// public API
variable = "hello"
) {
// private
computedEEL = ${$variable + " world"}
computedFusion = afx`
<p>{$computedEEL}</p>
`
renderer = afx`
{$variable} {$computedEEL} {$computedFusion}
`
}
Implementation ideas and further specification
To get this running would require two steps, preprocess this Fusion file and transpile it to actual Fusion and implement rendering and context logic in the prototype implementation.
The Fusion below shows how it transpiled could look like:
Note a few specialities:
- the prefix
_123
(which is unique for each functional prototype declaration) is appended to all previously via$
referenced variables. (this is done to make sure the context doesnt interfer with anything else) - the property
@selfReferencingContext
is used by theFunctionalComponent
and behaves similar to@context
, except one can reference newly defined context variables already. -
renderer
is not considered a variable but more like areturn
so it is not touched.
prototype(MHS.Site:Demo) < prototype(MHS.Fusion:FunctionalComponent) {
// public API
variable = "hello"
// private
@selfReferencingContext {
// hello
variable_123 = ${this.variable}
// hello world
computedEEL_123 = ${variable_123 + " world"}
// <p>hello world</p>
computedFusion_123 = afx`
<p>{computedEEL_123}</p>
`
}
renderer = afx`
{variable_123} {computedEEL_123} {computedFusion_123}
`
}
Usage / Instantiation
it can be used as any other object. The advantage beeing, that private variables are not mutable, due to the random identifier.
foo = MHS.Site:Demo {
variable = "moin"
}
Private variables
not usable from outside the declaration
the following code would not print out the variables, as they have now the unknown prefix _123
foo = MHS.Site:Demo {
renderer = ${computedEEL + variables}
}
also due to the anonymization the problem the bare use of @context
has, that the whole subtree suffers from the pollution is limited as it would be impossible to accidentally reference a randomized identifier. To minimize the pollution, the variables could also be combined in a props
like associative array.
Limitations
- inheritance of those components will be forbidden due to undefined behavior - use composition
- recursively calling the same component could lead to the context not working as expected so it shouldnt be allowed at first (need to spend some brainpower on this one ;))
- a simple implementation would only allow one functional component per file and keep them separated from the actual Fusion
- extending the object is undefined behavior
Further improvements
-
lazy evaluation of the computed variables
-
Runtime type checking can be simply implemented for the API like:
prototype(MHS.Site:Demo) < prototype(MHS.Fusion:FunctionalComponent) (
variable: string = "hello"
) {
...