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
or
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:
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:
-
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. TheComposition API
inVue 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 classicOptions 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 theStandard
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 theAirbnb
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
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"
},
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:
npm install vuex-module-decorators
or
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.
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
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 animport
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)
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)
}
}
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>
...
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')
}
})
...
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.
The code created by this tutorial is available as a project template on GitHub: xkonti/quasar-clean-typescript.
Top comments (1)
Thanks for this!