Manu Martinez-Almeida

Stencil: a built-time approach to the web

We all know and love framework-like features such as hot module replacement, reactive properties, templating, CSS-in-JS, lazy-loaded bundling, etc. Stencil is a new approach, a build-time abstraction with framework-level productivity, that generates hand-optimized components using future-proof web APIs. We’ll discuss the architecture of Stencil and the innovations a compiler can introduce to your apps and design systems!

Portrait photo of Manu Martinez-Almeida


MANU: Hello. Hi, everyone. I'm so excited to be here. I hope you like my talk.

This is the last one, everyone is a little bit tired. But today I'm going to talk about the Stencil which is this open source project we have been working for the last few years. And how a compiler can help you build better complications and in general more specifically design systems.

So, my name is Manu and I'm a software engineer at Ionic. The team that builds Ionic framework and the Stencil itself. We basically are a tooling company. I want to help web developers build applications.

100% based on web technology. Okay. So, before getting into too much detail, I want to start with the true history.

12 years ago, JavaScript was not what it is today. It was a mess. Different browsers have different behavior. Internet Explorer, Netscape. And JavaScript itself lacked the highlevel APIs that today we consider basic.

So, it was hard to write for this application on top of it. Then something happened. And it came to solve all the problems that JavaScript had at the time. It normalized the behavior between browsers and provided a higher set of APIs.

Actually, some of them are part of the web today. Like selector. But the biggest innovation is that it allowed JavaScript to grow as a language and allowed it to be used for more applications.

Not just like some simple form validation. And in fact, my company built one of the first drag and drop tools for UI components. And it was based on a query mobile. So, in order to instantiate and create the components, you have to use the imperative of the query.

It worked but it was not great. And some years, and the JavaScript ecosystem was a little bit healthier.

So, the problems that JavaScript had  AngularJS had to solve was not the same ones. It does not only provide higher level APIs, but a way of working. A way of organizing a set of good defaults that today we know as a framework. So, that helped JavaScript to grow even more. And introduced the concept of directives.

That was mind blowing to us. It was closer to our idea of components. We were so excited about it that we decided to port everything we got into this new system because it was a big step forward.

And that's how Ionic was born. Then the team announced Angular2, it was going to be faster, smaller, mobile first. Everything. And we decided to make the investment.

And that's how Ionic 2 and ionic 3 was born. Even though the name was the same, it would require a complete refactor.

Think about it, this is already the third time that we have to code the same components. And today, well, the ecosystem is completely different. There are many frameworks and alternatives, and all are great. It depends on your own preferences, your team, your skills were your product or even just for hiring in a specific country.

So, going back to our mission as a company, that we want to help all the web developers. Not the ones using a specific framework. So, we have to think about how we could achieve that. If we have to port these 100 components to every popular framework of today and maybe tomorrow. And, of course, the answer is not.

We have to go through this refactor many, many times and we know that's not going to happen. It's so time consuming. And we realized this was not going to be the solution. So, we've got to think about something else.

And think like frameworks are great for building final products. Even if you say that React is not a framework, it is. They built a framework around it. And that's not the last thing. It creates the React app, an organizer and a way of testing.

A set of good defaults. And that makes sense because that allows you to focus and build your product. You don't have to make hundreds of decisions every single time.

So, yeah. And don't reinvent the wheel every time. But at the same time, they are terrible for building reusable components. And you might say this is working for you today. It did for us.

But at some point, you might want to use a different technology or your use cases change. Or you just want to serve your component with more people. And not all the people will use the same framework.

So, the point of building reusable components is to last in time, right? So, it's pretty much like an investment you make to allow you to build faster in the future. So, you should build them on top of futureproof technology. That's the web. That doesn't change.

So, the solution of this problem, of course, is web components. But again, this is really important.

Web components does not solve any other problems. It just is the universal mobile components. And even if you like it or not, it will work. So, I'm sorry for that sometimes, but this will work.

You can go around it. So, as I said, it doesn't handle anything else. So, you'll still  I believe that frameworks will exist forever. They will not be replaced.

And Angular and React and Vue does not replace the Web APIs of today, they just live together. And, of course, we know in the past that some APIs, web APIs that break, they remove sometimes. But there is something in common for APIs that have a full consensus across browsers.

