How to build a large Vue application

By Michael Lin

The MVVM of the Observable model gives Vue a natural advantage in small and medium-sized Web applications, but with the growing popularity of Vue, Vue’s use in large projects is slightly awkward.

Obviously, in high complexity projects, type checking has become a required feature, and Vue2’s type checking support in TypeScript is not good enough, and importantly the modular design of Vuex’s state logic is lacking.

So here are the following proposed solutions:

  • Modularization of business logic — usm-vuex will solve the important problem of modularization
  • TypeScript — vue-cli3
  • TSX — Better template type checking
  • Dependency injection — Best DI library: inversify
  • Subpackage — Build Monorepo with lerna

After lerna initialization, the domain-driven design is carried out, and large domain modules are obtained. If necessary, it will be possible to subpackage while enabling dynamic import lazy loading or module loaders such as RequireJS to improve run-time performance and building performance.

In the initialization of the core application sub-package using Vue-cli3 structure, select TypeScript as the primary language, it will automatically introduce Webpack ts-loder.

This is the core directory structure:

|-- App.vue
|-- main.ts
+-- modules/
|-- Todos/
|-- Navigation/
|-- Portal/
|-- Counter/
...
+-- lib/
|-- loader.ts
|-- moduleConnect.ts
...
+-- components/
...

main.ts is the default entry.

// ...
// omit some modules
import { load } from './lib/loader';
const {
portal,
app,
} = load({
bootstrap: 'Portal',
modules: {
Counter,
Todos,
Portal,
Navigation
},
main: App,
components: {
home: {
screen: TodosView,
path: '/',
module: 'todos',
},
counter: {
screen: CounterView,
path: '/counter',
module: 'counter',
},
}
});
Vue.prototype.portal = portal;
new Vue(app).$mount("#app");

App.vue is a main view file.

<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/counter">Counter</router-link>
</div>
<router-view />
</div>
</template>

modules contains all the business logic, including the view layer state and navigation modules and so on. It will be run by Vuex.

For example, the following is Counter module:

import { injectable } from "inversify";
import Module, { state, action } from "../../lib/baseModule";
@injectable()
export default class Counter extends Module {
@state count: number = 0;
  @action
calculate(num: number, state?: any) {
state.count += num;
}
  getViewProps() {
return {
count: this.count,
calculate: (num: number) => this.calculate(num)
}
}
}

lib/loader.ts is the app configuration loader。

import { Container } from 'inversify';
export function load(params: any = {}) {
const { bootstrap, modules, ...option } = params;
const container = new Container({ skipBaseClassChecks: true });
Object.keys(modules).forEach(key => {
container.bind(key).to(modules[key]);
});
container.bind("AppOptions").toConstantValue(option);
const portal: any = container.get(bootstrap);
portal.bootstrap();
const app = portal.createApp();
return {
portal,
app,
};
}

lib/moduleConnect.ts is ViewModule's view connector. This is a connector for a high-order component.

import { Component, Vue } from "vue-property-decorator";
export default (ViewContainer: any, module: string) => {
@Component({
components: {
ViewContainer
}
})
class Container extends Vue {
get module() {
return this.portal[module];
}
    render(createElement: any) {
const props = this.module.getViewProps();
return createElement(ViewContainer, {
props
})
}
}
return Container;
}

components/Counter/index.tsx is a Counter view component.

import { Component, Vue, Prop } from "vue-property-decorator";
import './style.scss';
type Calculate = (sum: number) => void;
@Component
export default class CounterView extends Vue {
@Prop() count!: number;
@Prop(Function) calculate!: Calculate;
  render(){
return (
<div class="body">
<button onClick={()=> this.calculate(1)}>+</button>
<span>{this.count}</span>
<button onClick={()=> this.calculate(-1)}>-</button>
</div>
)
}
}

In conjunction with TSX’s view component module, the overall design and based on this architecture, it will be easier to check type with TypeScript.

The core design part of the Vue architecture should be usm-vuex. It makes Vuex of business modularization simple and straightforward, and it can make the current architecture design high cohesion and low coupling with the ViewModule of the view layer.

It makes greatly improving the reusability and maintainability in the architecture, and the dependencies between modules are made clear and understandable with dependency injection. Of course, there are a lot of other details to be perfected for large applications, and this article is not covered here.

usm-vuex Repo: https://github.com/unadlib/usm

This Demo Repo: https://github.com/unadlib/usm-vuex-demo