diff --git a/app-vite/bin/quasar.js b/app-vite/bin/quasar.js
index 2cda328fbc6..9a0d8599e92 100755
--- a/app-vite/bin/quasar.js
+++ b/app-vite/bin/quasar.js
@@ -5,6 +5,7 @@ import '../lib/node-version-check.js'
const commands = [
'dev',
'build',
+ 'prepare',
'clean',
'inspect',
'describe',
@@ -23,6 +24,7 @@ if (cmd) {
const mapToCmd = {
d: 'dev',
b: 'build',
+ p: 'prepare',
e: 'ext',
r: 'run',
c: 'clean',
diff --git a/app-vite/lib/app-devserver.js b/app-vite/lib/app-devserver.js
index 19dff2ba7ef..bb924eb9dfd 100644
--- a/app-vite/lib/app-devserver.js
+++ b/app-vite/lib/app-devserver.js
@@ -2,6 +2,7 @@ import { AppTool } from './app-tool.js'
import { printDevRunningBanner } from './utils/banner.js'
import { encodeForDiff } from './utils/encode-for-diff.js'
import { EntryFilesGenerator } from './entry-files-generator.js'
+import { generateTypes } from './types-generator.js'
function getConfSnapshot (extractFn, quasarConf, diffExtractFnMap) {
return extractFn(quasarConf, diffExtractFnMap).map(item => (item ? encodeForDiff(item) : ''))
@@ -36,6 +37,11 @@ export class AppDevserver extends AppTool {
quasarConf.ssr.manualPostHydrationTrigger
]))
+ this.registerDiff('types', quasarConf => ([
+ quasarConf.build.typescript,
+ quasarConf.build.alias
+ ]))
+
this.registerDiff('viteUrl', quasarConf => ([
quasarConf.metaConf.APP_URL
]))
@@ -63,6 +69,10 @@ export class AppDevserver extends AppTool {
this.#entryFiles.generate(quasarConf)
}
+ if (this.#diff('types', quasarConf)) {
+ this.#queue(this.#runId, quasarConf, () => generateTypes(quasarConf))
+ }
+
if (__isRetry !== true) {
this.#runId++
}
diff --git a/app-vite/lib/cmd/build.js b/app-vite/lib/cmd/build.js
index 8a8fd862863..1b493511001 100755
--- a/app-vite/lib/cmd/build.js
+++ b/app-vite/lib/cmd/build.js
@@ -137,9 +137,6 @@ await quasarConfFile.init()
const quasarConf = await quasarConfFile.read()
-const { ensureTypesFeatureFlags } = await import('../utils/types-feature-flags.js')
-ensureTypesFeatureFlags(quasarConf)
-
const { QuasarModeBuilder } = await import(`../modes/${ argv.mode }/${ argv.mode }-builder.js`)
const appBuilder = new QuasarModeBuilder({ argv, quasarConf })
@@ -151,6 +148,9 @@ const { EntryFilesGenerator } = await import('../entry-files-generator.js')
const entryFiles = new EntryFilesGenerator(ctx)
entryFiles.generate(quasarConf)
+const { generateTypes } = await import('../types-generator.js')
+await generateTypes(quasarConf)
+
if (typeof quasarConf.build.beforeBuild === 'function') {
await quasarConf.build.beforeBuild({ quasarConf })
}
diff --git a/app-vite/lib/cmd/dev.js b/app-vite/lib/cmd/dev.js
index 1851cce6035..d76e04a22fd 100755
--- a/app-vite/lib/cmd/dev.js
+++ b/app-vite/lib/cmd/dev.js
@@ -156,9 +156,6 @@ await quasarConfFile.init()
const quasarConf = await quasarConfFile.read()
-import { ensureTypesFeatureFlags } from '../utils/types-feature-flags.js'
-ensureTypesFeatureFlags(quasarConf)
-
if (quasarConf.metaConf.vueDevtools !== false) {
await startVueDevtools(ctx, quasarConf.metaConf.vueDevtools.port)
}
diff --git a/app-vite/lib/cmd/help.js b/app-vite/lib/cmd/help.js
index 31ea44c3b82..2879cd3a7ee 100755
--- a/app-vite/lib/cmd/help.js
+++ b/app-vite/lib/cmd/help.js
@@ -28,6 +28,7 @@ console.log(`
Commands
dev, d Start a dev server for your App
build, b Build your app for production
+ prepare, p Prepare the app for linting, type-checking, IDE integration, etc.
clean, c Clean dev/build cache, /dist folder & entry points
new, n Quickly scaffold page/layout/component/... vue file
mode, m Add/remove Quasar Modes for your App
diff --git a/app-vite/lib/cmd/mode.js b/app-vite/lib/cmd/mode.js
index 3339ad39d9f..7bad9d77808 100755
--- a/app-vite/lib/cmd/mode.js
+++ b/app-vite/lib/cmd/mode.js
@@ -44,11 +44,13 @@ if (argv._.length !== 0 && argv._.length !== 2) {
import { green, gray } from 'kolorist'
import { getCtx } from '../utils/get-ctx.js'
-const ctx = getCtx()
+import { generateTypes } from '../types-generator.js'
async function run () {
const [ action, mode ] = argv._
+ const ctx = getCtx({ mode })
+
if (![ 'add', 'remove' ].includes(action)) {
console.log()
warn(`Unknown action specified (${ action }).`)
@@ -83,9 +85,23 @@ async function run () {
}
await actionMap[ action ]({ ctx })
+
+ // Ensure types are re-generated accordingly
+ const { QuasarConfigFile } = await import('../quasar-config-file.js')
+ const quasarConfFile = new QuasarConfigFile({
+ ctx,
+ // host and port don't matter for this command
+ port: 9000,
+ host: 'localhost'
+ })
+ await quasarConfFile.init()
+ const quasarConf = await quasarConfFile.read()
+ await generateTypes(quasarConf)
}
async function displayModes () {
+ const ctx = getCtx()
+
log('Detecting installed modes...')
const info = []
diff --git a/app-vite/lib/cmd/prepare.js b/app-vite/lib/cmd/prepare.js
new file mode 100755
index 00000000000..7236992d209
--- /dev/null
+++ b/app-vite/lib/cmd/prepare.js
@@ -0,0 +1,62 @@
+import parseArgs from 'minimist'
+
+import { log } from '../utils/logger.js'
+
+const argv = parseArgs(process.argv.slice(2), {
+ alias: {
+ h: 'help'
+ },
+ boolean: [ 'h' ]
+})
+
+if (argv.help) {
+ console.log(`
+ Description
+ Prepare the app for linting, type-checking, IDE integration, etc.
+ It will generate the relevant files such as '.quasar/tsconfig.json', types files, etc.
+ Running 'quasar dev' or 'quasar build' will automatically handle this for you.
+ Use this command for a lightweight alternative to dev/build. Useful in CI/CD pipelines.
+
+ Usage
+ $ quasar prepare
+
+ Options
+ --help, -h Displays this message
+ `)
+ process.exit(0)
+}
+
+const { readFileSync } = await import('node:fs')
+
+console.log(
+ readFileSync(
+ new URL('../../assets/logo.art', import.meta.url),
+ 'utf8'
+ )
+)
+
+const { getCtx } = await import('../utils/get-ctx.js')
+// ctx doesn't matter for this command
+const ctx = getCtx({
+ mode: 'spa',
+ debug: false,
+ prod: true
+})
+
+const { QuasarConfigFile } = await import('../quasar-config-file.js')
+const quasarConfFile = new QuasarConfigFile({
+ ctx,
+ // host and port don't matter for this command
+ port: 9000,
+ host: 'localhost'
+})
+
+await quasarConfFile.init()
+
+const quasarConf = await quasarConfFile.read()
+
+const { generateTypes } = await import('../types-generator.js')
+await generateTypes(quasarConf)
+
+log('Generated tsconfig.json and types files in .quasar directory')
+log('The app is now prepared for linting, type-checking, IDE integration, etc.')
diff --git a/app-vite/lib/modes/bex/bex-installation.js b/app-vite/lib/modes/bex/bex-installation.js
index 4dc7e6ce37c..74b956fa30d 100644
--- a/app-vite/lib/modes/bex/bex-installation.js
+++ b/app-vite/lib/modes/bex/bex-installation.js
@@ -2,7 +2,6 @@ import fs from 'node:fs'
import fse from 'fs-extra'
import { log, warn } from '../../utils/logger.js'
-import { generateTypesFeatureFlag } from '../../utils/types-feature-flags.js'
export function isModeInstalled (appPaths) {
return fs.existsSync(appPaths.bexDir)
@@ -23,7 +22,6 @@ export async function addMode ({
log('Creating Browser Extension source folder...')
fse.copySync(appPaths.resolve.cli('templates/bex/common'), appPaths.bexDir)
- generateTypesFeatureFlag('bex', appPaths)
const hasTypescript = await cacheProxy.getModule('hasTypescript')
const format = hasTypescript ? 'ts' : 'default'
diff --git a/app-vite/lib/modes/electron/electron-installation.js b/app-vite/lib/modes/electron/electron-installation.js
index ce967421aaa..f21689ff4bf 100644
--- a/app-vite/lib/modes/electron/electron-installation.js
+++ b/app-vite/lib/modes/electron/electron-installation.js
@@ -2,7 +2,6 @@ import fs from 'node:fs'
import fse from 'fs-extra'
import { log, warn } from '../../utils/logger.js'
-import { generateTypesFeatureFlag } from '../../utils/types-feature-flags.js'
const electronDeps = {
electron: 'latest'
@@ -37,8 +36,6 @@ export async function addMode ({
appPaths.electronDir
)
- generateTypesFeatureFlag('electron', appPaths)
-
log('Creating Electron icons folder...')
fse.copySync(
appPaths.resolve.cli('templates/electron/icons'),
diff --git a/app-vite/lib/modes/pwa/pwa-installation.js b/app-vite/lib/modes/pwa/pwa-installation.js
index c8ff531a6dd..7db7f1f25fd 100644
--- a/app-vite/lib/modes/pwa/pwa-installation.js
+++ b/app-vite/lib/modes/pwa/pwa-installation.js
@@ -2,7 +2,6 @@ import fs from 'node:fs'
import fse from 'fs-extra'
import { log, warn } from '../../utils/logger.js'
-import { generateTypesFeatureFlag } from '../../utils/types-feature-flags.js'
const defaultVersion = '^7.0.0'
@@ -58,8 +57,6 @@ export async function addMode ({
hasEslint === true ? { filter: src => !src.endsWith('/.eslintrc.cjs') } : void 0
)
- generateTypesFeatureFlag('pwa', appPaths)
-
log('Copying PWA icons to /public/icons/ (if they are not already there)...')
fse.copySync(
appPaths.resolve.cli('templates/pwa-icons'),
diff --git a/app-vite/lib/modes/ssr/ssr-devserver.js b/app-vite/lib/modes/ssr/ssr-devserver.js
index d1f5dccae12..6a8ee04106f 100644
--- a/app-vite/lib/modes/ssr/ssr-devserver.js
+++ b/app-vite/lib/modes/ssr/ssr-devserver.js
@@ -279,7 +279,7 @@ export class QuasarModeDevserver extends AppDevserver {
}
async #bootWebserver (quasarConf) {
- const done = progress(`Booting Webserver...`)
+ const done = progress('Booting Webserver...')
if (this.#webserver !== null) {
await this.#webserver.close()
diff --git a/app-vite/lib/modes/ssr/ssr-installation.js b/app-vite/lib/modes/ssr/ssr-installation.js
index d1f893a420c..807de51469f 100644
--- a/app-vite/lib/modes/ssr/ssr-installation.js
+++ b/app-vite/lib/modes/ssr/ssr-installation.js
@@ -2,7 +2,6 @@ import fs from 'node:fs'
import fse from 'fs-extra'
import { log, warn } from '../../utils/logger.js'
-import { generateTypesFeatureFlag } from '../../utils/types-feature-flags.js'
export function isModeInstalled (appPaths) {
return fs.existsSync(appPaths.ssrDir)
@@ -27,8 +26,6 @@ export async function addMode ({
appPaths.ssrDir
)
- generateTypesFeatureFlag('ssr', appPaths)
-
log('SSR support was added')
}
diff --git a/app-vite/lib/quasar-config-file.js b/app-vite/lib/quasar-config-file.js
index 07c6256ddf0..b1f1ae7255a 100644
--- a/app-vite/lib/quasar-config-file.js
+++ b/app-vite/lib/quasar-config-file.js
@@ -281,7 +281,14 @@ export class QuasarConfigFile {
plugins: [
quasarEsbuildInjectReplacementsPlugin,
quasarEsbuildVueShimPlugin
- ]
+ ],
+ logOverride: {
+ // .quasar/tsconfig.json won't be available for the first time executing dev/build/prepare.
+ // So, esbuild will show a warning saying it can't find the `extends` file.
+ // We need to suppress the warning. Otherwise, it will be noisy and cause a temp file to be created.
+ // tsconfig is not really important for the config file itself anyway.
+ 'tsconfig.json': 'silent'
+ }
}
}
@@ -755,6 +762,11 @@ export class QuasarConfigFile {
assets: appPaths.resolve.src('assets'),
boot: appPaths.resolve.src('boot'),
stores: appPaths.resolve.src('stores')
+ },
+
+ typescript: {
+ strict: false,
+ vueShim: false
}
}, cfg.build)
diff --git a/app-vite/lib/types-generator.js b/app-vite/lib/types-generator.js
new file mode 100644
index 00000000000..fd70cb52bf8
--- /dev/null
+++ b/app-vite/lib/types-generator.js
@@ -0,0 +1,213 @@
+import { readFileSync, writeFileSync, statSync } from 'node:fs'
+import { ensureFileSync } from 'fs-extra'
+import { join, relative } from 'node:path'
+
+// We generate all the files for JS projects as well, because they provide
+// better autocomplete and type checking in the IDE.
+export async function generateTypes (quasarConf) {
+ const { appPaths } = quasarConf.ctx
+
+ const tsConfigPath = appPaths.resolve.app('.quasar/tsconfig.json')
+ ensureFileSync(tsConfigPath)
+ writeFileSync(tsConfigPath, JSON.stringify(generateTsConfig(quasarConf), null, 2), 'utf-8')
+
+ await writeFeatureFlags(quasarConf)
+
+ writeDeclarations(quasarConf)
+}
+
+/**
+ * @param {import('../types/configuration/conf').QuasarConf} quasarConf
+ */
+function generateTsConfig (quasarConf) {
+ const { appPaths, mode } = quasarConf.ctx
+
+ const toTsPath = (path) => {
+ const relativePath = relative(appPaths.resolve.app('.quasar'), path)
+ if (relativePath.length === 0) {
+ return '.'
+ }
+ if (!relativePath.startsWith('./')) {
+ return './' + relativePath
+ }
+ return relativePath
+ }
+
+ const aliases = { ...quasarConf.build.alias }
+
+ // TS aliases doesn't play well with package.json#exports: https://github.com/microsoft/TypeScript/issues/60460
+ // So, we had to specify each entry point separately here
+ const appVitePath = 'node_modules/@quasar/app-vite'
+ delete aliases[ '#q-app' ] // remove the existing one so that all the added ones are listed under each other
+ aliases[ '#q-app' ] = toTsPath(join(appVitePath, 'types/index.d.ts'))
+ aliases[ '#q-app/wrappers' ] = toTsPath(join(appVitePath, 'types/app-wrappers.d.ts'))
+ aliases[ '#q-app/bex/background' ] = toTsPath(join(appVitePath, 'types/bex/entrypoints/background.d.ts'))
+ aliases[ '#q-app/bex/content' ] = toTsPath(join(appVitePath, 'types/bex/entrypoints/content.d.ts'))
+
+ if (mode.capacitor) {
+ // Can't use cacheProxy.getRuntime('runtimeCapacitorConfig') as it's not available here yet
+ const { dependencies } = JSON.parse(
+ readFileSync(appPaths.resolve.capacitor('package.json'), 'utf-8')
+ )
+ const target = appPaths.resolve.capacitor('node_modules')
+ const depsList = Object.keys(dependencies)
+ depsList.forEach(dep => {
+ aliases[ dep ] = join(target, dep)
+ })
+ }
+
+ const paths = Object.fromEntries(
+ Object.entries(aliases).flatMap(([ alias, path ]) => {
+ const stats = statSync(path, { throwIfNoEntry: false })
+ // If the path doesn't exist, don't add an alias for it yet (e.g. src/stores)
+ if (!stats) {
+ return []
+ }
+
+ if (stats.isFile()) {
+ return [
+ [ alias, [ toTsPath(path) ] ]
+ ]
+ }
+
+ return [
+ // import ... from 'src' (resolves to 'src/index')
+ [ alias, [ toTsPath(path) ] ],
+ // import ... from 'src/something' (resolves to 'src/something.ts' or 'src/something/index.ts')
+ [ `${ alias }/*`, [ `${ toTsPath(path) }/*` ] ]
+ ]
+ })
+ )
+
+ // See https://www.totaltypescript.com/tsconfig-cheat-sheet
+ // We use ESNext since we are transpiling and pretty much everything should work
+ const tsConfig = {
+ compilerOptions: {
+ esModuleInterop: true,
+ skipLibCheck: true,
+ target: 'esnext',
+ allowJs: true,
+ resolveJsonModule: true,
+ moduleDetection: 'force',
+ isolatedModules: true,
+ // force using `import type`/`export type`
+ verbatimModuleSyntax: true,
+
+ // We are not transpiling with tsc, so leave it to the bundler
+ module: 'preserve', // implies `moduleResolution: 'bundler'`
+ noEmit: true,
+
+ lib: [ 'esnext', 'dom', 'dom.iterable' ],
+
+ /**
+ * Keep in sync with the description of `typescript.strict` in {@link file://./../types/configuration/build.d.ts}
+ */
+ ...(quasarConf.build.typescript.strict
+ ? {
+ strict: true,
+ allowUnreachableCode: false,
+ allowUnusedLabels: false,
+ noImplicitOverride: true,
+ exactOptionalPropertyTypes: true,
+ noUncheckedIndexedAccess: true
+ }
+ : {}),
+
+ paths
+ },
+ exclude: [
+ 'dist',
+ '.quasar/*/*.js',
+ 'node_modules',
+ 'src-capacitor',
+ 'src-cordova',
+ 'quasar.config.*.temporary.compiled*'
+ ].map(path => toTsPath(path))
+ }
+
+ quasarConf.build.typescript.extendTsConfig?.(tsConfig)
+
+ return tsConfig
+}
+
+// We don't have a specific entry for the augmenting file in `package.json > exports`
+// We rely on the wildcard entry, so we use a deep import, instead of let's say `quasar/feature-flags`
+// When using TypeScript `moduleResolution: "bundler"`, it requires the file extension.
+// This may sound unusual, but that's because it seems to treat wildcard entries differently.
+const featureFlagsTemplate = `/* eslint-disable */
+import "quasar/dist/types/feature-flag.d.ts";
+
+declare module "quasar/dist/types/feature-flag.d.ts" {
+ interface QuasarFeatureFlags {
+ __INJECTION_POINT__
+ }
+}
+`
+
+/**
+ * Flags are also available in JS codebases because feature flags still
+ * benefit JS users by providing autocomplete.
+ *
+ * @param {import('../types/configuration/conf').QuasarConf} quasarConf
+ */
+async function writeFeatureFlags (quasarConf) {
+ const { appPaths } = quasarConf.ctx
+
+ const featureFlags = new Set()
+
+ if (quasarConf.metaConf.hasStore === true) {
+ featureFlags.add('store')
+ }
+
+ // spa does not have a feature flag, so we skip it
+ const modes = [ 'pwa', 'ssr', 'cordova', 'capacitor', 'electron', 'bex' ]
+ for (const modeName of modes) {
+ const { isModeInstalled } = await import(`./modes/${ modeName }/${ modeName }-installation.js`)
+ if (isModeInstalled(quasarConf.ctx.appPaths)) {
+ featureFlags.add(modeName)
+ }
+ }
+
+ const flagDefinitions = Array.from(featureFlags)
+ .map(flag => `${ flag }: true;`)
+ .join('\n ')
+ const contents = featureFlagsTemplate.replace(
+ '__INJECTION_POINT__',
+ flagDefinitions || '// no feature flags'
+ )
+
+ writeFileSync(appPaths.resolve.app('.quasar/feature-flags.d.ts'), contents)
+}
+
+/*
+ Load app-vite's augmentations for `quasar` package.
+ It will augment CLI-specific features.
+
+ Load Vite's client types, see https://vitejs.dev/guide/features#client-types
+*/
+const declarationsTemplate = `/* eslint-disable */
+///
+
+///
+`
+
+// Mocks all files ending in `.vue` showing them as plain Vue instances
+const vueShimsTemplate = `/* eslint-disable */
+declare module '*.vue' {
+ import { DefineComponent } from 'vue';
+ const component: DefineComponent;
+ export default component;
+}
+`
+
+/**
+ * @param {import('../types/configuration/conf').QuasarConf} quasarConf
+ */
+function writeDeclarations (quasarConf) {
+ const { appPaths } = quasarConf.ctx
+
+ writeFileSync(appPaths.resolve.app('.quasar/quasar.d.ts'), declarationsTemplate)
+ if (quasarConf.build.typescript.vueShim) {
+ writeFileSync(appPaths.resolve.app('.quasar/shims-vue.d.ts'), vueShimsTemplate)
+ }
+}
diff --git a/app-vite/lib/utils/types-feature-flags.js b/app-vite/lib/utils/types-feature-flags.js
deleted file mode 100644
index 344c36050fe..00000000000
--- a/app-vite/lib/utils/types-feature-flags.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import { join, dirname } from 'node:path'
-import { writeFileSync, existsSync } from 'node:fs'
-
-import { log } from './logger.js'
-
-// We don't have a specific entry for the augmenting file in `package.json > exports`
-// We rely on the wildcard entry, so we use a deep import, instead of let's say `quasar/feature-flags`
-// When using TypeScript `moduleResolution: "bundler"`, it requires the file extension.
-// This may sound unusual, but that's because it seems to treat wildcard entries differently.
-//
-// Keep in sync with `create-quasar/templates/**/store-flag.d.ts`
-const template = `/* eslint-disable */
-/*
- WARNING: DO NOT MODIFY OR DELETE
- This file is auto-generated by Quasar CLI
- It's recommended to NOT .gitignore it
- You don't have to use TypeScript in your project, don't worry
-*/
-import "quasar/dist/types/feature-flag.d.ts";
-
-declare module "quasar/dist/types/feature-flag.d.ts" {
- interface QuasarFeatureFlags {
- __FEATURE_NAME__: true;
- }
-}
-`
-
-export function generateTypesFeatureFlag (modeName, appPaths) {
- const destFlagPath = appPaths.resolve[ modeName ](`${ modeName }-flag.d.ts`)
-
- writeFileSync(
- destFlagPath,
- template.replace('__FEATURE_NAME__', modeName)
- )
-}
-
-export function ensureTypesFeatureFlags (quasarConf) {
- // Flags must be available even in pure JS codebases,
- // because boot and configure wrappers functions files will
- // provide autocomplete based on them also to JS users
- // Flags files should be copied over, for every enabled mode,
- // every time `quasar dev` and `quasar build` are run:
- // this automatize the upgrade for existing codebases
- const { appPaths, mode } = quasarConf.ctx
-
- if (quasarConf.metaConf.hasStore === true) {
- const destFlagPath = appPaths.resolve.app(
- join(dirname(quasarConf.sourceFiles.store), 'store-flag.d.ts')
- )
-
- if (!existsSync(destFlagPath)) {
- writeFileSync(
- destFlagPath,
- template.replace('__FEATURE_NAME__', 'store')
- )
- log('"store" feature flag was missing and has been regenerated')
- }
- }
-
- for (const modeName of Object.keys(mode)) {
- if (modeName === 'spa' || mode[ modeName ] !== true) {
- continue
- }
-
- const destFlagPath = appPaths.resolve[ modeName ](`${ modeName }-flag.d.ts`)
-
- if (!existsSync(destFlagPath)) {
- writeFileSync(
- destFlagPath,
- template.replace('__FEATURE_NAME__', modeName)
- )
- log(`"${ modeName }" feature flag was missing and has been regenerated`)
- }
- }
-}
diff --git a/app-vite/package.json b/app-vite/package.json
index 46d149c76c6..dfb6dcb8c97 100644
--- a/app-vite/package.json
+++ b/app-vite/package.json
@@ -12,9 +12,7 @@
"bin",
"lib",
"templates",
- "types",
- "stricter-tsconfig-preset.json",
- "tsconfig-preset.json"
+ "types"
],
"types": "./types/index.d.ts",
"exports": {
@@ -139,6 +137,7 @@
"electron-builder": ">= 22",
"pinia": "^2.0.0",
"quasar": "^2.16.0",
+ "typescript": ">= 5.4",
"vue": "^3.2.29",
"vue-router": "^4.0.12",
"vuex": "^4.0.0",
diff --git a/app-vite/stricter-tsconfig-preset.json b/app-vite/stricter-tsconfig-preset.json
deleted file mode 100644
index 87c5c03da27..00000000000
--- a/app-vite/stricter-tsconfig-preset.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "extends": "./tsconfig-preset.json",
- "compilerOptions": {
- "exactOptionalPropertyTypes": true,
- "noImplicitReturns": true,
- "noPropertyAccessFromIndexSignature": true,
- "noUncheckedIndexedAccess": true
- }
-}
diff --git a/app-vite/tsconfig-preset.json b/app-vite/tsconfig-preset.json
deleted file mode 100644
index 28c89a88ae9..00000000000
--- a/app-vite/tsconfig-preset.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "compilerOptions": {
- "allowImportingTsExtensions": true,
- "allowJs": true,
-
- // `"baseUrl": "."` option should be defined in the tsconfig file in devland which extends this preset
- // That way, the `paths` we defined in this preset are resolved starting from the devland project root
- // instead of this TS preset file's location
- // This allows Quasar to centralize the management of default paths in the preset,
- // instead of asking the dev to keep them in sync when something changes
-
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- // Needed to address https://github.com/quasarframework/app-extension-typescript/issues/36
- "noEmit": true,
- "resolveJsonModule": true,
- "sourceMap": true,
- "strict": true,
- "target": "esnext",
- "lib": ["esnext", "dom"],
- "isolatedModules": true,
-
- "paths": {
- // TS aliases doesn't play well with package.json#exports: https://github.com/microsoft/TypeScript/issues/60460
- // So, we had to specify each entry point separately here
- "#q-app": ["node_modules/@quasar/app-vite"],
- "#q-app/wrappers": ["node_modules/@quasar/app-vite/types/app-wrappers.d.ts"],
- "#q-app/bex/background": ["node_modules/@quasar/app-vite/types/bex/entrypoints/background.d.ts"],
- "#q-app/bex/content": ["node_modules/@quasar/app-vite/types/bex/entrypoints/content.d.ts"],
- "src/*": ["src/*"],
- "app/*": ["*"],
- "components/*": ["src/components/*"],
- "layouts/*": ["src/layouts/*"],
- "pages/*": ["src/pages/*"],
- "assets/*": ["src/assets/*"],
- "boot/*": ["src/boot/*"],
- "stores/*": ["src/stores/*"]
- },
-
- // Fix Volar issue https://github.com/johnsoncodehk/volar/issues/1153
- "jsx": "preserve",
-
- // Rules preventing code smells and enforcing best practices, partially overlapping with linting
- "allowUnreachableCode": false,
- "allowUnusedLabels": false,
- "noImplicitOverride": true,
- "noUnusedLocals": true,
- // Avoid cross-os errors due to inconsistent file casing
- "forceConsistentCasingInFileNames": true,
- }
-}
diff --git a/app-vite/types/configuration/build.d.ts b/app-vite/types/configuration/build.d.ts
index 21eebb08f3a..0b45f34bb49 100644
--- a/app-vite/types/configuration/build.d.ts
+++ b/app-vite/types/configuration/build.d.ts
@@ -1,6 +1,7 @@
import { Plugin, UserConfig as ViteUserConfig } from "vite";
import { Options as VuePluginOptions } from "@vitejs/plugin-vue"
import { QuasarHookParams } from "./conf";
+import { CompilerOptions, TypeAcquisition } from "typescript";
interface HtmlMinifierOptions {
caseSensitive?: boolean;
@@ -43,6 +44,30 @@ interface HtmlMinifierOptions {
useShortDoctype?: boolean;
}
+// TSConfig type is adapted from https://github.com/unjs/pkg-types/blob/0bec64641468c9560dea95da2cff502ea8118286/src/types/tsconfig.ts
+type StripEnums> = {
+ [K in keyof T]: T[K] extends boolean
+ ? T[K]
+ : T[K] extends string
+ ? T[K]
+ : T[K] extends object
+ ? T[K]
+ : T[K] extends Array
+ ? T[K]
+ : T[K] extends undefined
+ ? undefined
+ : any;
+};
+interface TSConfig {
+ compilerOptions?: StripEnums;
+ exclude?: string[];
+ compileOnSave?: boolean;
+ extends?: string | string[];
+ files?: string[];
+ include?: string[];
+ typeAcquisition?: TypeAcquisition;
+}
+
interface InvokeParams {
isClient: boolean;
isServer: boolean;
@@ -144,6 +169,45 @@ interface QuasarStaticBuildConfiguration {
* }
*/
alias?: { [key: string]: string };
+ /**
+ * Configuration for TypeScript integration.
+ */
+ typescript?: {
+ /**
+ * Once your codebase is fully using TypeScript and all team members are comfortable with it,
+ * you can set this to `true` to enforce stricter type checking.
+ * It is recommended to set this to `true` and use stricter typescript-eslint rules.
+ *
+ * It will set the following TypeScript options:
+ * - "strict": true
+ * - "allowUnreachableCode": false
+ * - "allowUnusedLabels": false
+ * - "noImplicitOverride": true
+ * - "exactOptionalPropertyTypes": true
+ * - "noUncheckedIndexedAccess": true
+ *
+ * @see https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html#getting-stricter-checks
+ */
+ strict?: boolean;
+
+ /**
+ * Extend the generated `.quasar/tsconfig.json` file.
+ *
+ * If you don't have dynamic logic, you can directly modify your `tsconfig.json` file instead.
+ */
+ extendTsConfig?: (tsConfig: TSConfig) => void;
+
+ /**
+ * Generate a shim file for `*.vue` files to process them as plain Vue component instances.
+ *
+ * Vue Language Tools VS Code extension can analyze `*.vue` files in a better way, without the shim file.
+ * So, you can disable the shim file generation and let the extension handle the types.
+ *
+ * However, some tools like ESLint can't work with `*.vue` files without the shim file.
+ * So, if your tooling is not properly working, enable this option.
+ */
+ vueShim?: boolean;
+ };
/**
* Public path of your app.
* Use it when your public path is something else,
diff --git a/app-vite/types/index.d.ts b/app-vite/types/index.d.ts
index faa66435b99..a418b7cd884 100644
--- a/app-vite/types/index.d.ts
+++ b/app-vite/types/index.d.ts
@@ -1,10 +1,6 @@
-// These imports force TS compiler to evaluate contained declarations
-// which by defaults would be ignored because inside node_modules
-// and not directly referenced by any file
-// These types had to be moved from `quasar` to `@quasar/app-vite` to avoid generating TS errors
-// in Vue CLI projects, given that these features are only available for Quasar CLI projects
-// TS doesn't allow re-exports into module augmentation, so we were forced to
-// manually declare every file as a `quasar` augmentation
+// `quasar` package has some Quasar CLI-specific features, e.g. $q.cordova, 'quasar/wrappers', etc.
+// Those types should not be available in there when not using Quasar CLI
+// So, we augment the `quasar` package with these features from each engine (app-vite, app-webpack)
import "./globals";
export * from "./bex";
diff --git a/create-quasar/templates/app/quasar-v2/js-vite-2/BASE/_jsconfig.json b/create-quasar/templates/app/quasar-v2/js-vite-2/BASE/_jsconfig.json
index 4abfb6b1b82..a17e05fa70c 100644
--- a/create-quasar/templates/app/quasar-v2/js-vite-2/BASE/_jsconfig.json
+++ b/create-quasar/templates/app/quasar-v2/js-vite-2/BASE/_jsconfig.json
@@ -1,48 +1,3 @@
{
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "#q-app": [
- "node_modules/@quasar/app-vite"
- ],
- "#q-app/wrappers": [
- "node_modules/@quasar/app-vite/types/app-wrappers.d.ts"
- ],
- "#q-app/bex/background": [
- "node_modules/@quasar/app-vite/types/bex/entrypoints/background.d.ts"
- ],
- "#q-app/bex/content": [
- "node_modules/@quasar/app-vite/types/bex/entrypoints/content.d.ts"
- ],
- "src/*": [
- "src/*"
- ],
- "app/*": [
- "*"
- ],
- "components/*": [
- "src/components/*"
- ],
- "layouts/*": [
- "src/layouts/*"
- ],
- "pages/*": [
- "src/pages/*"
- ],
- "assets/*": [
- "src/assets/*"
- ],
- "boot/*": [
- "src/boot/*"
- ],
- "stores/*": [
- "src/stores/*"
- ]
- }
- },
- "exclude": [
- "dist",
- ".quasar",
- "node_modules"
- ]
+ "extends": "./.quasar/tsconfig.json"
}
diff --git a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json
index 8d8cf77929e..c6faacd06d9 100644
--- a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json
+++ b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json
@@ -12,7 +12,8 @@
<% } %>
"test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev",
- "build": "quasar build"
+ "build": "quasar build",
+ "postinstall": "quasar prepare"
},
"dependencies": {
<% if (preset.axios) { %>"axios": "^1.2.1",<% } %>
diff --git a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_tsconfig.json b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_tsconfig.json
index 6b2177abff9..a17e05fa70c 100644
--- a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_tsconfig.json
+++ b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_tsconfig.json
@@ -1,14 +1,3 @@
{
- "extends": "@quasar/app-vite/tsconfig-preset",
- "compilerOptions": {
- "baseUrl": "."
- },
- "exclude": [
- "./dist",
- "./.quasar",
- "./node_modules",
- "./src-capacitor",
- "./src-cordova",
- "./quasar.config.*.temporary.compiled*"
- ]
+ "extends": "./.quasar/tsconfig.json"
}
diff --git a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/quasar.config.ts b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/quasar.config.ts
index d9c01f0aef1..a52e6aafc4d 100644
--- a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/quasar.config.ts
+++ b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/quasar.config.ts
@@ -48,6 +48,12 @@ export default defineConfig((<% if (preset.i18n) { %>ctx<% } else { %>/* ctx */<
node: 'node20'
},
+ typescript: {
+ strict: true,
+ vueShim: true
+ // extendTsConfig(tsConfig) {}
+ },
+
vueRouterMode: 'hash', // available values: 'hash', 'history'
// vueRouterBase,
// vueDevtools,
@@ -82,9 +88,7 @@ export default defineConfig((<% if (preset.i18n) { %>ctx<% } else { %>/* ctx */<
include: [ fileURLToPath(new URL('./src/i18n', import.meta.url)) ],
}]<% } %><% if (preset.lint) { %><% if (preset.i18n) { %>,<% } %>
['vite-plugin-checker', {
- vueTsc: {
- tsconfigPath: 'tsconfig.vue-tsc.json'
- },
+ vueTsc: true,
eslint: {
lintCommand: 'eslint "./**/*.{js,ts,mjs,cjs,vue}"'
}
diff --git a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/src/App.vue b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/src/App.vue
index 9219f612279..a340030664c 100644
--- a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/src/App.vue
+++ b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/src/App.vue
@@ -3,9 +3,7 @@
<% if (sfcStyle === 'composition-setup') { %>
<% } else if (sfcStyle === 'composition' || sfcStyle === 'options') { %>
<% } else if (sfcStyle === 'options') { %>
<% } else if (sfcStyle === 'composition') { %>
<% } else if (sfcStyle === 'options') { %>
<% } else if (sfcStyle === 'composition' || sfcStyle === 'options') { %>
<% } else if (sfcStyle === 'composition') { %>
<% } else if (sfcStyle === 'options') { %>