This is more a note to myself, but please feel free to add in ideas.
I have observed that the current state of master components still has a bunch of caveats to it and would like to cover some possible features we can implement to alleviate various issues.
I already touched upon an issue that is related to a missing nested component feature in #223 and will not cover this here.
Rather this is more about improving the workflow with master components for developers who are more "code drive", which is to say, "prefer to have everything in human readable source code".
Async Hell
The first issue I would like to cover is the asynchronous nature that master components introduce to the code.
When for instance a designer has crafted a master component for us we as a developer would like to utilize for our tool,
we can not make any assumptions about the submorphs and properties being properly initialized until the master component has been loaded successfully:
this.master = 'styleguide://someWorld/path/to/master'
await this.master.whenApplied();
// now we can proceed and assume that everything is the way its *supposed* to be
This, I think one can say quite reasonably, sucks and forces asynchronous code where it really should not be.
How could we remove the asynchronous nature? Well actually quite simply. Assuming the master component was already resolved somehow previously, we can assume it can synchronously apply itself to the morph it is assigned to since the only reason we are asynchronous is that master components are potentially fetched from files:
// some time earlier
const masterComponent = await resource('styleguide://someWorld/path/to/master').read()
// some time somewhere later
this.master = masterComponent;
// by here the master component has already applied itself fully and initialised all submorphs
So it all boils down to those nasty files required to be fetched. But wait, wasnt there another thing that comes from files all the time? That's right, modules do. But why then do they not introduce the asyncronous problem all over the place?
Well for one at some point they do, which is when we utilize dynamic imports:
await System.import("lively.ide/code-search.js");
Yet most of the time, the modules are declared as imports, which informs the module system to only run the module fully once all of the depending modules have been loaded:
import A from "moduleA";
import { B } from "moduleB"
This essentially hides the asyncronous evaluation from the module scope and moves it into the module system itself removing any kind of asyncronous code from our module. Nice! So can we do the same with master components?
In principle we can! lively.modules allows us to almost arbitrarily instrument our module system, and we can very well make it support the following without much issues:
import masterComponentA from "styleguide://someWorld/path/to/master"
Once we then extend the freezer to properly resolve said imports, we should be able alleviate the asynchronous master component code from the codebase once and for all.
Cyclical Graph Hell
Right now the workflow for master components essentially forces developers to define parts in worlds, which for many, especially non design folks, constitutes a shift to a different mode of working, which is somewhat inconvenient. (I will not go over existing bugs which arise, only the conceptual issues with the approach). Furthermore, we still have the issue that platforms such as github etc. will not allow us to really create meaningful changesets and pullrequests between worlds, since they constitute graphs and not human readable data formats (whatever that really is).
Now @rksm and I have spent a great deal thinking about this issue in the past, and it always comes back to the simple answer that in order to embrace a more what I would call coder and platform friendly approach we need to move to a declarative, ideally simplified representation of morphs or in this case components. In an optimal world we would also like to achieve said feat without abandoning all the nice things we get from working in worlds, which is predominantly direct manipulation and the ability to craft interfaces visually rather than in code.
Well let's first note the fact, that right now nothing prevents the user to just define master components inside modules instead of worlds and use them directly:
const masterA = morph({
isComponent: true,
name: "button/red",
fill: Color.red,
borderRadius: 5,
submorphs: [{ name: "button label", type: 'label', fontSize: 14, value: "Hello World!" }]
});
// and then just use or export this component
export { masterA }; // import and use in other modules
aMorph.master = masterA; // directly assign as master
// use in custom morph definitions
class SomeButton extends Morph {
static get propeties() {
return {
master: {
initialize() { this.master = masterA; }
},
submorphs: {
initialize() {
this.master.reconcileSubmorphs(); // returns syncronously!
}
}
}
}
}
I think for people who work from code, it is quite obvious what benefits we get from resorting to this definition of master components: We keep the nice separation of appearance from code, remaining nicely modular. Further the components are now formidably suited to be managed in "classical" text based version tools and platforms a la github. However we loose the ability to manipulate master components inside modules via halos or other primarily visual tools. This is quite unfortunate since for one it excludes people who don't know how to code and even worse: It is just a plain old nightmare to design good interfaces from a code first approach. Freely drawing or manipulating elements on the famous "blank canvas" is I think crucial for getting to high quality and well working designs as fast as possible. The reason the tools designers prefer to use are so heavily visual is in fact not, as I sometimes believe a lot of programmers like to believe, "because they cant code" but instead due the complete inadequacy of the code first approach for this part of application development.
I think this is the perfect opportunity to revive a Lively idea from a couple years back, were we came up with mapping direct manipulation to source code transformations. One of the main problems Transmorphic faced back in the days, was to reconcile properly with abstractions such as functions calls, loops or conditionals. This is now more or less gone, since these code bits are usually never part of a master component definition. A master component is by design something that is separate from accompanying behaviour. While we can choose to ship a component with custom behaviour, we are not required to and in fact the way code based master components definitions work, we do not really need to worry about messing up abstractions of any kind. In fact master components should not ship with any custom behavior at all. This should be the role of stateful parts that live in worlds and serialize their state when they are supposed to be stored away. For more on that topic see one of the posts below about state junkies vs code people.
Concretely I am envisioning an alternative "view" provided by the system browser, where a module that defines master components can be viewed as a canvas upon which all the defined master components are rendered and displayed as morphs and can in turn be directly manipulated. Those changes can then be directly mapped back to the changes in the code of the module. This should be exceedingly straight forward due to the already existing AST support in lively which allows us to work with code as data and conveniently resolve master components to source code ranges and changes.
I will refine this idea a bit more in the near future and provide some more examples before we flesh out the details for a concrete implementation.
This is more a note to myself, but please feel free to add in ideas.
I have observed that the current state of master components still has a bunch of caveats to it and would like to cover some possible features we can implement to alleviate various issues.
I already touched upon an issue that is related to a missing nested component feature in #223 and will not cover this here.
Rather this is more about improving the workflow with master components for developers who are more "code drive", which is to say, "prefer to have everything in human readable source code".
Async Hell
The first issue I would like to cover is the asynchronous nature that master components introduce to the code.
When for instance a designer has crafted a master component for us we as a developer would like to utilize for our tool,
we can not make any assumptions about the submorphs and properties being properly initialized until the master component has been loaded successfully:
This, I think one can say quite reasonably, sucks and forces asynchronous code where it really should not be.
How could we remove the asynchronous nature? Well actually quite simply. Assuming the master component was already resolved somehow previously, we can assume it can synchronously apply itself to the morph it is assigned to since the only reason we are asynchronous is that master components are potentially fetched from files:
So it all boils down to those nasty files required to be fetched. But wait, wasnt there another thing that comes from files all the time? That's right, modules do. But why then do they not introduce the asyncronous problem all over the place?
Well for one at some point they do, which is when we utilize dynamic imports:
Yet most of the time, the modules are declared as imports, which informs the module system to only run the module fully once all of the depending modules have been loaded:
This essentially hides the asyncronous evaluation from the module scope and moves it into the module system itself removing any kind of asyncronous code from our module. Nice! So can we do the same with master components?
In principle we can!
lively.modulesallows us to almost arbitrarily instrument our module system, and we can very well make it support the following without much issues:Once we then extend the freezer to properly resolve said imports, we should be able alleviate the asynchronous master component code from the codebase once and for all.
Cyclical Graph Hell
Right now the workflow for master components essentially forces developers to define parts in worlds, which for many, especially non design folks, constitutes a shift to a different mode of working, which is somewhat inconvenient. (I will not go over existing bugs which arise, only the conceptual issues with the approach). Furthermore, we still have the issue that platforms such as github etc. will not allow us to really create meaningful changesets and pullrequests between worlds, since they constitute graphs and not human readable data formats (whatever that really is).
Now @rksm and I have spent a great deal thinking about this issue in the past, and it always comes back to the simple answer that in order to embrace a more what I would call coder and platform friendly approach we need to move to a declarative, ideally simplified representation of morphs or in this case components. In an optimal world we would also like to achieve said feat without abandoning all the nice things we get from working in worlds, which is predominantly direct manipulation and the ability to craft interfaces visually rather than in code.
Well let's first note the fact, that right now nothing prevents the user to just define master components inside modules instead of worlds and use them directly:
I think for people who work from code, it is quite obvious what benefits we get from resorting to this definition of master components: We keep the nice separation of appearance from code, remaining nicely modular. Further the components are now formidably suited to be managed in "classical" text based version tools and platforms a la github. However we loose the ability to manipulate master components inside modules via halos or other primarily visual tools. This is quite unfortunate since for one it excludes people who don't know how to code and even worse: It is just a plain old nightmare to design good interfaces from a code first approach. Freely drawing or manipulating elements on the famous "blank canvas" is I think crucial for getting to high quality and well working designs as fast as possible. The reason the tools designers prefer to use are so heavily visual is in fact not, as I sometimes believe a lot of programmers like to believe, "because they cant code" but instead due the complete inadequacy of the code first approach for this part of application development.
I think this is the perfect opportunity to revive a Lively idea from a couple years back, were we came up with mapping direct manipulation to source code transformations. One of the main problems Transmorphic faced back in the days, was to reconcile properly with abstractions such as functions calls, loops or conditionals. This is now more or less gone, since these code bits are usually never part of a master component definition. A master component is by design something that is separate from accompanying behaviour.
While we can choose to ship a component with custom behaviour, we are not required to and in fact the way code based master components definitions work, we do not really need to worry about messing up abstractions of any kind.In fact master components should not ship with any custom behavior at all. This should be the role of stateful parts that live in worlds and serialize their state when they are supposed to be stored away. For more on that topic see one of the posts below about state junkies vs code people.Concretely I am envisioning an alternative "view" provided by the system browser, where a module that defines master components can be viewed as a canvas upon which all the defined master components are rendered and displayed as morphs and can in turn be directly manipulated. Those changes can then be directly mapped back to the changes in the code of the module. This should be exceedingly straight forward due to the already existing AST support in lively which allows us to work with code as data and conveniently resolve master components to source code ranges and changes.
I will refine this idea a bit more in the near future and provide some more examples before we flesh out the details for a concrete implementation.