Skip to page content
Quasar CLI with Vite - @quasar/app-vite v3

Handling Vite

The build system uses Vite to create the UI of your website/app (/src folder). Don’t worry if you aren’t acquainted with Vite. Out of the box, you won’t need to configure it because it already has everything set up.

Updating Vite config

You may have noticed that the vite.config.js / vite.config.ts file does not exist in your Quasar CLI with Vite project. This is because Quasar CLI generates the Vite configuration for you so that you don’t have to worry about it.

In case you need to tweak it, you can do so through quasar.config file > build > extendViteConf like so:

/quasar.config file

build: {
  extendViteConf (viteConf, { isServer, isClient }) {
    // We return an Object which will get deeply merged into
    // the config, instead of directly tampering with viteConf
    return {
      build: {
        chunkSizeWarningLimit: 750
      }
    }
    // equivalent of following vite.config.js/vite.config.ts:
    // export default defineConfig({
    //   build: {
    //     chunkSizeWarningLimit: 750
    //   }
    // })
  }
}

Notice that you don’t need to return anything. The parameter of extendViteConf(viteConf) is the Vite configuration Object generated by Quasar for you. You can add/remove/replace almost anything in it, assuming you really know what you are doing. Do not tamper with the input and output files though or any other option that is already configured by quasar.config file > build.

If you want to add some Vite plugins, see the Adding Vite plugins section below.

Npm packages that import from Quasar

An npm package (a component library, a helper package, an App Extension) that does import { Notify } from “quasar” in its own code gets pre-bundled by Vite’s dep optimizer, which links it against a second copy of Quasar. Quasar Plugins installed by your app then appear uninstalled to that package, with errors like Notify.create is not a function.

The fix is excluding such packages from pre-bundling, so their Quasar imports resolve to the same modules as your app code:

/quasar.config file

build: {
  extendViteConf () {
    // gets deeply merged into the generated Vite config
    return {
      optimizeDeps: {
        exclude: ['my-quasar-helper-package']
      }
    }
  }
}

App Extensions can (and should) configure this themselves instead of relying on the host app. See Injecting Quasar Plugin.

WARNING

The dep optimizer only runs for the dev server. Production builds instead rely on Quasar’s import mapping, which processes the file extensions listed in quasar.config file > framework > autoImportScriptExtensions (default: ['js', 'jsx', 'ts', 'tsx']). If such a package ships its ESM build as .mjs files, add 'mjs' to that list, otherwise the production bundle will contain a second copy of Quasar with the same symptoms as above.

Inspecting Vite Config

Quasar CLI offers a useful command for this:

$ quasar inspect -h

  Description
    Inspect Quasar generated Vite config

  Usage
    $ quasar inspect
    $ quasar inspect -c build
    $ quasar inspect -m electron -p 'build.outDir'

  Options
    --cmd, -c        Quasar command [dev|build] (default: dev)
    --mode, -m       App mode [spa|ssr|ssg|pwa|bex|cordova|capacitor|electron] (default: spa)
    --depth, -d      Number of levels deep (default: 2)
    --path, -p       Path of config in dot notation
                        Examples:
                          -p build.outDir
                          -p server.port
                          -p plugins
    --thread, -t     Display only one specific app mode config thread
    --no-color       Disable colored output
    --help, -h       Displays this message

Adding Vite plugins

Install the Vite plugin with your project’s package manager, then edit the /quasar.config file:

/quasar.config file

build: {
  vitePlugins: [
    // both are perfectly equivalent:
    ['<plugin-name>', {/* plugin options */}],
    ['<plugin-name>', {/* plugin options */}, { server: true, client: true }]
  ]
}

You can disable a plugin on the client-side or the server-side, which is especially useful when developing a SSR app:

/quasar.config file

build: {
  vitePlugins: [
    // disable on the server-side:
    ['<plugin-name>', {/* plugin options */}, { server: false }],

    // disable on the client-side:
    ['<plugin-name>', {/* plugin options */}, { client: false }]
  ]
}

