DEV Community

loading...
Cover image for Creating Quasar Framework project with Typescript support

Creating Quasar Framework project with Typescript support

xkonti profile image Xkonti Originally published at xkonti.tech ・8 min read

Quasar Framework 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? 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.

Project creation with Quasar CLI

To start your journey with Quasar Framework install the Quasar CLI globally by running the command:

npm install -g @quasar/cli
Enter fullscreen mode Exit fullscreen mode

or

yarn global add @quasar/cli
Enter fullscreen mode Exit fullscreen mode

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:

quasar create
Enter fullscreen mode Exit fullscreen mode

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.
    • Vue-i18n - For pre-configured 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, 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 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.
  • 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:

quasar dev
Enter fullscreen mode Exit fullscreen mode

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:

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

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.

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

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 which allows you to quickly create familiar app layouts and supports pages (the QPage component), 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. It will allow you to use the Vuex store in a much easier way:

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

// We'll be able to write this:
LayoutStore.setLeftDrawer(true)
Enter fullscreen mode Exit fullscreen mode

Installation

All you need to do is install the package:

npm install vuex-module-decorators
Enter fullscreen mode Exit fullscreen mode

or

yarn add vuex-module-decorators
Enter fullscreen mode Exit fullscreen mode

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.

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
})
Enter fullscreen mode Exit fullscreen mode

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
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)
Enter fullscreen mode Exit fullscreen mode

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

 LayoutStore.setLeftDrawer(true)
Enter fullscreen mode Exit fullscreen mode

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:

...

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

...

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

...
Enter fullscreen mode Exit fullscreen mode

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.

...

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')
    }
  })

...
Enter fullscreen mode Exit fullscreen mode

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:

// 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 { ... }
Enter fullscreen mode Exit fullscreen mode

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.

Discussion

pic
Editor guide