Skip to content

Creating Quasar Framework project with Typescript support

Posted on: November 15, 2020

background

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:

code

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:

Terminal window
npm install -g @quasar/cli

or

Terminal window
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:

Terminal window
quasar create

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

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:

Terminal window
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:

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

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

Installation

All you need to do is install the package:

Terminal window
npm install vuex-module-decorators

or

Terminal window
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.

src/store/index.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:

src/store/LayoutStoreModule.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:

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

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:

Script part of MainLayout.vue
...
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.

template part of MainLayout.vue
...
<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.

src/router/index.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:

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

code

The code created by this tutorial is available as a project template on GitHub: xkonti/quasar-clean-typescript.

👍 12 likes on dev.to 💬 1 comments on dev.to