There are multiple syntaxes supported:

/quasar.config file

vitePlugins: [
  ['<plugin1-name>', {/* plugin1 options */}, { server: true, client: true }],
  ['<plugin2-name>', {/* plugin2 options */}, { server: true, client: true }]
  // ...
]

// or:
import plugin1 from 'plugin1'
import plugin2 from 'plugin2'

vitePlugins: [
  [plugin1, {/* plugin1 options */}, { server: true, client: true }],
  [plugin2, {/* plugin2 options */}, { server: true, client: true }]
  // ...
]

// finally, you can specify using the form below,
// but this one has a drawback in that Quasar CLI cannot pick up
// when you change the options param so you'll have to manually
// restart the dev server
import plugin1 from 'plugin1'
import plugin2 from 'plugin2'

vitePlugins: [
  plugin1({/* plugin1 options */}),
  plugin2({/* plugin2 options */})
  // ...
]

And, should you want, you can also add Vite plugins through extendViteConf() in the /quasar.config file. This is especially useful for (but not limited to) SSR/SSG mode where you’d want a Vite plugin to be applied only on the server-side or the client-side:

import plugin1 from 'plugin1'
import plugin2 from 'plugin2'

build: {
  extendViteConf (viteConf, { isClient, isServer }) {
    viteConf.plugins.push(
      plugin1({ /* plugin1 options */ }),
      plugin2({ /* plugin2 options */ })
      // ...
    )
  }
}

Moreover, don’t forget that your /quasar.config file exports a function that receives ctx as parameter. You can use it throughout the whole config file to apply settings only to certain Quasar modes or only to dev or prod:

export default defineConfig(ctx => {
  return {
    build: {
      extendViteConf(viteConf, { isClient, isServer }) {
        if (ctx.mode.pwa) {
          viteConf.plugins.push(/* ... */)
        }

        if (ctx.dev) {
          viteConf.plugins.push(/* ... */)
        }
      }
    }
  }
})

Example: rollup-plugin-copy

It is likely that you will need to copy static or external files to your Quasar project during the build to production process, rollup-plugin-copy allows you to copy files and folders when building your app.

/quasar.config file

// ...
build: {
  // ...
  vitePlugins: [
    [
      'rollup-plugin-copy',
      {
        targets: [
          {
            // Syntax code, check doc in https://www.npmjs.com/package/rollup-plugin-copy
            src: '[ORIGIN_PATH]',
            dest: '[DEST_PATH]'
          },
          {
            // Copying firebase-messaging-sw.js to SPA/PWA/SSR/SSG dist build folder
            src: 'config/firebase/firebase-messaging-sw.js',
            dest: 'dest/spa' // example when building SPA
          }
        ]
      }
    ]
    // other vite/rollup plugins
  ]
}
// ...

Vite Vue Plugin options

If you need to tweak the Vite Vue Plugin(@vitejs/plugin-vue) options, you can do so through quasar.config file > build > viteVuePluginOptions like so:

/quasar.config file

build: {
  viteVuePluginOptions: {
    script: {
      // example: enable experimental props destructuring
      propsDestructure: true
    },

    template: {
      compilerOptions: {
        // example: enable custom/web element tag detection
        isCustomElement: (tag) => tag.startsWith('my-')
      }
    }
  }
}

JSX/TSX
Quasar UI v2.26+
@quasar/app-vite v3.8+

Enabling JSX/TSX

If you want to write components with JSX/TSX instead of (or alongside) Vue templates, enable it through quasar.config file > build > vueJsx:

/quasar.config file

