Getting started with Vue (2.x), Storybook and Typescript

Neelesh Joshi
11 min readDec 18, 2019

In this article, we will take a look at getting started with Storybook and VueJS (2.x) and in particular, using Typescript, class-style syntax and the property decorators. We will build a noughts and crosses (tic-tac-toe) game.

Storywhat?

Storybook is a tool for developing UI components in isolation, with support for most of the popular frameworks including React, Angular and Vue. In essence, Storybook becomes a living, breathing guide for the building blocks of your project. It can be an excellent addition to your development process.

The approach in which you build your project may change if your current principles do not correlate with Atomic design or micro-frontends. The key here being modularity. Storybook encourages the use of Component-Driven Development (CDD). In summary, there are several great benefits of using this methodology. By developing a single component at a time, you can alleviate the need to manipulate various parts of the app to emulate the needed state for the component. In turn, the UI coverage should be more comprehensive by enumerating all relevant states, including ones that may not be as common in development such as an error or loading state. There are two in-depth articles you can read here and here.

Another aspect of this is the adoption of a form of “Visual Test-Driven Development”. As with the regular Test-Driven Development in which you are supposed to write your tests before your code, with Storybook, we can write our stories before we fully flesh out a component. With the other benefits, this can help to ensure you produce a robust UI for the end product, which is not an easy feat to achieve, especially for larger projects.

Building your projects from the components up to the screen can also change a number of important aspects for each component. For example, the ownership of both data and styles must be thoughtfully decided as you build up components — should your list item define its own size and directly mutate its data or should that be managed by a parent component ? Initially, while it may add some development time, being interactively develop and test your components certainly has its benefits.

Let’s get setup

If you have not already, get the CLI installed for Vue or if you have NPM v5.2+ installed you can use npx if you prefer.

npm install -g @vue/cli

Use the CLI to create the project or NPX and manually select the following features: class-style syntax, babel with typescript, SASS (node-sass), VueX and Jest (unit testing).

vue create <app-name>
npx @vue/cli create <app-name>

While we won’t be using the router for this tutorial, it may come in handy if you decide to expand this out. Next, we can add Storybook using the Vue CLI as there is a Storybook plugin available. There are other ways to add Storybook into your project; however, this way provides the need for minimal setup so we can get started as fast as possible. The CLI installation will also add an example component along with a story.

vue add storybook

Before we carry on, let’s make sure everything is working.

npm run serve // Serves the Vue app locallynpm run test:unit // Runs our Jest, unit Testsnpm run storybook:serve // Serves Storybook locally

If it hasn’t opened in your browser, navigate to localhost:6060, and you should be able to see our Storybook instance, with the generated test component.

Finally, we need to update the config for Storybook to look for our Typescript stories in the whole project directory. Update ‘config/storybook/config.js’ with the following:

For this tutorial, we will keep the stories and the components together; however, for complex or large projects, it may be preferable to store the stories in a dedicated folder.

Time to build some components

So as a brief overview, we will build a tile component, then the board component, a text component and finally the screen.

So let’s start with the tile component and its accompanying story: components/Tile.vue and components/Tile.stories.ts.

We start with a simple base for the component, which we will build up once we have worked on the story. To initiate our component, we call the storiesOf method and pass into it the name that will appear in our Storybook app. We then have to call add() for each of the states that we want to describe for the component. For now, we have a default state that displays the component. If we look in our app, you should see the Tile along with the default state we just created. The app window is easy to understand, with a navigation sidebar for all our components, a canvas section to display our component(s) and at the bottom, tabs for various addons that are currently in the project. Using the CLI to install Storybook includes the Actions and Knobs addons.

As with TDD and writing the tests first, we will build out the story before we finish our component. The tile component will need to have three states; empty or occupied with an ‘X’ or an ‘O’ depending on the player that has clicked on it. We will also need to use one of the previously mentioned addons — actions. From the documentation, ‘Actions help you verify interactions when building UI components in isolation. Often you won’t have access to the functions and state you have in the context of the app. Use action() to stub them in.’.

So let’s update our story.

As mentioned before we wanted three states for the tile which now replace the ‘default’ state we had previously. To create the callback for when we click a tile, we can use action(), and as we need to use this on all the different states, we can bundle it within a single variable and pass it to our story. In the case of a more complex component, with multiple actions, this can significantly reduce the amount of code written. Also, for re-usability, we have various exports that we can use elsewhere in the project, such as the definition for the Tile data (interface) as well as an example of said data. Enumerations and interfaces are usually in a separate directory.

Now in Storybook, you will see some errors, but we will finish our component, and that should fix everything.

Moving back to Storybook, refresh if needed, but all the errors should be gone, and we should have our component with the three different states. If you now click on the tile too, you will see the action we defined in Storybook.

The next component to build will be the game board which will compose of a collection of board tiles (9). The component is simple so let's get straight into the story and component: components/Board.vue and components/Board.stories.ts.

