Templates are crucial for the well-being of a framework; they are the language of the developer to the DOM, allowing developers to express their UI in a declarative and expressive manner.
Templates are, by definition, blueprints that represent the initial structure of a component's DOM with its inline logic, which a runtime transforms into the final DOM structure.
Over the years, different techniques have been invented to implement templates, each with different properties. Some were naively simple, others required a new language, and some transform into lower-level forms.
All of the templating technologies used today can be categorized into four types of implementations, according to their reliance on compilers and transformers.
The first implementations of templates were defined using normal, functional JavaScript. They did not rely on complex compilation processes; they were just declared with vanilla JavaScript values.
They are the simplest and most portable form of templates, as they can be used in any environment. They are also a lower-level form, as all other implementations are ultimately transformed into this form due to its simplicity and nativity.
Native value templates can be further classified into three types:
While native value templates are the most portable form of templates, they require custom parsing at runtime or are difficult to write. For that reason, the JavaScript community developed new template implementations that leverage the power of compilers and transformers.
A fast and easy way of incorporating compilers into template generation is to write the templates in a standalone module, then parse and serialize them into the app bundle.
This approach worked so well, as it reused existing parsing engines, reduced bundle size, and made the templates more efficient, without affecting the rest of the application or the IDEs and JavaScript parsers and viewers.
While having templates in separate modules is not a harmful idea, it caused jumping between the templates and their component logic, which were found in separate files. So, single-file components were invented, in which the logic is written inside a script tag in the template, along with styles. The majority of frameworks adopted this approach.
Single-file components encapsulate all the concerns of the component, enabling better type safety and easier debugging. It also enables better compiler optimizations.
This implementation, however, was not free of cons. It depended on a build step. However, a build step is not that difficult or unintuitive; it's often just a script copied and pasted from project to project. Moreover, we are forced to use tooling, as even using modules requires having a development server.
Some frameworks wanted templates to be inline with the code and also wanted the benefits of compilers. Template modules were not an option as they separated the template and logic, so template transpilation was invented.
Transpilation is a general concept; it is a build-step process in which JavaScript enhanced with custom syntax is transformed into vanilla JavaScript. It is commonly used in the ecosystem, especially in TypeScript, and for downgrading JavaScript syntax to be compatible with older runtimes.
The most notable form of template transpilation is JSX, developed to be used with React but adopted by multiple frameworks, which allows templates to be written inline with the code in HTML syntax with code wrapped in curly braces.
An advantage of transpilation over modules is the freedom of usage. Modules contain templates in a single big blob, while transpilation allows them to be used wherever they are needed, from utilities to components.
Also, transpiled templates share the same block scope as the using code, a thing modules cannot do or can only at the component level.
Although transpiled templates have additional benefits over template modules, they have a destructive issue: adding custom syntax makes the code incompatible with standard code parsers and viewers, requiring additional work to support it. Although JSX has become a standard, not every parser supports it.
Moreover, there is a way to have the same benefits of transpilation without the custom syntax.
Vanilla transformation is a general process in which vanilla JavaScript (not necessarily functional) is transformed into another form of JavaScript in order to make it more efficient or to enhance its behavior.
A notable example of vanilla transformation is the Svelte compiler. Svelte compiles vanilla JavaScript expressions into reactive ones, changing their behavior to run when their dependencies change.
This technology is incredible; it enables enhancing the behavior and capabilities of the code without affecting its syntax, making it fully compatible with standard code parsers and viewers, and it is not limited to the whole module.
Also known as macros, it is the substitution of a function call into another equivalent JavaScript expression. It, like vanilla transformation, allows for the enhancing and optimizing of code without affecting its syntax and compliance with the specifications. The difference is that it works on a micro scale.
The concept is general and highly capable. It is used in different fields, especially language-related ones like constant evaluation, and it can be used in templating.
Template macros take native value templates, like a template string, and transform them into an optimized form, all while not affecting the syntax, reducing the bundle size by removing the parsing system. Most amazingly, it can be used without a build step as the source can be run normally.
This type of implementation might be the best approach until now, as it has all the benefits of its predecessors while being optional and unnoticeable, and it is capable of optimizing all native value templates.
NeoComp mainly has runtime string templates, optionally optimized with the power of the template macro. In addition, it has template modules and can have templates directly in any DOM tree, achieving its goal of being a flexible framework for all environments, and complementing this goal, the hidden power of object-oriented components, done right.