Sharing reusable Vue.js components with Lerna, Storybook, and npm

Creating reusable components is extremely important these days. We can get a lot of benefits from following the Component-Driven Development (CDD) methodology, it will result in code with more quality and fewer bugs, simply because while developing we can concentrate all our focus on a single component at a time, a single specific task.

It becomes easier to create new applications and even modify them, because we change the component once, and every place where it is being used will reflect that change. However, maintaining, sharing and reusing components can be tricky without the proper tools and that’s what I try to tackle in this article with Lerna, Storybook, and npm.

Photo by José Alejandro Cuffia on Unsplash

What is the end goal of this article?

Create a GitHub repository where some Vue.js components that can be used across projects will be developed. It should be possible to visualize and publish these components to the npm registry. All these components must be scoped inside our own npm username (eg. jsilva-pt).

What tools will we be using?

Lerna 3 (currently at v3.13.x)

Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.

Storybook 5 (currently at v5.0.x)

Storybook is a user interface development environment and playground for UI components. The tool enables developers to create components independently and showcase components interactively in an isolated development environment.

npm 6 (currently at v6.9.x)

npm is the world’s largest software registry.

How these tools can work together?

Lerna will be used to keep all our components inside the same repository (monorepos) with independent versions, Storybook to create and visualize components in isolation and npm to distribute them to any npm user.

What will be our steps?

Setting up the project

  • Create a new project using Lerna;
  • Install Storybook;
  • Adjust some configurations;

Create the first component

  • Create a JTableRow component and its stories;
  • Publish to npm;

Create the second component

  • Create a JTable component;
  • Add JTableRow as a dependency;
  • Create its stories;
  • Publish to npm;

Improve the JTable component

  • Add optional header;
  • Publish a new version to npm;

So, let's start!

Setting up the project

Let’s start by installing Lerna globally and creating a new project:

npm install --global lerna
mkdir medium-reusable-vue-components && cd $_
lerna init --independent

Here we are creating a project called medium-reusable-vue-components and initializing a Lerna project in independent mode, which is perfect for us, as we can see in their documentation:

“Independent mode Lerna projects allows maintainers to increment package versions independently of each other. Each time you publish, you will get a prompt for each package that has changed to specify if it’s a patch, minor, major or custom change.
Independent mode allows you to more specifically update versions for each package and makes sense for a group of components.”

Then, we install Storybook for Vue …

npx -p @storybook/cli sb init --type vue

… as well as the necessary dependencies

npm install vue --save
npm install vue-loader vue-template-compiler @babel/core babel-loader babel-preset-vue --save-dev

In the package.json file we can now find some commands that will allow us to.

  • Compile and hot-reload for development (npm run storybook);
  • Compile and minify for production (npm run build-storybook).
{
...
"scripts": {
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
}
}

By default, Lerna creates a packages folder where every package must be located. However, we are creating UI components so it would be preferable to have a folder called components. Lerna allows us to do it by replacing the configuration in the lerna.json file.

  • Rename packages folder to components;
  • Rename "packages/*" to "components/*" in lerna.json
{
"packages": [
"components/*"
],
"version": "independent"
}

To finish the configuration let’s just create a .gitignore file to ignore some files and folders that we don’t want to commit to our repository.

node_modules
storybook-static
npm-debug.log*
lerna-debug.log*

And it's done! The project configuration is done! 💪

Too easy, right? :P

At this point, our project structure must look like this:

├── .storybook
│ ├── addons.js
│ └── config.js
├── components
├── stories
│ └── ...
├── .gitignore
├── lerna.json
├── package.json
└── package-lock.json

Let's start creating some components! 😃

Creating our first component: JTableRow

To start a new component we could just run lerna add <package>, however, it would generate some folders and files that we really don’t want, so let's do it manually.

  • Create a JTableRow folder inside the components folder;
  • Create a JTableRow.vue file inside the JTableRow folder with the following content:

This component is simply a row of a table that receives and prints the values sent to the prop values.

I like to keep my components, stories, and tests as close as possible to each other, so let's create the JTableRow.stories.js file next to the component:

When we install Storybook, by default, it will search for a stories folder in our root project. However, as we are going to keep the stories next to the components, let's remove the stories folder and change this configuration in .storybook/config.js file.

