Currently, Agrippa creates the React component code as one big array of "lines" (strings or nulls), from which the nulls are then filtered and the rows then joined. ostr()
is used extensively for optional lines, and cstr()
for conditional strings inside lines.
This approach is nice and was simple to implement, but is harder to scale and maintain since all the logic is stored in one place as a huge block of code. Right now (v1.1.1) this is a minor issue, since the amount of code is not too large, but as Agrippa grows on it can only get much worse.
Also, since all the logic is bundled together, it is difficult to test one piece at a time. This is a major issue, as demonstrated in issues #3 and #5 - both are awkward bugs that were missed due to the lack of standardized testing on my part.
To solve these issues, Agrippa's internals will be reformed as such:
-
The generation code will be implemented in a functional manner (functional as in functional programming); I prefer writing functional code as long as there's no eminent benefit to using a different paradigm (OOP or otherwise).
-
The logic of determining the correct form for a small segment of the code should be separated from the logic of composing those segments together. By "small segment of the code" I mean an atomic segment, i.e. a segment which cannot be reasonably split into a composition of two other segments. Segment composing other segments will be called composing segments; **the composed segments can be atomic segments, other composing segments or a combination of the two.
For example, consider the type Agrippa declares for a component in a Typescript project - e.g. React.VFC<ButtonProps>
in const Button: React.VFC<ButtonProps> = (props) => { ... }
. This simple segment would be modeled as a composition segment of two other atomic segments - The base React type (React.VFC
or React.FC
) and the name of the props interface (derived from the component's name, e.g. ButtonProps
in the example above).
This distinction between atomic and composing segments will help keep concerns separated and the code DRY.
-
The logic for atomic segments will be implemented as pure functions, which will receive as parameters exactly the data they need (and no more); this will make unit-testing them dead simple.
The logic for composing segments, on the other hand, will be implemented as methods of a single class, Composer
(name definitely not final), which will have the entire config as a readonly member, and will pass the relevant parts of it down to the atomic segments it composes.
This means each method will have access to the entire config (and will typically need no parameters of its own). Having the config be a readonly member assures that the code remains functional, since no internal state is implemented - this is essentially a cleaner alternative to requiring the config as an argument in the funcitons of all composing segments and partially applying it.
Requiring the entire config for each method (by way of requiring it for the entire Composer
class) makes testing a bit more cumbersome, but should pose no real problem.
type |
atomic segments |
composing segments |
implemented as |
pure functions |
Composer methods |
parameters |
only what is used |
typically none; access to entire config as class member |
-
Finally, the code generation task will create and use the Composer
class to generate the final component code.
-
Implementing atomic segments as pure functions and composing segments as methods of Composer
is important, but not cardinal - if there's a very good reason to implement an atomic segment as a mehtod of Composer
it will be implemented as such (and vice versa).
This spec might change or extended as it's implemented, but I think it's a solid strategy. It will also make it easier to implement some of the features I've been willing to add to Agrippa, such as an option to declare components as function
s instead of const
s, and in turn the ability to generate memo
components or the likes.
Suggestions and inputs are welcome.
Another issue will be opened in the near future for the testing RFC.