DEV Community

siegerts
siegerts

Posted on • Originally published at xiegerts.com on

Creating a Vue.js component library: Part II - Structure

Overview

In this post, we'll step through the initial project structure for your Vue components. In the last post, I covered the importance of packaging together a component library. Give that post a quick read, if you haven't yet, for a more context on goals of the project.

Vue project structure

Let's jump right in. We're going to use the Vue CLI 3. Bam!

Luckily, a lot of the once was configuration steps are now handled by the CLI, mostly regarding webpack. That's not to say that you won't eventually need to modify the webpack config with vue.config.js but you'll be surprised how far you can get without doing that. I try to avoid modifying the generic webpack settings, if possible 🙏. Remember, you'll want to to try to lower the barriers to entry of understanding for other developers in order for them to grasp your design choices. Not only for use but also for debugging (which is probably more likely, TBH).

With that in mind, create your Vue project scaffold using the CLI.

vue create vue-component-library
Enter fullscreen mode Exit fullscreen mode

After the project is created, and dependencies downloaded, you should see this in your terminal:


🎉  Successfully created project vue-component-library.
👉  Get started with the following commands:

 $ cd vue-component-library
 $ yarn serve

Enter fullscreen mode Exit fullscreen mode

When prompted during the project initialization, I choose the below options:

Vue CLI v3.0.0
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Linter
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) n
Enter fullscreen mode Exit fullscreen mode

Make sure to adjust these options in the future if your preferences change. The Vue CLI comes bundled with a nifty GUI that makes it easy to add and remove plugins.

vue-component-library is the name of the component library project (folder, etc.). This does not need to be the same as the programmatic representation of the library. We'll get into that in the upcoming plugin post of the series.

By default, the CLI will create the src/components directory. I consolidate this project directory and project by removing unused items such as App.vue, assets/favicon.ico, etc. The initial structure is typically used as an application baseline. For a typical web application, I leave the setup as-is. Instead, we'll use VuePress for the documentation site functionality.

Next, we'll:

  1. Remove the public directory
  2. Remove src/assets
  3. Remove components/HelloWorld.vue
  4. Remove src/App.vue

The directory changes are diffed in the layout below.

  .
- ├── public/
  ├── src/
- │   ├─ assets/
  │   └─ components/
- │      └─ HelloWorld.vue
- └─ App.vue
Enter fullscreen mode Exit fullscreen mode

You make be thinking...did we just delete the whole project? Nope! The CLI adds a tremendous amount of functionality to your project besides the file layout. Note, vue-cli-service and the corresponding devDependencies in the generated package.json.

Consider using the above-generated view structure as a custom Vue app or ejecting your VuePress theme if you'd like fewer guardrails.

{
  "name": "vue-component-library",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "vue": "^2.6.6"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.0.0",
    "@vue/cli-plugin-eslint": "^3.0.0",
    "@vue/cli-service": "^3.0.0",
    "@vue/eslint-config-prettier": "^4.0.1",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.8.0",
    "eslint-plugin-vue": "^5.0.0",
    "vue-template-compiler": "^2.5.21"
  }
}
Enter fullscreen mode Exit fullscreen mode

Package versions may not be exactly the same depending on creation date.

Component directory structure

For each component, I create three files in a named component directory within src/components.

A generic view of the directory structure is:

.
└─ src/
  └─ components/
    └─ <component-identifier>/
      ├─ <component-identifier>.vue
      ├─ <component-identifier>.spec.js
      └─ index.js
Enter fullscreen mode Exit fullscreen mode

Now, instead, for a hypothetical Button component:

.
└─ src/
  └─ components/
    └─ Button/
      ├─ Button.vue
      ├─ Button.spec.js
      └─ index.js
Enter fullscreen mode Exit fullscreen mode
  • <component>.vue

Contains the single file component (SFC).

  • index.js

Imports and exports the component from the self-contained component directory.

  • <component>.spec.js

Where the component tests will live. If the tests create a snapshot, then a __snapshots__ directory will be created within this directory.

So, for each of the files, let's create a placeholder.

<component>.vue

<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
  export default {
    name: 'name-of-your-component'
  }
</script>

<style></style>
Enter fullscreen mode Exit fullscreen mode

Components do not need to end up completely self-contained (template + script + style) but I like to start with this approach. I refactor, if needed, as the library grows in size or complexity. There are a lot of opinions about styles with JS or CSS in JS. I like to start with a regular SFC + scoped styles and iterate from there.