It's that they will be around forever. So, even if  yeah. So, think about the website you built 20 years ago. It still works because the framework can break, but the web can't. If you build with a web component, it will work in the future framework.

Around that idea, we don't to want make the decision of the framework you have to use. We don't know your product. We just build the components.

So, around that idea we create Ionic 2, the latest Ionic, ionic 4, is based on web components. And this fourth refactor is very different. Because this time we didn't build on top of the Angular directives or components. This time it's built on top of standard web APIs.

They're just web components that sustain the HTML. But before that we found, as I said, that web components have little API. It doesn't solve any other thing.

So, what happens when a software engineer has to deal with, you know, a lowlevel API. Well, they just read an instruction. And that usually comes with a new problem. And disable of overhead. But I'm not saying it's a performance overhead.

It's a knowledge. It's a runtime logging. You are no longer building on top of this fix it web API. You are building on top of something else that can't break and change.

So, we have to start thinking about what will be the sweet spot here. What if the instruction is comparative time? Meaning that there's no specific framework, no specific runtime. You see your component, but it doesn't come with a welldefined runtime. Instead, the competitor will take your components and generate the best possible source code.

And it will generate the best possible component. This has worked for decades. Languages like C and REST.

They don't have to care if there's a new CPU or they have to target a different CPU architecture. Instead they just use a different compiler, or they upgrade the existing one. And that is exactly what a Stencil is. It's a build time instruction. It's a compiler for the web.

As a technology, the web will be always this moving target. Sorry, the web will be the hardware. This always moving target. And Stencil, the compiler. Taking your components and using the newest APIs without the developer having to make any single change.

So, the idea is that we are not only able to update the highly optimized component but avoid any type of breaking change. This is really important for us. This is the main use case.

Because we have already gone through all of these refactors. And our main use case is to build reusable components. As I implement design systems. And being futureproof.

So, we have the same design API compiler of Stencil to restrict ourselves to our interfaces like properties, HTML attributes and DOM events. We don't try to have a stenciled way of doing things.

So, this way we can keep changing how the compiler works. The optimizations we apply. And using new APIs without requiring developers to make any changes. So, for example, let's say that next year Chrome ships a new feature that's built in.

Or some kind of template system. This is going to be built into the browser. And it's going to be much faster because, well, it's probably programming in C++, whatever they use.

And you don't have to see any JavaScript. Right? In a stencil, that could happen, and you will not have to make any changes. We will just fill in your component and change how that thing works. There is not a specific render.

Like even, you know, we are not using a specific render. We could change it.

So, in addition we can support all browsers in the same idea. Like a compiler can generate different targets for different browsers. A comic can generate for different architectures, we can do the same for different browsers without developers having to think about it. So, for example, most of your users actually are using Chrome, Firefox Safari, modern browsers will get the smallest bundle without any kind of polyfill.

Without using modern JavaScript features like native async await or ES modules. And all browsers, if you had to support them, like Internet Explorer, it will get a little bit bigger ES5 bundle.

And there's an example of the same component. It's ion button, a button with the design and the styles. The stencil compiler generates up to eight different versions. But that doesn't mean that the client will have to download all six files. In fact, it's just going to download one of them.

So, we can see that some files have the ES5 prefix, yeah? So, that means that ES5 is the code. SC means that for browser that doesn't support DOM. But in any case, this way we can cover the whole spectrum of browsers in the most efficient way. And we can do it with the best developer experience.

The ones you are used to. Think about it.

We build a stencil for us and obviously we used frameworks before. We wanted to be productive in the same way. We are not telling our engineers to write assembly. You know, like assembly for the web.

That will be the web components. But have this service. Like service worker generation, prerendering, fast incremental builds. Like a tradition with sys JavaScript in their types. And we take the static analysis of this component and we can even generate what we mean.

You just deploy your docs in GitHub without making changes. Or we can integrate with things like Storybook or your web generator. So, for example, for the docs of Ionic in the website, we use a target that outputs all the information.

And even like  we even acquire the CSS and extract the CSS variables that you might use because this was a big deal for us. You know? We have in this testing. We use Puppet under the hood. Everything, this is already done. You don't have to implement these things or confuse these things.

We built this. And an example, my colleague, Randy, they fixed an issue with an ion input. And we can review that previously. But the idea is that we can have web components and still have frameworklevel features.

