Storybook + StencilJS + Ionic 4 Angular under one roof?
Design Systems becoming a trend, projects require more and more structure and systemising to establish a reliable single source of truth which helps keep products cohesive as the amount of interfaces, components and platforms scales. This approach is great if you struggle keeping things in check within a large team and end up re-developing that one button every single time instead of reusing it across pages. However, it does carry a few potential pitfalls.
For one, it’s a nightmare as trends change up and new tools/frameworks/libraries/versions keep popping up. You might end up caught up in an eternal loop of always trying to catch the latest and greatest for fear of sticking with what you know and becoming an outdated old fart. Furthermore, since nothing is perfect, wanting to have a solid system in place might leave you with a large pile of different tools that all need to connect and work together as they change and grow or fade away into the deprecated hell.
Today, being a frontend developer, a design system engineer, or whatever position in between might mean that your daily job is balancing those perks and struggles and if it is so, all I can say is “welcome to the club”. I’m here with a solution that worked for me. Not saying it’s the best (or close) but it works and solves some of the aforementioned problems I faced as I started working on my first Design System. I ended up opting for a Storybook, StencilJS and Ionic Framework combination, so if that makes any sense to you keep reading.
What are those ?
Ionic Framework is “an open source UI toolkit for building performant, high-quality mobile and desktop apps using web technologies (HTML, CSS, and JavaScript)”, according to the official Ionic documentation. Originally built on top of Angular, it now supports integration with other popular frameworks, such as React and Vue (currently in beta). Thus, if unsure of the future, it might give you a bit more flexibility for switching up later with a bit more ease.
To be honest, I started with Ionic because it was what the company I worked with on this project chose but (being a React lover) I have to admit that it was pretty nice and very straightforward, so it wasn’t the struggle I (stereotypically) expected from anything related to Angular. My bad judgement, I guess. Ionic actually comes with a set of components, however, for a stronger brand experience, we decided on having them custom made.
StencilJS seemed like just the tool for the job. Originating from the Ionic team first as the support for the framework, but later on extended to support Web Components and customised for building Design systems, it offered all the necessary features and was once again very framework-agnostic, supporting all of the main integrations and therefore, could be used by anyone for the component library.
Finally, every Design system requires some kind of structure, documentation and management to keep the consistency and organise the components as they pile up. This one is tricky because you can easily end up having multiple tools to handle different things and trust me, you don’t want to make it too complicated. Why? Because a Design system can only do the job if people can take advantage of it easily and want to use it in their daily work.
I decided to go with Storybook. Honestly, the choice here might be based on preference, so go with whatever you prefer. I like Storybook because it gives me a great overview over the different components, can generate documentations and even comes with a bunch of awesome add-ons to test components and expose the different use cases and variations. The way I see it, this can help keep the team understanding of the component usage unified which should reflect in the products they create.
The Setup
It might be due to being more of a beginner but this was a bigger struggle than I expected. That’s too bad because the three make a lot of sense combined, which is why I decided to write it up all in one place for any like minded individuals that venture down my path in the future. If you end up reading this and realise things have changed since, please let me know in the comments so we can keep this piece up to date! 😊
Since the Stencil component library is the piece in the middle of it, there’s two parts for this setup: integrating Storybook into Stencil project and pouring the Stencil components into the Ionic Application.
Part 1: Storybook + Stencil
As of now, the best option to do this is Storybook for HTML (Storybook for Web components is currently in alpha, so check out if it’s out yet first). I assume you have a basic Stencil component library up and running or you might want to do that following their guide and I started out with the Storybook guide for manual setup (available here) — I’ll describe the steps but use it for reference as things might change. Let’s start:
Add stroybook html to your project (updated on 9/11/2020 to match the newly recommended setup, original below):
npx -p @storybook/cli sb init -t htmlORIGINAL: npm install @stroybook/html — save-dev
Add babel core and loader to your project:
npm install babel-loader @babel/core — save-dev
Add storybook script to scripts in package.json (load with -s dist flag to have Stencil dist build served as static files):
{ “scripts”:
{ “storybook”:
“start-storybook -s dist”
}
}
Create main storybook file at .storybook/main.js
and add the following:
module.exports = {
stories: [‘../src/**/*.stories.[tj]sx’],
};
Add npm run all package:
npm install npm-run-all — save-dev
Add a script for running both Stencil and Storybook simultaneously:
“story”: “npm-run-all — parallel start storybook”
Configure .storybook/preview-head.html
file with the following content:
<head>
<script type=”module” src=”/component-lib-name/component-lib-name.esm.js”></script>
<script nomodule=”” src=”/component-lib-name/component-lib-name.js”></script>
</head>
Start creating stories and components and whenever you’re ready check out your work by running:
npm run story
There you go! Time to use the awesome components you built in the Ionic application!
Part 2: Stencil + Ionic Angular
One would assume that Stencil and Ionic being created by the same team implies good connection. Not to say that it’s too difficult to combine them, but they could have documented this in a better way for sure!
First as Ionic Angular comes with Sass, it’s probably useful to add the Sass plugin to Stencil as well. This is really simple, just install the plugin:
npm i @stencil/sass
and then add it to stencil.config.ts
file export:
plugins: [
sass()
]
Right now there is no official documentation on adding custom Stencil web component libraries into Ionic 4 Angular projects, which is why it requires looking at dist output target and Angular setup. The best way is publishing the component build as an npm package and then importing it via npm install but that isn’t everything you need to do (as you’ll see later).
Let’s say that we have a standard Stencil component library with dist as one of our output targets which should be looking somewhat like this:
outputTargets: [
{
type: ‘dist’,
esmLoaderPath: ‘../loader’
},
…
]
which will build the web components in a /dist folder when we call npm run build
command.
First, we should publish the components library to npm:
- Run
npm login
and follow the steps to login with your npm account (has to be registered beforehand at npm.js) - Change project name in
package.json
to how you want the package to be called and set the version (I guess 0.0.1 will do for a start). Luckily, Stencil already sets up most of the other things necessary to build the package. - Remember to work on the
readme.md
file to make sure others understand your outstanding work. - Run
npm publish
to get it up and running. - Whenever you add anything more, always up the version afterwards before re-running the
npm publish
command. If you’re new to versioning, it might be good to Google the three numbers and their meaning before. :)
Following is the integration into an Ionic 4 Angular project:
Once again I’ll assume you have an existing project, so you already set up the basic project and are familiar with Ionic (otherwise feel free to come back here after following the extensive Ionic documentation).
Run npm install --save name-of-package
to install the package into the project.
You have to import the package into the modules of the pages that will use it (e.g. home.module.ts) by adding import 'name-of-package';
into the file.
Since we’re talking about Ionic Angular project, we have to allow non-angular elements in the project to prevent errors. Therefore, import and add the CUSTOM_ELEMENTS_SCHEMA
to the same module file from above. The import statement should look something like that:
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from ‘@angular/core’;
and the schema is added within the NgModule class (marked with a @NgModule decorator), under schemas. After adding the schema, your @NgModule should look sth like this:
@NgModule({
imports: [
CommonModule,
IonicModule ],
declarations: [HomePage],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class HomePageModule {}
The last step is calling the defineCustomElements
function (another Angular requirement) in a main.ts
file within the Ionic project (located in the /src
folder). First import it from the package:
import { defineCustomElements } from ‘package-name/loader’;
This import is based on how your output targets are setup (check above) and works on the default setup of Stencil at the time of writing.
After importing feel free to add the function (somewhere in the bottom of the file, after the platformBrowserDynamic()...
) like this: defineCustomeElements(window);
.
Afterwards, you can use the components in the html element of the page with the Stencil tag you specified.
TODO: Some users reported issues about the component library not being included in the build back in 2018 and came up with an additional step to prevent this problem. I will test it later and come back to edit this post if this step is still needed for Ionic 4 and above. However, if you experience issues with components when trying to build your Ionic project, you can try the following:
- Create a
copy.config.js
file in the same folder as thepackage.json
and add this inside:
module.exports = {
copy-component-lib-name: {
src: [‘{{ROOT}}/node_modules/component-lib-name/dist/component-lib-name**/*’],
dest: ‘{{BUILD}}’
}
}
Then, add a field to the package.json
file (same level as name, version, etc.):
“config”: {
“ionic_copy”: “./copy.config.js”
}
That should help! :)
Feel free to let me know what you think or if you have any improvements in the comments! Thank you for reading! ☺️👌
And for those eager to know more:
References and Further Reads
This guide is a result (and potentially update) of the followiong amazing sources, where more information on the different steps can be found: