Skip to content

πŸ‘·β€πŸ‘·β€Helps you implement a folder-by-feature module structure in large Vue projects

Notifications You must be signed in to change notification settings

laander/vue-modular

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

21 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Vue Modular

Helps you implement a folder-by-feature module structure in large Vue projects πŸ‘·β€πŸ‘·β€

CircleCI Codecov npm

Building large Vue projects

When Vue projects grow, the classic folder-by-type folder structure can become unmanageable because dependencies across files are hard to refactor and mentally compute.

Instead, you should consider structuring your code into "features" that are encapsulated with their own components, vuex module, routes and more. Think of it as a structural design pattern that encourages encapsulation of related code.

The main purpose of this repo is simply to prescribe a scalable project structure. To ease the setup, this simple Vue plugin helps you do that easily by extracting vuex stores and router definitions from each module and registering them globally.

Plugin installation

Install the NPM module

yarn add vue-modular

In your main.js (or equivalent), add

import VueModular from 'vue-modular'
import foo from './modules/foo'
import bar from './modules/bar'

// simplified for example purpose
const router = new Router()
const store = new Vuex.Store()

// install the plugin with modules and router + store references
Vue.use(VueModular, {
  modules: {
    foo,
    bar
  },
  router,
  store
})

The plugin currently achieves three things:

  1. Vuex stores defined in each module are registered on the global vuex instance
  2. Vue router definitions in each module are merged and registered on the global router instance
  3. A vue instance helper vm.$modules is injected into all components which makes it easy to access custom values that each module can export

Recommended setup

Please see the example folder for a reference setup

Choose a folder structure that will with your local modules. Each module can be simple (foo below) or complex (bar below), depending on your need.

modules/
β”‚
β”œβ”€β”€ foo/
β”‚   β”œβ”€β”€ index.js
β”‚   β”œβ”€β”€ router.js
β”‚   β”œβ”€β”€ store.js
β”‚   └── ComponentA.vue
β”‚
└── bar/
    β”œβ”€β”€ index.js
    β”œβ”€β”€ router.js
    β”œβ”€β”€ store/
    β”‚   β”œβ”€β”€ mutations.js
    β”‚   β”œβ”€β”€ actions.js
    β”‚   └── getters.js
    β”‚
    β”œβ”€β”€ views/
    β”‚   β”œβ”€β”€ PageA.vue
    β”‚   └── PageB.vue
    β”‚
    β”œβ”€β”€ services/
    β”‚   β”œβ”€β”€ datasource.js
    β”‚   └── tracking.js
    β”‚
    └── tests/
        β”œβ”€β”€ services.spec.js
        └── views.spec.js

There's technically no restriction on how you structure your directory tree or what you name your files, as long as each module exports an object like this

// modules/foo/index.js

import router from './router.js'
import store from './store.js'

export default {
  router, // module vue router (if any)
  store, // module vuex store (if any)
  custom: {
    // optional extra info you to share
    foo: 'bar'
  }
}

The router and store objects are exactly what you'd expect and know from Vuex modules and Vue Router definitions

// modules/foo/router.js

import ComponentA from './ComponentA.vue'

export default {
  routes: [
    {
      path: '/foo',
      name: 'foo',
      component: ComponentA
    }
  ],
  beforeEach: (to, from, next) => {
    next()
  }
}
// modules/foo/store.js

export default {
  namespaced: true,
  state: {
    foo: []
  },
  mutations: {
    setFoo(state, foo) {
      state.foo = foo
    }
  },
  getters: {
    getFoo(state) {
      return state.foo
    }
  }
}

When to use it

Don't over-engineer prematurely

For small and medium-sized projects, the folder-by-type approach is usually simpler to navigate because it's helpful to have all your routes and stores in one place.

If you're a one or two people building a small Vue app, that's usually fine.

LIFT your code base

When projects grow to hundreds of components with large developer teams working on the same code base, things start to change.

What you're looking to achieve can be abbreviated LIFT: Structure the app such that you can Locate code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to be DRY

Do remember though that not all your code can or should be self-contained: base UI components, API client, general purpose utilities etc. should probably not be modules.

But an authentication module with it's own routes (ex. /login), store (ex. currentUser), tests (ex. login.spec.js) and view components (ex. Login.vue) is ideal for encapsulation.

Tips & tricks

Exposing stuff from modules

As a thumb rule, the more isolated you can keep your modules from each other and the rest of your codebase, the easier it will be to maintain.

However, sometimes you do want to export a component, service or utility function for other code to use. In these cases, I recommend that you do it explicitly and only from your module's index.js

// modules/foo/index.js

import ComponentA from './ComponentA.vue'
import { utilityFunction } from './utilities.js'

export { ComponentA, utilityFunction }
// somewhere-else.js

import { ComponentA, utilityFunction } from './modules/foo'

This achieves two things:

  1. The import statement reads well and is self explanatory i.e. import x from module foo
  2. It's a lot easier to refactor and modify your module afterwards, because it's explicit what is exposed outside of your module. You can even change file names and still keep the named exports outwards in order to not break other stuff while refactoring.

Lazy load modules async

When your code base grows, bundling everything together in one giant chunk results in a big up-front download for code that the client might never use.

Instead, consider using code splitting by loading your modules only when needed. You can do this inside each module with lazy loading routes, or you can load in entire module definitions with the registerModules function and webpack's dynamic imports

import { registerModules } from 'vue-modular'

const { default: foo } = await import('./modules/foo')
registerModules({ foo })

Contribute

For the repo and install dependencies

# Serve example app
yarn serve

# Production build
yarn build

# Run unit tests
yarn test:unit

# Lint files
yarn lint

PRs and issues are welcome!

License

MIT

Use it, fork it, change it, do what you want πŸ––

About

πŸ‘·β€πŸ‘·β€Helps you implement a folder-by-feature module structure in large Vue projects

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published