Today I'm announcing a big milestone in development of Stencil, Stencil 1. And you maybe wonder, we are using  we are not using numbers. Just like from 1.0. But because Stencil 1 is incremental.

It's not an incremental release. It's a consolidation of the API.

Stencil was initially built to solve the problems to Ionic. But it solved more problems for more people. In the last month, we have been collecting feedback from thousands of developers and using it for this API we are proud of. In addition, we have a new runtime, a new compiler. So, one of the most interesting things about the Stencil is that when you  a Stencil is about components, right? So, you are not using it most of the time to create an app, right?

So, how will you handle the lazy loading? Because most of the time it's about routing. It's a routingbased lazy loading. Here we can't have that. We took a different approach, it's a componentbased lazy loading.

We want to use a stencil to build the components, but they will be used in different places. Might be used in React.

We have ionic React, ionic Angular. They have different ways of lazy loading. So, in order to do that, a stencil is able to perform a static analysis of how the components depend on each other and apply the best optimizations. And the developer doesn't have to deal with it.

So, in Stencil 1 we have a new algorithm inspired by the learning technique. I'm going to explain because it's kind of weird. But the thing is that our use case, all the components are entry points. Because we don't know how they are going to be used.

So, like traditional algorithms like the one used in Webpack or roll app, they will just generate a different bundle for each component if you want to lazy load it. So, if you have 20 different components at the same time, you will have to download 20 different files.

So, we came up with an algorithm to make this feature. So, in this image we have nine components. There are the numbers behind them. But you can imagine there's base logging, base tutorial. Whatever.

So, thanks to the static analysis I said before of how components depend on each other. Like how they are using the templates, we can extract the information of the dependencies with our components. And that's what these lines represent.

All right. So, the next step is to revolve the transitive dependencies. So, for example, if we know that the component one depends on three, and the three depends on six, that means that by the transitive property that one also depends on six. Oh, damn it. Okay? Make sense? Everyone? Yes? Okay.

So, we keep  we basically do the same with all the components, okay? So, let's focus on the component number nine. It has a lot of arrows pointing to it. That means that it has a lot of dependents. Some component like five doesn't have any.

Okay. Let's focus again on component number nine. As I said, each arrow represents a dependent.

So, in this case we collect the dependents of nine. So, yeah, it's 3, 1, 6, 4 and 2, right? Where these lines are coming from. And we do the same with all of them. Understood? Yeah? Yes. Okay.

So, let's focus again in the component number nine. For our example. So, we have  let's get the dependencies. So, we go  we get already all the data. But we just have to encode it in a different data structure.

So, what if we could convert these dependencies of each component into a vector just like word embeddings do with words. So, for example, the component number  let's encode one and just put a one when it's a dependent and a zero when it's not. So, one, it's a component. Is it dependent or not?

It is. Right? Yeah. So, one. Two, three, four is as well.

But five? Five is not. We don't have any arrow pointing to six. We will encode with zero.

Six it is and seven, eight, nine is not. Okay? Well, we do the same with all the components. And we get an array of vectors. So, while we had trans filed our components, we can imagine it has points in the space.

Here is a 3D space. But you can imagine then as with more dimensions.

The core idea is to group together the components that are close enough in this space. But in order to know if they are close enough or not, we just have to calculate the distance. And how do we calculate the distance? Well, here is my friend, a philosopher and mathematician, and start following him. He's a smart guy.

Came up with this famous algorithm that you probably remember from school that relates the lengths of a triangle with a hypotenuse. And in this example, the hypotenuse is the distance between the two blue circles, okay?

Well, I'm not here to scare you, promise. But in our case, it's a little bit more complicated. Well, this model in two dimensions. But the idea is the same.

Turns out we can use the generalization of the Pi theory to solve this problem. That's where the fuck iris, sorry. And we can have the all the points.

And going back to with this, we have the approach. And going with the external and internal apps, it is better than anything. In the app, it generates budgets better. But if a developer has to try to budget the 100 components in a more efficient way. Most of the time the conditions are changing.

So, this one. In addition, you know like now imagine that preferably optimized bundles. And we can even generate hints so the browser can download and parse all the assets that your application needs. Yeah, in the critical path.

As an example, we have the module preload for all the JavaScript because we use modules natively. So, only JavaScript requires a path. It's downloading  like, yeah, in parallel I was saying, yeah.