The story for the board only has two states showing the board empty and with some selected tiles. The board state is an array of the TileInterfaces which we can pass to the board to display. As with the tile(s), the board will need to show the click event action. Its functionality will, however, be handled by the screen component, later on, so we just need to confirm the interaction is working correctly for now. Now when we click on a tile on the board, we also emit the tile, which you can see the data for in the actions tab.

Before we move onto the next component, we can remove the generated story and component for the button. Next, we will introduce another addon, Knobs. Knobs give you the ability to manipulate the props of a component. To demonstrate this, we will create a simple text component to display the current game status. Thanks to the CLI, the addon is installed already.

Create the text component and its story, components/InfoText.vue and components/InfoText.stories.ts.

There is not that much to say about the component itself; it is just a div that displays the text prop with some styles. We now import the withKnobs decorator and the text and boolean knob types, so that we can define the props that we want to be able to have access to in Storybook. Then to the addDecorator() function, we pass withKnobs as a parameter so finally we can integrate the two knob-types for the props into the default story.

Back in our Storybook app, in the knobs tab, we now have access to the props that we defined in the story. You can change the text and the styles from what we have access to with this component. In my opinion, this is one of the best addons for Storybook, for everyone who is browsing the component library (not just developers).

Onto the Game screen

The last item to build is the game screen, which will be a composition of the board and text components. For this, we will introduce one more aspect into the stories (and game), VueX. If you followed along with the same CLI options, we don’t need to install it. For the store itself, I prefer to use the following library; however, it is not required.

npm install -D vuex-module-decorators

First, make sure your main.ts reflects what is below, and we need to create a file for our store src/store.ts.

This is just to define our store and its included modules. It’s fine to have this within your main.ts, however I prefer the separation.We can now update our store which should be at store/index.ts. Rename this to State.ts and update with the following :

There are three main items we want to manage in our store, the current state of the board, the text to be displayed and the current player. The focus of this isn’t on VueX so I won’t be explaining much but its a straightforward store to store the game state.

We can now create our screen, views/Game.vue and its story views/Game/stories.ts. Also, we need to update our router and navigation to be able to navigate to the new screen, so let’s do that first.

The story for the game screen will have an essential difference to all the previous ones featured so far, instead of various variables for the data we need, we will use VueX. We can include a store directly in the story(s) that need it and use a simplified version of what we just created as it only needs to provide the data. Other than that the story will be straightforward, with just one state. The screen component is a composition of the board and text components, with some styles to position them.

Now, if we jump back to Storybook, we have our screen with data loaded from our VueX store instead of the imported variables. We also can view the game in our Vue app, but it won’t do much without any logic.

Here is the updated script for Game.vue with some basic game logic, making the game somewhat playable.

Wrapping things up

With everything complete, you should have a working game along with the basics of Storybook down. While at first, it may take a little longer to write the stories and get used to the component-driven development style, I firmly believe that it is worth that extra investment. While there is plenty of room for improvement with the logic and other aspects I hope this has piqued your interest. The revision to project and component structure, along with the ease of writing the stories has led to a great experience of including Storybook into my workflow and is something that I will try to include most, if not all of my future projects.

Here is the completed source for this project.

Bonus : Automated Testing

Note : I didn't include this section previously as I ran into a small error with VueX and Jest, but for the most part it works. The code for this section is also in a separate branch which you can find here.

Now we have these stories for our components, with the help of another addon we can get some automated tests out of them too. Storyshots allows us to automate snapshot tests of our component’s UI in conjunction with Jest. With the only caveat to make sure that the components render data that doesn’t change or the snapshot tests will fail every time. For example, if you are using Date.now() replace that with a hardcoded value instead.

So add the addon and the dependencies we need

npm i -D @storybook/addon-storyshots jest-vue-preprocessor babel-plugin-require-context-hook

Next update your babel config, babel.config.js.

module.exports = (api) => ({presets: ['@vue/cli-plugin-babel/preset'],...(api.env('test') && { plugins: ['require-context-hook'] }),});

And then your jest config, jest.config.js.

module.exports = {preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',transformIgnorePatterns: ['/node_modules/(?!(@storybook/.*\\.vue$))'],};

Create the spec that will initialise the addon for us, tests/unit/storyshots.spec.js.

import initStoryshots from '@storybook/addon-storyshots';import registerRequireContextHook from 'babel-plugin-require-context-hook/register';registerRequireContextHook();initStoryshots({/* configuration options */configPath: 'config/storybook',framework: 'vue',});

It's important we add the configPath option as the vue cli does not place the Storybook config in the default location.

Finally, update your test/test:unit script in package.json so that it will watch for changes.

"test": "vue-cli-service test:unit --watchAll",

So that’s it to get everything setup. Not too painful right? Now we can run our unit tests, and you should see the Snapshots all passing, seven in total, for the seven states/stories of our components.

Now if we head into our views/Game.vue and duplicate the InfoText component. This should cause our snapshot test to fail once the tests have run again.

Finally, after updating a component you’ll need to update the snapshot so it will pass again. So we can add another script in our package.json that can update the snapshots.

"test:update": "vue-cli-service test:unit --u",

If you now run our new script, you’ll see the snapshot has been updated successfully!

--

--