Sharing reusable Vue.js components with Lerna, Storybook, and npm or GitHub Package Registries

José Silva
Vue.js Developers
Published in
9 min readApr 24, 2019

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, npm and GitHub Package Registry.

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 or GitHub package registries. All these components must be scoped inside our username (eg. jsilva-pt).

What tools will we be using?

Lerna

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

Storybook

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

npm is the world’s largest software registry.

GitHub Package Registry

GitHub Package Registry is a software package hosting service, similar to npmjs.org, rubygems.org, or hub.docker.com, that allows you to host your packages and code in one place. You can host software packages privately or publicly and use them as dependencies in your projects.

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/GitHub package registry to distribute them to any 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;

Moving from npm to GitHub package registry

  • Configure Lerna to use GitHub as a package registry;
  • Authenticate on GitHub package registry;
  • Publish to GitHub package registry;

So, let's start!

Setting up the project

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

npm install --global lernamkdir 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

Moving from npm to GitHub Package Registry

Our project is ready to publish packages to npm but GitHub Package Registry just arrived and we decided we want to keep our packages close to the code. What do we need to do?

  • Specify on the package.json of each component the repository where our code lives, adding the following configuration:
{
...
"repository" : {
"type" : "git",
"url": "ssh://git@github.com:jsilva-pt/medium-reusable-vue-components.git"
}
}
  • Specify on lerna.json the registry where we want to publish the components, adding the following configuration:
{
...
"command": {
"publish": {
"registry": "https://npm.pkg.github.com"
}
}
}
  • Create a personal token on GitHub with the permissions write:packages and read:packages;
  • Authenticate on GitHub Package Registry using the generated token as a password:
$ npm login --registry=https://npm.pkg.github.com
> Username: USERNAME
> Password: TOKEN
> Email: PUBLIC EMAIL ADDRESS
  • Once we didn’t change any code on our components, Lerna won’t publish a new version if we run lerna publish. To enforce that, let’s use the command: lerna publish from-package, which will bump the components’ version, and publish them.
https://github.com/jsilva-pt/medium-reusable-vue-components/packages

How can we use these components in other projects?

Independently of using npm or GitHub as a package registry, 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. masterbranch publishes to npm while github-registry branch publishes to GitHub.
  • The code 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.

GitHub Package Registry is a new registry that will allow us to keep the packages close to the code, which in my opinion is a big advantage.

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

Thanks for reading. You can learn more about me on Medium, Github and Linkedin.

--

--

José Silva
Vue.js Developers

Lead Frontend Developer @ ActivePrime, Vue.js enthusiastic.