So, another thing is a faster runtime. We have profiled and refactored it to be much smaller and fastener time. The new runtime, like optimization killers and async away, the schedule. We use APIs, native async modules and async await.

In this chart we have 800  8,000 ion button components. It may look like a simple component, but under the hood it has a lot of classes, a lot of nested components that will try to replicate the iOS designs.

In this stress test we're actually adding 200,000 notes. Historically, it took 6 seconds to isolate the app. With Stencil 1, it's 3 seconds and uses pretty much half the memory. Which is  yeah.

[ Applause ]

So, two times faster and, yeah. Okay. Let's continue. Another advantage of using a compiler is we are no longer limited by resyncing to remove parts of the code that are no longer required.

Instead we can compile the metadata in order to heavily optimize components. So, a hello world app compiled with a stencil, so, it's so small you can barely see it.

It's just 133 bytes uncompressed. Why so small? Because it doesn't have any  it's a hello world, right? It doesn't need any runtime. So, the compress is even smaller. But here the important idea is that while a hello world example isn't really an artificial example, we are really proud about the power of a compiler can do applying heavy optimizations and completely remove what is not needed in a specific browser. If it's needed in some browser, we'll do that.

So, in this optimization, it can also apply to bigger use cases like a complex application built with Ionic or almost like a standard to do MVC. In our case, it's just 2.4 kilo bytes. And to put a little bit more context, well, the same application built with a different technology. We feel really proud about what we have achieved.

And all of this without meshing with your known modules. You probably have seen this image before.

I think? Yeah, in the first talk we also see it. But, you know, it's a good example. It's useful. It's heavier than a massive black hole. It might take a couple of minutes, okay?

In general, it's a big frown on today. Not because is there a dependencies held in every project. You don't know which code is running, actually. There is a healthy dependency in package managers. I have nightmares with npm apocalypse.

You can download anything. There is many things moving on, moving parts in our web projects. And developers already find creative ways to get around and get some space in their computer.

So, sorry. So, one of the same principles of a stencil is to deep dependencies to the minimum. So, it does not only make the installing screens much faster, but your project more futureproof and stable.

It's not  it's not the first time. At least for us. That a dependency of that dependency changed and finding a solution becomes a problem. For example, I think for Node Sass, you update with Node 12. It stopped working.

This kind of thing. So, for instance, if you open Node modules and you will only find two folders. Stencil and type of script.

Here we have the largest stencil drawing in the world. The cosmonaut. A stencil is a tool you use to paint something. But it's not longer there.

You can use the same stencil many times. But it's never part of the final product. And that's why a stencil has the name it has.

It's like my Max, my CEO said, it's like using a stencil for components. And we said, that's it. When you build a component with a stencil  sorry, when you build a component with delivery, element, angular, React, Vue, your components you upload to npm have a strong dependency in the framework and this version of the framework. But the components you generate with the stencil, they are not the stencil components.

They don't have a dependency on the stencil. They don't depend on it. If I have time  have time? So, I have a quick demo of how easy it is.

We'll see if I can  very quickly. Note. Okay. All right. Okay.

So, yeah. So, we have the terminal. We just run NP in it, Stencil.

We run it. We select  create a simple app. But most of the times there's a component. An app, JSConf.

Done. Yes. It's already downloaded.

You will have to  it's already downloaded, and we've run npm install for you. When you select the starter, while you were typing the name, we already start downloading everything. So, now we just go here. Npm start.

I'm using Edge, actually. So, for example, here we have a very simple application. We can  we have this feature I taught before about the frameworklevel feature.

If I open the code and try to make any change, even though these are web components, I can have a module replacement. So, for example, here I'm just going to make  just going to make  change the button from profile page to hello page. Safe. And, you know, you may to want run the docs.

Well, you just will go here. And the configuration.

And you will create it like type docs. I say, we have docs station, docs  so, if you have that and you run the build, it will generate a readme file in every like in here in this folder, in this folder, in this folder with the analysis of these components. Let's say you want to prerender the page. This is something that you won't expect with web components. Let's see if this is running.

No. It stopped. All right here. Stop it.

So, it will say npm run build, prerender. In this case we will run a build. And it will create the file and will have two pages. We have the profile page and another one.

Yeah, I think that's pretty much it. Okay? Yeah.

So, you  I really encourage you to check it out. Or the npm stencil, give it a try. Yeah. It's like magic.

So, thank you!