Creating Quasar Framework project with Typescript support
Tags: quasar, typescript, vuex, frontend
Published: 2020-11-16 01:13:42

[Quasar Framework](https://quasar.dev/) advertises itself with: *"Build high-performance VueJS user interfaces in record time"*. It is packed with features and developer-friendliness unlike any other Vue framework. If you've never heard of Quasar Framework, or unsure if you should give it a try, read the *[Why quasar?](https://quasar.dev/introduction-to-quasar)* documentation page.

This guide will show you how to quickly create a Quasar Framework project with:
- TypeScript - to provide type safety and additional language features
- Class-based Vue components - to write Vue components in a more organized and TypeScript-friendly manner
- Class-based Vuex modules - to work with Vuex faster and safer - no more magic strings!

> The code created by this tutorial is available as a project template on GitHub: [xkonti/quasar-clean-typescript](https://github.com/Xkonti/quasar-clean-typescript).

## Project creation with Quasar CLI

To start your journey with Quasar Framework install the [Quasar CLI](https://quasar.dev/start/quasar-cli) globally by running the command:

```bash
npm install -g @quasar/cli
```

or

```bash
yarn global add @quasar/cli
```

Once the installation is complete you'll need to create a directory for this project and then navigate to it. It's time to use *the power of Quasar CLI* to save ourselves hours (or days) of tedious work:

```bash
quasar create
```

This command will start a project wizard requesting some of the following information:

- **Generate project in current directory?** - Definitely yes. You just navigated to this directory.
- **Project name** - This is the internal name of the project shown only to the developer.
- **Project product name** - This is the public name for the project, by default displayed as a title of the website.
- **Project description** - A description of the project - it will be placed in the `package.json`.
- **Author** - The name of the author.
- **Pick your favourite CSS preprocessor** - Choose whatever you prefer. For this template I'm selecting: `Sass with SCSS syntax`
- **Pick a Quasar components & directories import strategy** - The days of manually specifying used Quasar components are long gone and the *Auto-import* option is a blessing.
- **Check the features needed for your project** - This is an important aspect: deciding the features you want the Quasar CLI to set up for you automatically:
   - `ESLint` - For pre-configured linting.
   - `TypeScript` - For pre-configured TypeScript support. Required for this tutorial.
   - `Vuex` - For pre-configured Vuex integration. Required for this tutorial.
   - `Axios` - For pre-configured [Axios](https://github.com/axios/axios).
   - `Vue-i18n` - For pre-configured [Vue-i18n](https://kazupon.github.io/vue-i18n/).
- **Pick a component style** - Quasar lets you select a Vue component writing style. It'll preconfigure required plugins, and all example components will be appropriately adjusted. In this tutorial I'm sticking with `Class-based` style as it is flexible, familiar and works well with Vue 2 and TypeScript. The `Composition API` in `Vue 2` has a great number of [limitations](https://github.com/vuejs/composition-api#limitations), so I'm not recommending it unless you are quite familiar with it. You can always go with the classic `Options API`, but then you are losing some of the 🍭 that TypeScript brings.
- **Pick an ESLint preset** - If you've selected the `ESLint` to be preconfigured, Quasar CLI will ask you what preset to configure. For smaller projects I recommend the [`Standard` preset](https://standardjs.com/rules.html) as it's a fairly popular one, and it isn't strict to the point of annoyance. However, if you prefer something more strict because you work in a larger team, or you just prefer things this way, I recommend the [`Airbnb` preset](https://github.com/airbnb/javascript).
- **Continue to install project dependencies after the project has been created?** - Yes. Allow the Quasar CLI to install everything. The `Yarn` package manager is recommended by the Quasar team, but the choice is yours.

Give the Quasar CLI some time to complete the installation, and you should finish with a project that's ready to run. Go to your project folder and run:

```bash
quasar dev
```

This command will start the development server for your newly created project. It supports automatic hot-reloading. It will even automatically reload when you change configuration files or install new packages unlike some other frameworks.

Before continuing with the project setup, I recommend adding basic scripts in the `package.json`. Just for convenience:

```json
"scripts": {
  "dev": "quasar dev",
  "build": "quasar build",
  "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore ./",
  "test": "echo \"No test specified\" && exit 0"
},
```

## Quasar project overview

A Quasar Framework project has a structure similar to most Vue.js projects. In the root directory there are `package.json`, `README.md` and some other config files. Among which is the `quasar.conf.js` - the main configuration file for Quasar. You will need to touch this file a couple of times throughout your project's life. [Learn more](https://quasar.dev/quasar-cli/quasar-conf-js).

The `src` directory has a typical structure with the `App.vue` and `index.template.html` - the Vue app component and HTML template for the whole app.

The `boot` directory contains code related to libraries that run before creation of the Vue instance. This is where the `Vue.use(...)` *parts* happen. [Learn more](https://quasar.dev/quasar-cli/boot-files)

The `components` directory is there to contain your Vue components, but it's completely optional. The `layouts` directory contains application layout Vue components. Quasar has its own [`QLayout` component](https://quasar.dev/layout/layout) which allows you to quickly create familiar app layouts and supports pages (the [`QPage` component](https://quasar.dev/layout/page)), which reside in the `pages` directory. Project generated by the Quasar CLI has a simple example of the `QLayout` and the `QPage` components relation as well as their configuration in the Vue router.

There's also the `store` directory which contains a Vuex store with an example of a Vuex module. We'll continue with this topic next.


## Configuring class-based Vuex modules

To be able to create Vuex store modules in a class-based manner we'll use the [vuex-module-decorators package](https://championswimmer.in/vuex-module-decorators/). It will allow you to use the Vuex store in a much easier way:

```ts
// Instead of using Vuex the old way:
this.$store.commit('layout/setLeftDrawer', true)

// We'll be able to write this:
LayoutStore.setLeftDrawer(true)
```

### Installation

All you need to do is install the package:

```bash
npm install vuex-module-decorators
```

or

```bash
yarn add vuex-module-decorators
```

### Preparing the store

First of all you can remove the `src/store/module-example` folder with all files in it. This example shows a classic way of using Vuex with TypeScript.

Since you probably won't be using the Vuex store in the standard way, you'll have to clean up the `src/store/index.ts` file which is the main store instance.

```ts
import { store } from 'quasar/wrappers'
import Vuex, { Store } from 'vuex'

export let storeInstance: Store<unknown>

export default store(function ({ Vue }) {
  Vue.use(Vuex)
  const store = new Store<unknown>({
    modules: {},
    strict: !!process.env.DEBUGGING
  })
  storeInstance = store
  return store
})
```

In addition to eliminating of the state interface, you'll also want to export the `storeInstance` so you can use it when creating dynamic Vuex modules. Remember that the `storeInstance` won't be available until Quasar creates it. Quasar Framework instantiates the store on its own using the `store` wrapper function imported from `quasar/wrappers`.

After having removed the state interface, you'll then have to fix the Vue router instantiation code located in `src/router/index.ts`. Remove the `import { StateInterface } from '../store'` import line and change the type of the store instance to match the `src/store/index.ts`: `export default route<Store<unknown>>(function ({ Vue }) {`.

## Creating a dynamic Vuex module 🔥

Creating a dynamic class-based Vuex module is extremely easy. The following example illustrates the `LayoutStoreModule` that handles the state of the left drawer in the `src/layouts/MainLayout.vue` component. You can place the store module file wherever we'd like, but to keep things clean it's recommended to place it the `store` folder. The module contains:
  - A state indicating whether the left drawer is open or not
  - A mutation for setting the state
  - An action for setting the state using the mutation

```ts
import { storeInstance } from './index'
import { Action, getModule, Module, Mutation, VuexModule } from 'vuex-module-decorators'

@Module({
  dynamic: true,
  store: storeInstance,
  namespaced: true,
  name: 'LayoutStoreModule'
})
class LayoutStoreModule extends VuexModule {
  isLeftDrawerOpen = false

  @Mutation
  SET_LEFT_DRAWER (setOpen: boolean) {
    this.isLeftDrawerOpen = setOpen
  }

  @Action
  setLeftDrawer (setOpen: boolean) {
    this.SET_LEFT_DRAWER(setOpen)
  }
}

export const LayoutStore = getModule(LayoutStoreModule)
```

This Vuex store module takes the form of a class `LayoutStoreModule` that extends the `VuexModule` class. The state takes the form of the `isLeftDrawerOpen` boolean field with a default value of `false`. The `SET_LEFT_DRAWER` function is marked as a store mutation using the `@Mutation` decorator. The action for setting the state takes a form the `setLeftDrawer` function decorated with the `@Action` decorator.

The whole module class is decorated with the `@Module` decorator. The `@Module` decorator accepts a configuration object. In the case of this module:
  - `dynamic: true` - this is a dynamic module. Dynamic modules are registered at runtime upon an `import` statement.
  - `store: storeInstance` - since this module will register itself, it then needs a reference to the store instance.
  - `namespaced: true` - marks this module as a [namespaced module](https://vuex.vuejs.org/guide/modules.html#namespacing).
  - `name: 'LayoutStoreModule'` - specifies module's name.
 
At the end of the file, the module's instance is exported, so it can then be used directly in almost any place in the project:

```ts
 LayoutStore.setLeftDrawer(true)
```

## Using the dynamic vuex module

Now you can use your newly-created store module. In the `MainLayout.vue` component, you can replace the internal state with the one stored in the `LayoutStore` module:

```ts
...

import { Vue, Component } from 'vue-property-decorator'
import { LayoutStore } from 'src/store/LayoutStoreModule'

@Component({
  components: { EssentialLink }
})
export default class MainLayout extends Vue {
  essentialLinks = linksData

  get isLeftDrawerOpen () {
    return LayoutStore.isLeftDrawerOpen
  }

  set isLeftDrawerOpen (value) {
    LayoutStore.setLeftDrawer(value)
  }
}
```

First import the newly-created `LayoutStore` module. Then replace the previous `leftDrawerOpen` field with the getter and setter `isLeftDrawerOpen`. The getter simply returns the value from the store, while the setter dispatches the `setLeftDrawer` action. The days of `dispatch` and `commit` alongside *magic strings* are gone.

```ts
...

<q-btn
  flat
  dense
  round
  icon="menu"
  aria-label="Menu"
  @click="isLeftDrawerOpen = !isLeftDrawerOpen"
/>

...

<q-drawer
  v-model="isLeftDrawerOpen"
  show-if-above
  bordered
  content-class="bg-grey-1"
>
  ...
</q-drawer>

...
```

Finally, in the template section of the component, use the `isLeftDrawerOpen` as a `v-model` for the `q-drawer` and in the `@click` event in the `q-btn`. That's it. Way simpler than classic Vuex and with a much better code autocompletion support. 

## Things to watch out for

While these dynamic class-based modules bring an amazing coding experience, there are some limitations that you should be aware of. First of all, the module registers itself at its first import attempt. If the Vuex store hasn't instantiated yet, the module registration will fail.

```ts
...

import routes from './routes'
// Imports the module here - no store yet!
import { AuthStore } from 'src/store/AuthStoreModule';

export default route<Store<unknown>>(function ({ Vue }) {
  Vue.use(VueRouter)

  const Router = new VueRouter({ ... })

  router.beforeEach(async (to, from, next) => {
    // The AuthStore module wasn't initialized properly.
    // The following statement fails.
    if (AuthStore.isLoggedIn) {
      next()
    } else {
      next('/login')
    }
  })

...
```

Another thing worth noting, is that you can pass only one argument to the store module's actions and mutations - *payload* - just like classic Vuex modules:

```ts
// Wrong! Can't pass 2 arguments
@Action
async verifyUser (login: string, passwordHash: string): boolean { ... }

// Pass 1 payload object instead
@Action
async verifyUser (payload: {login: string, passwordHash: string}): boolean { ... }
```

## Conclusion

Not only is it easy to configure the Quasar project to work well with TypeScript, but it's also fun to use class-based Vue components and class-based Vuex modules. I hope this will help those just starting out with Quasar and Typescript.

> The code created by this tutorial is available as a project template on GitHub: [xkonti/quasar-clean-typescript](https://github.com/Xkonti/quasar-clean-typescript).