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

José Silva
Apr 24, 2019 · 9 min read

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.

Image for post
Image for post
Photo by José Alejandro Cuffia on Unsplash

What is the end goal of this article?

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?

What will be our steps?

Setting up the project

  • Install Storybook;
  • Adjust some configurations;

Create the first component

  • Publish to npm;

Create the second component

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

Improve the JTable component

  • Publish a new version to npm;

Moving from npm to GitHub package registry

  • Authenticate on GitHub package registry;
  • Publish to GitHub package registry;

So, let's start!

Setting up the 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

  • 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:

Image for post
Image for post
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:
Image for post
Image for post
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.

Image for post
Image for post
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 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;
Image for post
Image for post
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;
Image for post
Image for post
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

  • 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;
Image for post
Image for post
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;
Image for post
Image for post
Publishing a new version of JTable with Lerna

And we are done! A new version is available.

Image for post
Image for post
JTable and JTableRow components on npm registry

Moving from npm to GitHub Package Registry

  • 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.
Image for post
Image for post
https://github.com/jsilva-pt/medium-reusable-vue-components/packages

How can we use these components in other projects?

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

Final Notes:

  • 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

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.

Vue.js Developers

Helping web professionals up their skill and knowledge of…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store