build: {
  /**
   * Should you want to write your components with JSX/TSX (.jsx/.tsx files
   * or <script lang="jsx|tsx"> in .vue files).
   *
   * Vite compiles them itself, so all this does is pointing it at Vue's JSX
   * runtime (instead of the React one that it assumes by default) and adding
   * the matching "jsx"/"jsxImportSource" to the generated
   * .quasar/tsconfig.json (TypeScript projects).
   *
   * Set to `true`, or to an options object to override the defaults below,
   * or to "preserve" when a Vite plugin (like @vitejs/plugin-vue-jsx, which
   * adds the Vue specific JSX sugar: v-model, v-show, v-slots) should
   * transform the JSX instead.
   *
   * Default options supplied to Vite (Oxc) when `true`:
   * @example
   * {
   *   runtime: 'automatic',
   *   importSource: 'vue'
   * }
   *
   * @default false
   */
  vueJsx?: boolean | NonNullable<OxcOptions["jsx"]>;
}

This is all that is needed. Vite compiles the JSX/TSX itself, so no additional package is required. What the option does is telling it to use Vue’s JSX runtime (instead of the React one that it assumes by default) and, on TypeScript projects, adding the matching jsx / jsxImportSource to the generated .quasar/tsconfig.json.

You can now use .jsx / .tsx files:


import { QBadge } from 'quasar'

export default function MyBadge({ text }) {
  return <QBadge class="q-ma-sm" color="accent" label={text} />
}

…and <script> blocks in .vue files, by declaring their language:


<script setup lang="jsx">
  import { QBadge } from 'quasar'

  const MyBadge = () => <QBadge color="accent" label="Hello" />
</script>

Quasar components are fully typed in JSX/TSX: their props, their events (onClick, onUpdate:modelValue, …) and the props that Vue accepts on any component (class, style, key, ref).

Vue sugar

Check the Vite’s specific vue jsx plugin if you also want the Vue specific JSX sugar.

Configuring the JSX transformation

Instead of true, you can supply an options object, which is handed over to Vite (Oxc). It overrides the defaults, which are:

/quasar.config file

build: {
  vueJsx: {
    runtime: 'automatic',
    importSource: 'vue'
  }
}

Using @vitejs/plugin-vue-jsx

Vite compiles plain JSX, which does not include the Vue specific JSX sugar (v-model, v-show, v-slots). Should you need it, install @vitejs/plugin-vue-jsx in the root of your app, then hand the transformation over to it:

/quasar.config file

build: {
  // Vite leaves the JSX alone, for the plugin to transform
  vueJsx: 'preserve',

  vitePlugins: [
    [ '@vitejs/plugin-vue-jsx', { /* plugin options */ } ]
  ]
}

Folder aliases

Quasar comes with the @ folder alias pre-configured. It points to /src.

However, should you wish to add more aliases, for example an utils one to point to /src/utils, which may be used as import { formatTime } from 'utils/time.js', then there are three ways:

  1. (Recommended) Just use @/utils/. Plain and simple, no config needed. Makes it easier for contributors to your code since the @ alias is already popular.

  2. Through /quasar.config file > build > “alias” property. This is the simplest way to add a folder alias. Use an absolute path to your alias. Example:

/quasar.config file

export default defineConfig(ctx => {
  return {
    build: {
      alias: {
        // will point to /src/utils
        utils: ctx.appPaths.resolve.src('utils')
      }
    }
  }
})
  1. By extending the Vite config directly. Do not assign to viteConf.resolve.alias directly to preserve the built-in aliases, use Object.assign instead or return an Object with your extra aliases. Always use absolute paths.
/quasar.config file

export default defineConfig(ctx => {
  return {
    build: {
      extendViteConf(viteConf, { isServer, isClient }) {
        viteConf.resolve.alias.utils = ctx.appPaths.resolve.src('utils')
      }
    }
  }
})
Using with TypeScript

If you are using TypeScript, you DON’T have to also add the aliases to your tsconfig.json file (nor use packages like vite-tsconfig-paths). These are taken care of by the Quasar CLI by default.

PostCSS

Styles in *.vue files (and all other style files) are piped through PostCSS by default, so you don’t need to use a specific loader for it.

By default, PostCSS is configured to use Autoprefixer. Take a look at /postcss.config.js where you can tweak it if you need to.