The world of frontend web development changes quickly, thanks to new frameworks mixing up the market and new ideas and approaches evolving rapidly.
React and Vue, for example, were one of the first component-based frameworks, and they are currently the most widely used.
With the trend of Jamstack websites moving back to statically rendered websites instead of dynamically rendered websites on the client side, new frameworks built on top of React or Vue are gaining popularity, such as Next.JS, Gatsby, and Nuxt.
In addition, new frameworks like Svelte are being adopted by developers because they make it even easier to create statically rendered websites without losing the modern feel of dynamic frameworks like React.
Tonic is a component framework that takes this minimal yet elegant approach and works perfectly with Jamstack, as it doesn’t use virtual DOM and is server-side rendered by default. It’s based on web components and can be used out of the box without a build tool. It is super small (5KB!) and blazingly fast.
This article explains how Tonic works and why you should use it when building a Jamstack website. I will also show you how to set up a project and build your first components using Tonic.
Tonic uses native web components, a concept introduced in 2011 that is now part of all modern browsers by default.
One of the most famous websites powered by web components is YouTube. If you look at the source code, you’ll quickly discover many components in the DOM:
Although using web components natively is possible, it can be complex and difficult to understand.
Because of this, Tonic has added some abstraction on top of web components that gives it a similar look to frameworks like React and Vue, but, aside from using virtual DOM to render elements, it actually uses the web components natively built into the browser.
So, why use it when there are so many frameworks out there? Here are some great reasons you should use Tonic to create a Jamstack website:
Now that we’ve seen the benefits of Tonic, let’s bootstrap a project using it. Our goal is to have a page that contains a counter
button and a number next to it that increments every time we click the button.
Because we don’t need a build tool, we can create a simple HTML file that contains:
<!doctype html> <html> <head> <title>Website with Counter</title> <script src="https://unpkg.com/@optoolco/[email protected]/dist/index.js" ></script> <script type="module" src="index.js" ></script> <script src="https://unpkg.com/@optoolco/[email protected]/dist/index.js" ></script> <link rel="stylesheet" type="text/css" href="index.css" media="screen" /> </head> <body> <p>Hello</p> </body> </html>
Let’s go through the parts we have in the head
tag. We have the title
of our website, and we need a script
tag to import the code from Tonic. We use the unpkg tool to import it with a script
tag.
The next script
tag is for importing our custom JavaScript file we will create with no content for now. We also want some styles, so we’ll create a style.css
file and link
it as a stylesheet.
The body
part will contain our custom counter element. We’ll leave it empty for now and fill it in when we write the actual component. Now, we should have the following classic web development files:
-- index.css -- index.js -- index.html
And that’s it! You don’t need any other files or a node modules folder. However, if you want to run it on a development server, you can initialize it as an npm package and use serve to run your website on localhost
by adding the following package.json
file:
// package.json { "name": "tonic-proj", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "serve" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "serve": "^13.0.2" } }
Then, after running npm i
and npm start
, the project should open on localhost:3000
. This should be a pretty static hello message.
So far, there’s nothing special here. But to add the actual counter, we need to write our first Tonic component.
We have to first get the Tonic class from the imported package. At the top of the index.js
file, let’s add the following:
const components = window.components; const Tonic = components.Tonic;
Then, we can create our first component that extends the Tonic class:
// ... class CounterElement extends Tonic { state = { counter: 0 }; click(e) { const trigger = Tonic.match(e.target, "button"); if (trigger) { this.state.counter++; this.reRender(); } } render() { if (typeof this.state.counter === "undefined") { this.state.counter = 0; } return this.html` <div> <button>+1</button> <p>Count: ${this.state.counter.toString()}</p> </div> `; } }
As you can see, it looks similar to React code and the JSX pattern. Similar to React, you need a render
function in your class that returns the return value of the inherited function this.html
. This function accepts a string containing the HTML elements you want your component to render.
If you want to render dynamic values, you can simply add them with a template string. The component also accepts a set of event methods that are fired when the HTML element events are triggered.
In our example, we use the click
event to listen for when someone clicks on our component. Because we want the count to fire only when we have actually clicked on the button, and not on the entire component, we add a matching check for this in the click
method. This is because of Tonic’s decision to use event delegation instead of multiple event listeners.
To wrap up the JavaScript piece, we also need to add this component to Tonic by adding the following to the end of our JavaScript file:
// all the above code... Tonic.add(CounterElement)
Don’t panic that nothing is displayed on your website yet. We need to add the element to the HTML file by adding it as a web component that Tonic automatically creates for you.
... <body> <counter-element></counter-element> </body>
Now, you should be able to see your counter element in the DOM, and you should even be able to see it when you inspect the DOM of the site.
Here, you can see a fully functional example of the component we just created:
See the Pen
Untitled by lowe1111 (@lowe1111)
on CodePen.
Writing components in Tonic feels pretty similar to writing components in other frameworks. In particular, the class-based approach is very similar to writing class components in React. You also have lifecycle methods that are automatically triggered when the component is updated or rendered, and you return HTML elements in a render function.
The major difference is that Tonic uses your exact written code, and the DOM structure matches exactly what you have on the server and what the user sees in their browser.
This means that you can create Jamstack components without having a build tool or rendering engine installed on the server side. It uses the power of web components.
And, although the developer experience is pretty similar to React or other modern frameworks, using Tonic is much more streamlined, the debugging feels simpler, thanks to the lack of build tools, and because of static rendering, Tonic also loads and renders lightning fast. Happy hacking!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowReact Native’s New Architecture offers significant performance advantages. In this article, you’ll explore synchronous and asynchronous rendering in React Native through practical use cases.
Build scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.