Notice that the component has a name. This is very important and will impact our registering the library as a plugin in a few steps. Components are registered and referred to by the name attribute. Try to use an identifier that won't collide with other project dependencies or tags.

index.js

import Button from './Button.vue'

export default Button
Enter fullscreen mode Exit fullscreen mode

<component>.spec.js

We'll leave this file empty for now. Ultimately, this will contain the component tests.

Component export

Within in the src directory, create another index.js file to export the component(s). This file will sit alongside the top level /components directory as below.

 .
 └─ src/
   ├─ components/
   │  └─ ...
+  └─ index.js
Enter fullscreen mode Exit fullscreen mode

In this file, we'll import, and export, the components from this file.

// index.js
export { default as Button } from './Button'
Enter fullscreen mode Exit fullscreen mode

This pattern may seem a bit repetitive but it provides flexibility in the library. The intermediate index.js file consolidates the components to be imported as a one-liner in the entry point file, main.js.

Alt Text

More than one component can live in the same <component> directory. For example, it may make sense to group components in a logical fashion based on usage pattern (i.e. <List> and <ListItem>). If so, adjust the above files to reflect:

// src/components
import List from './List.vue'
import ListItem from './ListItem.vue'

export default { List, ListItem }
Enter fullscreen mode Exit fullscreen mode

And one level higher:

// src/index.js
export { List, ListItem } from './ListComponents'
Enter fullscreen mode Exit fullscreen mode

The foundation is now set to add on the documentation part of the library 📖. But first, we'll consolidate the components into a plugin! We'll get to that next.

Top comments (6)

Collapse
 
dentous profile image
dan entous • Edited

shouldn’t the import/export index.js file be placed in the /src/components/ directory?

Collapse
 
gerardfigols profile image
Gerard Fígols

Yes, I agree.

Collapse
 
adriatic profile image
Nikolaj Ivancic

Link 'ejecting your VuePress theme' is broken. Hope you will fix it as this set of tutorials is quite important to many

Collapse
 
siegerts profile image
siegerts

Thanks for spotting! The link is fixed. Glad that you find the series helpful :)

Collapse
 
adriatic profile image
Nikolaj Ivancic

Thanks for confirming my issues, dev.to/siegerts (this page does not seem to support GitHub's login id that starts with '@' character). Seeing that you care about your 6 part series I hope you would allow me to send you a few more issues I encountered, in order to make everything work with today's tools/libraries.

You might about why am I doing that, so let me explain: I find Vuepress quite an attractive tool for creating technical documentation - probably better than all alternatives due to its flexibility to augment it myself (locally). I am starting an open-source project ('github.com/iam-concepts') where I want to demystify the use of Identification and Authentication Services packaged as PaaS. Besides writing a lot of samples (all of them being a set of [front end, back end]), I want to write a lot of documents, thus my interest in Vuepress.

Except for your series on the component library, all other Vuepress tutorials are behind the current state of Vuepress and Vue, and I could not find any of the authors that would be willing to refresh them. You might be different in that respect, so let me propose:

  1. I report to you any other "gotchas" I find in running this complete series and you tell me (if necessary) how to address those. This step will result in the code in Github that you may take over and add to your GitHub account for your readers (this assumes that you do not have Github resident code - if you do, I can send you as many PRs as it will take.

  2. Having 1. done, I am interested in creating the tutorial and code describing how to make a MKDOCS equivalent VuePress based tool: (here follows the screenshot that I uploaded, but cannot see in the preview)
    Such a tutorial would be sufficiently different from yours as it does not deal with self-documenting components).

Are you interested in participate in any view other than helping me to make your series current?

Regards

Nik

Collapse
 
gerardfigols profile image
Gerard Fígols • Edited

Thanks @siegerts for this great and clear article!

Let me however add a small improvement:
In order to export multiple components from a same index.js file, the "default" reserved word shouldn't be used. Otherwise an error will be thrown:

export 'List' was not found in './ListComponents'
export 'ListItem' was not found in './ListComponents'

Overview of the changes in components/ListComponents/index.js:

"- export default { List, ListItem }"
"+ export { List, ListItem }"

Both components can be then imported in the /components/index.js file this way:

export { List, ListItem } from './ListComponents'

Source:
stackoverflow.com/questions/499825...