import { configure } from '@storybook/vue';
// automatically import all files ending in *.stories.js
const req = require.context(
'../components',
true,
/\.stories\.js$/
);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);

Right now, our folder structure must be:

├── .storybook
│ ├── addons.js
│ └── config.js
├── components
│ └── JTableRow
│ ├── JTableRow.stories.js
│ ├── JTableRow.vue
│ └── package.json
├── .gitignore
├── lerna.json
├── package.json
└── package-lock.json

And we should be able to see our first component by running the command npm run storybook, that will give us this page:

Storybook displaying JTableRow stories

We are not done yet! We need to make this component available to be used by other applications/projects/components. In order to do that, let’s create a package.json file inside the JTableRow folder, with the following content:

{
"name": "@jsilva-pt/j-table-row",
"version": "0.0.0",
"publishConfig": {
"access": "public"
}
}

As described in our goal, our components must be scoped. When we create an account on npm, our username can be used as scope name, so we only need to:

  • Prefix the component name with the scope name (@jsilva-pt/j-table-row);
  • Set the access to the component as public.

Regarding versioning, when publishing a new version of the component, it will be automatically incremented, so we set its version to 0.0.0.

Our final steps are:

  • Create a repository and push our code.
git add .
git commit -m "JTableRow component"
git remote add origin git@github.com:jsilva-pt/medium-reusable-vue-components.git
git push -u origin master
  • Login in our npm account: npm login;
  • Run lerna publish:
Publishing JTableRow component with Lerna

And we are done! Our first reusable component is published and available to be used by everyone thought npm registry.

JTableRow component on npm registry

This component, by itself, is not very useful, so let's create another one that will use it as a dependency.

Creating our second component: JTable

  • Create a JTable folder inside the components folder;
  • Create its package.json;
{
"name": "@jsilva-pt/j-table",
"version": "0.0.0",
"publishConfig": {
"access": "public"
}
}
  • Add JTableRow as its dependency, which will update our JTable package.json and generate a node_modules folder inside the JTable;
lerna add @jsilva-pt/j-table-row --scope=@jsilva-pt/j-table
  • Create a JTable.vue file inside the JTable folder;
  • Create a JTable.stories.js file inside the JTable folder;
  • Check Storybook to make sure everything is ok;
Storybook displaying JTable and JTableRow stories
  • Push our modifications to Github;
git add .
git commit -m "JTable component"
git push
  • And publish the component: lerna publish;
JTable and JTableRow components on npm registry

Our final structure is:

├── .storybook
│ ├── addons.js
│ └── config.js
├── components
│ ├── JTable
│ │ ├── JTable.stories.js
│ │ ├── JTable.vue
│ │ └── package.json
│ └── JTableRow
│ ├── JTableRow.stories.js
│ ├── JTableRow.vue
│ └── package.json
├── .gitignore
├── lerna.json
├── package.json
└── package-lock.json

Improve JTable

Our JTable is already useful but it would be nice to have a header like every normal table has, so let's do it.

  • Add a prop header that accepts an array of values, iterate and displays them;
  • Add a new story to the JTable.stories.js file that demonstrates this new option;
  • Check Storybook;
Storybook displaying JTable and JTableRow stories
  • Push our modifications to Github;
git add .
git commit -m "Added header prop to JTable"
git push
  • Publish the improvement made: lerna publish;
Publishing a new version of JTable with Lerna

And we are done! A new version is available.

JTable and JTableRow components on npm registry

How can we use these components in other projects?

Just install them like any other dependency, through npm or yarn.

# install
npm install @jsilva-pt/j-table
# importing JTable automatically will bring JTableRow
import JTable from '@jsilva-pt/j-table/JTable'

Final Notes:

  • We didn’t cover tests to our components, however, it is essential to have them.
  • The source code can be found at Github and it is deployed on Netlify.

Conclusion

With Lerna, we can manage several components in the same repository instead of creating one for each, which would become really difficult to maintain.

Storybook, for me, is essential in every project, even when components are not being created in a separated repository.

npm don’t really need a presentation, it is the “world’s largest software registry” that allow us to easily share components between projects.

Currently, it is very easy to create reusable components. Tools like Lerna, Storybook and npm really help to isolate our components from the application, resulting in more focus, quality and development speed.


Thanks for reading. 👏 Clap it if you like it. You can learn more about me on Medium, Github and Linkedin.