How to build a simple web app with web components
Recently I developed a simple project to test how far is it possible to go in writing a simple web app without any libraries and with the tiniest amount of tooling.
Let’s clarify one point right at the beginning: when the complexity increases a proper build step and a framework are probably needed. This is just an experiment to understand where this threshold lies.
As a developer, I expect a modern web app to have:
- A modular code base
- Composable components
- Reactivity
- Declarative code style
- Input sanitization
- Scoped style
- A dev server with auto reload and hot reload
- To be testable at all levels (unit, integration, functional, ect.)
- Type checking
- SSR capabilities
Results
Modular code base
This is the easy part, just use ES modules. If needed they can be easily bundled with a production build pipeline.
Composable components
Custom element can be used to create composable components.
Reactivity
Implementing the signal pattern results in a good level of reactivity. A library can be used, but it is possible to use a basic custom implementation.
Signals effectively replace:
- Local state (a signal declared in the component scope and not exported)
- Global state (a signal exported and thus accessible everywhere)
- Effects (an effect is just the subscription to a signal)
Declarative code style
This is the pain point. I didn’t find a way to achieve this without using an external library. I’d like not to take this step because the main objective is to be as close as possible to the web platform standard.
On the other hand, using the imperative code style has the advantage of pairing nice with existing websites, rendered server side.
Input sanitization
Same as the previous point. Pay attention not to put user input in the DOM without proper sanitization.
A standard Sanitizer API is being developed, but there is no browser support at the moment.
Unfortunately, even using only innerText
to render user input is not safe enough.
Due to the sensibility of this topic I’d avoid a custom implementation and go for a library in this case, such as:
Scoped style
It is possible to achieve a basic scoping just by using the custom element name as a namespace.
I didn’t find an easy way to colocate the style with the component. This could eventually be done writing the css in the component file, but that has 2 issues:
- The style is not applied until the JS has run
- The autocomplete in the editor doesn’t work out of the box (but there may be extensions that enable autocompletion in template literals)
An Idea would be to put the style in a CSS file named as the component and add a build step to copy the file content into a global CSS file.
If you are fine with the limitations of the CSS in JS, though, you can use the new @scope
css selector in the custom element markup including it in a <style>
element.
A dev server with auto reload and HMR
As a dev server we can use , if offers hot reloading out of the box. It offers HHR with this plugin, but for vanilla components a change in the code is needed and I didn’t succeeded in implementing it. May be the good ol’ webpack is a better choice?
To be testable
No big issues here, testing can be done with the usual tools. Just to try something new I played with the OWC testing package.
Type checking
VS code can do a basic type check on single files adding the //@ts-check
comment at the top of the file. To use TypeScript a build step is needed.
There is a proposal by Microsoft to bring type annotations to the language, but I is still that, a proposal, and in any case I will not offer the same features as TypeScript.
SSR Capability
If the application is not data driven the so called “HTML web components” can be used. in other words, custom element that wrap regular HTML code. It should be easy to render this kind of HTML server side if needed.
Conclusion
It is possible to build a modern web app using only what the web platform offers out of the box. Some compromise on the developer experience (namely, use imperative code style) are balanced by:
- Performance & SEO: the page is basically just ol’ regular HTML (but for SEO the internationalization should be done server side)
- A code that will live forever
- No dependencies to maintain
- This approach can be used right now with any existing back end capable of producing HTML, CSS and JavaScript, to add interactivity
Further improvements
Development and production build pipeline
A production build step is probably needed to add:
- Bundling
- Minification
- Autoprefixing in CSS (is this really still needed?)
- Polyfills
So, since we are using some tooling anyway, accepting the idea of a more complex setup could unlock some interesting possibilities:
- Hot Module Reloading
- Colocation of CSS and components
- TypeScript
This would satisfy all the initial requirements, except for the declarative code style. But that could be a good trade off to keep thinks simple, future proof and compatible with the existing back ends.
Use a templating library
This could satisfy the last requirement, but I have mixes feelings about that. Once you go down this road, why stop and not use a front end framework?