UNPKG

@quasar/app-vite

Version:

Quasar Framework App CLI with Vite

1,278 lines (1,083 loc) 36.3 kB
import { join, isAbsolute, basename, dirname, relative } from 'node:path' import { pathToFileURL } from 'node:url' import { existsSync, readFileSync } from 'node:fs' import { createRequire } from 'node:module' import fse from 'fs-extra' import { merge } from 'webpack-merge' import debounce from 'lodash/debounce.js' import { build as esBuild, context as esContextBuild } from 'esbuild' import { transformAssetUrls } from '@quasar/vite-plugin' import { log, warn, fatal, tip } from './utils/logger.js' import { appFilesValidations } from './utils/app-files-validations.js' import { getPackageMajorVersion } from './utils/get-package-major-version.js' import { resolveExtension } from './utils/resolve-extension.js' import { ensureElectronArgv } from './utils/ensure-argv.js' import { quasarEsbuildInjectReplacementsDefine, quasarEsbuildInjectReplacementsPlugin } from './plugins/esbuild.inject-replacements.js' import { quasarEsbuildVueShimPlugin } from './plugins/esbuild.vue-shim.js' const urlRegex = /^http(s)?:\/\//i import { findClosestOpenPort, localHostList } from './utils/net.js' import { isMinimalTerminal } from './utils/is-minimal-terminal.js' import { readFileEnv } from './utils/env.js' import { BASELINE_WIDELY_AVAILABLE_TARGET_STRING } from './config-tools.js' const defaultPortMapping = { spa: 9000, ssr: 9100, // 9150 for SSR + PWA pwa: 9200, electron: 9300, cordova: 9400, capacitor: 9500, bex: 9600 } const quasarComponentRE = /^(Q[A-Z]|q-)/ const quasarConfigBanner = `/* eslint-disable */ /** * THIS FILE IS GENERATED AUTOMATICALLY. * 1. DO NOT edit this file directly as it won't do anything. * 2. EDIT the original quasar.config file INSTEAD. * 3. DO NOT git commit this file. It should be ignored. * * This file is still here because there was an error in * the original quasar.config file and this allows you to * investigate the Node.js stack error. * * After you fix the original file, this file will be * deleted automatically. **/ ` function escapeHTMLTagContent(str) { return str ? str.replace(/[<>]/g, '') : '' } function escapeHTMLAttribute(str) { return str ? str.replace(/"/g, '') : '' } function formatPublicPath(publicPath) { if (!publicPath) { return '/' } if (!publicPath.endsWith('/')) { publicPath = `${publicPath}/` } if (urlRegex.test(publicPath) === true) { return publicPath } if (!publicPath.startsWith('/')) { publicPath = `/${publicPath}` } return publicPath } function formatRouterBase(publicPath) { if (!publicPath || !publicPath.startsWith('http')) { return publicPath } const match = publicPath.match( /^(https?:)\/\/(([^:/?#]*)(?::([0-9]+))?)([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/ ) return formatPublicPath(match[5] || '') } function parseAssetProperty(prefix) { return asset => { if (typeof asset === 'string') { return { path: asset[0] === '~' ? asset.substring(1) : prefix + `/${asset}` } } return { ...asset, path: typeof asset.path === 'string' ? asset.path[0] === '~' ? asset.path.substring(1) : prefix + `/${asset.path}` : asset.path } } } function getUniqueArray(original) { return Array.from(new Set(original)) } function uniquePathFilter(value, index, self) { return self.map(obj => obj.path).indexOf(value.path) === index } const extRE = /\.[m|c]?[j|t]s$/ function formatQuasarAssetPath(asset, type) { return asset.indexOf('/') !== -1 ? extRE.test(asset) === true ? asset : `${asset}.js` : `quasar/${type}/${asset}.js` } let cachedExternalHost, addressRunning = false async function onAddress({ host, port }, mode) { if ( ['cordova', 'capacitor'].includes(mode) && (!host || localHostList.includes(host.toLowerCase())) ) { if (cachedExternalHost) { host = cachedExternalHost } else { const { getExternalIP } = await import('./utils/get-external-ip.js') host = await getExternalIP() cachedExternalHost = host } } try { const openPort = await findClosestOpenPort(port, host) if (port !== openPort) { warn() warn(`️️Setting port to closest one available: ${openPort}`) warn() port = openPort } } catch (e) { warn() if (e.message === 'ERROR_NETWORK_PORT_NOT_AVAIL') { warn( 'Could not find an open port. Please configure a lower one to start searching with.' ) } else if (e.message === 'ERROR_NETWORK_ADDRESS_NOT_AVAIL') { warn( 'Invalid host specified. No network address matches. Please specify another one.' ) } else { warn('Unknown network error occurred') console.error(e) } warn() if (addressRunning === false) { process.exit(1) } return null } addressRunning = true return { host, port } } let wsToken = null function createWsToken() { if (wsToken === null) { const list = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' const listLen = list.length wsToken = '' for (let i = 0; i < 12; i++) { wsToken += list.charAt(Math.floor(Math.random() * listLen)) } } return wsToken } export class QuasarConfigFile { /** @type {import('../types/configuration/context').QuasarContext} */ #ctx #opts #versions = {} #address #isWatching = false #require #tempFile #cssVariables #storeProvider #vueDevtools #electronInspectPort constructor({ ctx, host, port, verifyAddress, watch }) { this.#ctx = ctx this.#opts = { host, port, verifyAddress } if (watch !== void 0) { this.#opts.watch = debounce(watch, 550) } const { appPaths } = ctx this.#require = appPaths.quasarConfigOutputFormat === 'cjs' ? createRequire(import.meta.url) : () => {} const quasarConfigFileExtension = appPaths.quasarConfigOutputFormat === 'esm' ? 'mjs' : appPaths.quasarConfigOutputFormat // if filename syntax gets changed, then also update the "clean" cmd this.#tempFile = `${appPaths.quasarConfigFilename}.temporary.compiled.${Date.now()}.${quasarConfigFileExtension}` log( `Using ${basename(appPaths.quasarConfigFilename)} in "${appPaths.quasarConfigInputFormat}" format` ) } async init() { const { appPaths, cacheProxy, appExt } = this.#ctx await Promise.all([ cacheProxy.getModule('cssVariables').then(cssVariables => { this.#cssVariables = cssVariables }), cacheProxy.getModule('storeProvider').then(storeProvider => { this.#storeProvider = storeProvider }), cacheProxy.getModule('vueRouterVersion').then(vueRouterVersion => { this.#versions.vueRouter = vueRouterVersion }) ]) await appExt.registerAppExtensions() if (this.#ctx.mode.pwa) { // Enable this when workbox bumps version (as of writing these lines, we're handling v6 & v7) // this.#versions.workbox = getPackageMajorVersion('workbox-build', appPaths.appDir) } else if (this.#ctx.mode.capacitor) { const { capVersion } = await cacheProxy.getModule('capCli') const getCapPluginVersion = capVersion <= 2 ? () => true : name => { const version = getPackageMajorVersion( name, appPaths.capacitorDir ) return version === void 0 ? false : version || true } Object.assign(this.#versions, { capacitor: capVersion, capacitorPluginApp: getCapPluginVersion('@capacitor/app'), capacitorPluginSplashscreen: getCapPluginVersion( '@capacitor/splash-screen' ) }) } } read() { const esbuildConfig = this.#createEsbuildConfig() return this.#opts.watch !== void 0 ? this.#buildAndWatch(esbuildConfig) : this.#build(esbuildConfig) } // start watching for changes watch() { this.#isWatching = true } /** * @returns {import('esbuild').BuildOptions} */ #createEsbuildConfig() { const { appPaths } = this.#ctx return { platform: 'node', format: appPaths.quasarConfigOutputFormat, bundle: true, packages: 'external', banner: { js: quasarConfigBanner }, define: quasarEsbuildInjectReplacementsDefine, // Define the aliases which have to be usable in the quasar.config file alias: { '#q-app': '@quasar/app-vite' }, resolveExtensions: [ appPaths.quasarConfigOutputFormat === 'esm' ? '.mjs' : '.cjs', '.js', '.mts', '.ts', '.json' ], entryPoints: [appPaths.quasarConfigFilename], outfile: this.#tempFile, 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' } } } async #build(esbuildConfig) { try { await esBuild(esbuildConfig) } catch (e) { fse.removeSync(this.#tempFile) console.log() console.error(e) fatal( 'Could not compile the quasar.config file because it has errors.', 'FAIL' ) } let quasarConfigFn try { const fnResult = await import(pathToFileURL(this.#tempFile)) quasarConfigFn = fnResult.default || fnResult } catch (e) { console.log() console.error(e) fatal( 'The quasar.config file has runtime errors. Please check the Node.js stack above against the' + ` temporarily created ${basename(this.#tempFile)} file, fix the original file` + ' then DELETE the temporary one ("quasar clean --qconf" can be used).', 'FAIL' ) } return this.#computeConfig(quasarConfigFn, true) } async #buildAndWatch(esbuildConfig) { let firstBuildIsDone const { appPaths } = this.#ctx const tempFile = this.#tempFile esbuildConfig.plugins.push({ name: 'quasar:watcher', setup: build => { let isFirst = true build.onStart(() => { if (isFirst === false) { log() log( 'The quasar.config file (or its dependencies) changed. Reading it again...' ) } }) build.onEnd(async result => { // not ready yet; watch() has not been issued yet if (isFirst === false && this.#isWatching === false) return if (result.errors.length !== 0) { fse.removeSync(tempFile) const msg = 'Could not compile the quasar.config file because it has errors.' if (isFirst === true) { fatal(msg, 'FAIL') } warn(msg + ' Please fix them.\n') return } let quasarConfigFn // ensure we grab the latest version if (appPaths.quasarConfigOutputFormat === 'cjs') { delete this.#require.cache[tempFile] } try { const res = appPaths.quasarConfigOutputFormat === 'esm' ? await import(pathToFileURL(tempFile) + '?t=' + Date.now()) // we also need to cache bust it, hence the ?t= param : this.#require(tempFile) quasarConfigFn = res.default || res } catch (e) { // free up memory immediately if (appPaths.quasarConfigOutputFormat === 'cjs') { delete this.#require.cache[tempFile] } console.log() console.error(e) const msg = 'Importing quasar.config file results in error. Please check the' + ` Node.js stack above against the temporarily created ${basename(tempFile)} file` + ' and fix the original file then DELETE the temporary one ("quasar clean --qconf" can be used).' if (isFirst === true) { fatal(msg, 'FAIL') } warn(msg + '\n') return } // free up memory immediately if (appPaths.quasarConfigOutputFormat === 'cjs') { delete this.#require.cache[tempFile] } const quasarConf = await this.#computeConfig(quasarConfigFn, isFirst) if (quasarConf === void 0) return if (isFirst === true) { isFirst = false firstBuildIsDone(quasarConf) return } log('Scheduled to apply quasar.config changes in 550ms') this.#opts.watch(quasarConf) }) } }) const esbuildCtx = await esContextBuild(esbuildConfig) await esbuildCtx.watch() return new Promise(res => { firstBuildIsDone = res }) } // return void 0 if it encounters errors // and quasarConf otherwise async #computeConfig(quasarConfigFn, failOnError) { if (typeof quasarConfigFn !== 'function') { fse.removeSync(this.#tempFile) const msg = 'The default export value of the quasar.config file is not a function.' if (failOnError === true) { fatal(msg, 'FAIL') } warn(msg + ' Please fix it.\n') return } let userCfg try { userCfg = await quasarConfigFn(this.#ctx) } catch (e) { console.log() console.error(e) const msg = 'The quasar.config file has runtime errors.' + ' Please check the Node.js stack above against the' + ` temporarily created ${basename(this.#tempFile)} file` + ' then DELETE it ("quasar clean --qconf" can be used).' if (failOnError === true) { fatal(msg, 'FAIL') } warn(msg + ' Please fix the errors in the original file.\n') return } if (Object(userCfg) !== userCfg) { fse.removeSync(this.#tempFile) const msg = 'The quasar.config file does not default exports an Object.' if (failOnError === true) { fatal(msg, 'FAIL') } warn(msg + ' Please fix it.\n') return } fse.removeSync(this.#tempFile) const { appPaths } = this.#ctx const rawQuasarConf = merge( { ctx: this.#ctx, boot: [], css: [], extras: [], animations: [], framework: { components: [], directives: [], plugins: [], config: {} }, sourceFiles: {}, bin: {}, htmlVariables: {}, devServer: { fs: {} }, build: { target: {}, viteVuePluginOptions: {}, vitePlugins: [], env: {}, rawDefine: {}, envFiles: [], resolve: {}, htmlMinifyOptions: {} }, ssr: { middlewares: [] }, pwa: {}, electron: { preloadScripts: [], unPackagedInstallParams: [], packager: {}, builder: {} }, cordova: {}, capacitor: { capacitorCliPreparationParams: [] }, bex: { extraScripts: [] } }, userCfg ) const metaConf = { debugging: this.#ctx.dev === true || this.#ctx.debug === true, needsAppMountHook: false, vueDevtools: false, versions: { ...this.#versions }, // used by entry templates css: { ...this.#cssVariables } } if (rawQuasarConf.animations === 'all') { rawQuasarConf.animations = await this.#ctx.cacheProxy.getModule('animations') } try { await this.#ctx.appExt.runAppExtensionHook( 'extendQuasarConf', async hook => { log( `Extension(${hook.api.extId}): Extending quasar.config file configuration...` ) await hook.fn(rawQuasarConf, hook.api) } ) } catch (e) { console.log() console.error(e) if (failOnError === true) { fatal('One of your installed App Extensions failed to run', 'FAIL') } warn('One of your installed App Extensions failed to run.\n') return } const cfg = { ...rawQuasarConf, metaConf } // we need to know if using SSR + PWA immediately if (this.#ctx.mode.ssr) { cfg.ssr = merge( { pwa: false, pwaOfflineHtmlFilename: 'offline.html', manualStoreHydration: false, manualPostHydrationTrigger: false, prodPort: 3000 // gets superseded in production by an eventual process.env.PORT }, cfg.ssr ) } if (this.#ctx.dev) { if (this.#opts.host) { cfg.devServer.host = this.#opts.host } else if (!cfg.devServer.host) { cfg.devServer.host = '0.0.0.0' } if (this.#opts.port) { cfg.devServer.port = this.#opts.port tip( 'You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues' ) } else if (!cfg.devServer.port) { cfg.devServer.port = defaultPortMapping[this.#ctx.modeName] + (this.#ctx.mode.ssr === true && cfg.ssr.pwa === true ? 50 : 0) } else { tip( 'You (or an AE) specified an explicit quasar.config file > devServer > port. It is recommended to use' + ' a different devServer > port for each Quasar mode to avoid browser cache issues.' + ' Example: ctx.mode.ssr ? 9100 : ...' ) } if ( this.#address && this.#address.from.host === cfg.devServer.host && this.#address.from.port === cfg.devServer.port ) { cfg.devServer.host = this.#address.to.host cfg.devServer.port = this.#address.to.port } else { const addr = { host: cfg.devServer.host, port: cfg.devServer.port } const to = this.#opts.verifyAddress === true ? await onAddress(addr, this.#ctx.modeName) : addr // if network error while running if (to === null) { const msg = 'Network error encountered while following the quasar.config file host/port config.' if (failOnError === true) { fatal(msg, 'FAIL') } warn(msg + ' Reconfigure and save the file again.\n') return } cfg.devServer = merge({ open: true }, cfg.devServer, to) this.#address = { from: addr, to: { host: cfg.devServer.host, port: cfg.devServer.port } } } } if (cfg.css.length > 0) { cfg.css = cfg.css .filter(_ => _) .map(parseAssetProperty('src/css')) .filter(asset => asset.path) .filter(uniquePathFilter) } if (cfg.boot.length > 0) { cfg.boot = cfg.boot .filter(_ => _) .map(parseAssetProperty('boot')) .filter(asset => asset.path) .filter(uniquePathFilter) } if (cfg.extras.length > 0) { cfg.extras = getUniqueArray(cfg.extras) } if (cfg.animations.length > 0) { cfg.animations = getUniqueArray(cfg.animations) } if ( !['kebab', 'pascal', 'combined'].includes( cfg.framework.autoImportComponentCase ) ) { cfg.framework.autoImportComponentCase = 'kebab' } // special case where a component can be designated for a framework > config prop const { config } = cfg.framework if (config.loading) { const { spinner } = config.loading if (quasarComponentRE.test(spinner)) { cfg.framework.components.push(spinner) } } if (config.notify) { const { spinner } = config.notify if (quasarComponentRE.test(spinner)) { cfg.framework.components.push(spinner) } } cfg.framework.components = getUniqueArray(cfg.framework.components) cfg.framework.directives = getUniqueArray(cfg.framework.directives) cfg.framework.plugins = getUniqueArray(cfg.framework.plugins) const { lang, iconSet } = cfg.framework if (lang !== void 0) { cfg.framework.lang = formatQuasarAssetPath(lang, 'lang') } if (iconSet !== void 0) { cfg.framework.iconSet = formatQuasarAssetPath(iconSet, 'icon-set') } Object.assign(cfg.metaConf, { hasLoadingBarPlugin: cfg.framework.plugins.includes('LoadingBar'), hasMetaPlugin: cfg.framework.plugins.includes('Meta') }) cfg.build = merge( { viteVuePluginOptions: { isProduction: this.#ctx.prod === true, template: { isProd: this.#ctx.prod === true, transformAssetUrls } }, vueOptionsAPI: true, vueRouterMode: 'hash', minify: cfg.metaConf.debugging !== true && (this.#ctx.mode.bex !== true || cfg.bex.minify === true), sourcemap: cfg.metaConf.debugging === true, useFilenameHashes: true, polyfillModulePreload: false, distDir: join('dist', this.#ctx.modeName), htmlMinifyOptions: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true, collapseBooleanAttributes: true, removeScriptTypeAttributes: true // more options: // https://github.com/terser/html-minifier-terser?tab=readme-ov-file#options-quick-reference }, rawDefine: { // vue __VUE_OPTIONS_API__: cfg.build.vueOptionsAPI !== false, __VUE_PROD_DEVTOOLS__: cfg.metaConf.debugging, __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: cfg.metaConf.debugging, // Vue 3.4+ // vue-i18n __VUE_I18N_FULL_INSTALL__: true, __VUE_I18N_LEGACY_API__: true, __VUE_I18N_PROD_DEVTOOLS__: cfg.metaConf.debugging, __INTLIFY_PROD_DEVTOOLS__: cfg.metaConf.debugging }, alias: { '#q-app': '@quasar/app-vite', src: appPaths.srcDir, app: appPaths.appDir, components: appPaths.resolve.src('components'), layouts: appPaths.resolve.src('layouts'), pages: appPaths.resolve.src('pages'), assets: appPaths.resolve.src('assets'), boot: appPaths.resolve.src('boot'), stores: appPaths.resolve.src('stores') }, typescript: { strict: false, vueShim: false } }, cfg.build ) if (!cfg.build.target.browser) { cfg.build.target.browser = BASELINE_WIDELY_AVAILABLE_TARGET_STRING } if (!cfg.build.target.node) { cfg.build.target.node = 'node22' } if (this.#ctx.mode.ssr) { cfg.build.vueRouterMode = 'history' } else if ( this.#ctx.mode.cordova || this.#ctx.mode.capacitor || this.#ctx.mode.electron || this.#ctx.mode.bex ) { cfg.build.vueRouterMode = 'hash' } if (this.#ctx.mode.bex) { // we want to differentiate the folder // otherwise we can't run dev and build simultaneously; // it's better regardless because it's easier to select the dev folder // when loading the browser extension const name = basename(cfg.build.distDir) cfg.build.distDir = join( dirname(cfg.build.distDir), `bex-${this.#ctx.targetName}${name !== 'bex' ? `-${name}` : ''}${this.#ctx.dev ? '--dev' : ''}` ) if (this.#ctx.dev) { // we need this token to remain the same across quasar.config changes + server restarts cfg.metaConf.bexWsToken = createWsToken() } } if (!isAbsolute(cfg.build.distDir)) { cfg.build.distDir = appPaths.resolve.app(cfg.build.distDir) } cfg.build.publicPath = cfg.build.publicPath && ['spa', 'pwa', 'ssr'].includes(this.#ctx.modeName) ? formatPublicPath(cfg.build.publicPath) : ['capacitor', 'cordova', 'electron', 'bex'].includes( this.#ctx.modeName ) ? '' : '/' /* careful if you configure the following; make sure that you really know what you are doing */ cfg.build.vueRouterBase = cfg.build.vueRouterBase !== void 0 ? cfg.build.vueRouterBase : formatRouterBase(cfg.build.publicPath) // When adding new props here be sure to update // all impacted devserver diffs (look for this.registerDiff() calls) cfg.sourceFiles = merge( { // For esbuild JS/TS entry points, make sure this // gets appPaths.resolve.app() applied to it further down the line rootComponent: 'src/App.vue', router: 'src/router/index', store: `src/${this.#storeProvider.pathKey}/index`, pwaRegisterServiceWorker: 'src-pwa/register-service-worker', pwaServiceWorker: 'src-pwa/custom-service-worker', pwaManifestFile: 'src-pwa/manifest.json', electronMain: 'src-electron/electron-main', bexManifestFile: 'src-bex/manifest.json' }, cfg.sourceFiles ) if (appFilesValidations(appPaths) === false) { if (failOnError === true) { fatal('Files validation not passed successfully', 'FAIL') } warn('Files validation not passed successfully. Please fix the issues.\n') return } // do we have a store? const storePath = appPaths.resolve.app(cfg.sourceFiles.store) Object.assign(cfg.metaConf, { hasStore: resolveExtension(storePath) !== void 0, storePackage: this.#storeProvider.name }) // make sure we have preFetch in config cfg.preFetch = cfg.preFetch || false if ( this.#ctx.mode.capacitor & (cfg.capacitor.capacitorCliPreparationParams.length === 0) ) { cfg.capacitor.capacitorCliPreparationParams = [ 'sync', this.#ctx.targetName ] } if (this.#ctx.mode.ssr) { if (cfg.ssr.manualPostHydrationTrigger !== true) { cfg.metaConf.needsAppMountHook = true } if (cfg.ssr.middlewares.length > 0) { cfg.ssr.middlewares = cfg.ssr.middlewares .filter(_ => _) .map(parseAssetProperty('app/src-ssr/middlewares')) .filter(asset => asset.path) .filter(uniquePathFilter) } if (cfg.ssr.pwa === true) { // install pwa mode if it's missing const { addMode } = await import('../lib/modes/pwa/pwa-installation.js') await addMode({ ctx: this.#ctx, silent: true }) } this.#ctx.mode.pwa = cfg.ctx.mode.pwa = cfg.ssr.pwa === true } if (this.#ctx.dev) { if ( this.#ctx.vueDevtools === true || cfg.devServer.vueDevtools === true ) { if (this.#vueDevtools === void 0) { const host = localHostList.includes(cfg.devServer.host.toLowerCase()) ? 'localhost' : cfg.devServer.host this.#vueDevtools = { host, port: await findClosestOpenPort(11111, '0.0.0.0') } } cfg.metaConf.vueDevtools = { ...this.#vueDevtools } } if (this.#ctx.mode.electron || this.#ctx.mode.bex) { cfg.devServer.https = false } else if ( cfg.devServer.open && !this.#ctx.mode.cordova && !this.#ctx.mode.capacitor ) { cfg.metaConf.openBrowser = !isMinimalTerminal ? cfg.devServer.open : false } delete cfg.devServer.open } if (this.#ctx.dev) { if (cfg.devServer.https === true) { const { getCertificate } = await import('@quasar/ssl-certificate') const sslCertificate = await getCertificate({ log, fatal }) cfg.devServer.https = { key: sslCertificate, cert: sslCertificate } } else if (Object(cfg.devServer.https) === cfg.devServer.https) { const { https } = cfg.devServer // we now check if config is specifying a file path // and we actually read the contents so we can later supply correct // params to the node HTTPS server ;['ca', 'pfx', 'key', 'cert'].forEach(prop => { if (typeof https[prop] === 'string') { try { https[prop] = readFileSync(https[prop]) } catch (e) { console.error(e) console.log() delete https[prop] warn( `The devServer.https.${prop} file could not be read. Removed the config.` ) } } }) } } if (this.#ctx.mode.pwa) { cfg.pwa = merge( { workboxMode: 'GenerateSW', injectPwaMetaTags: true, swFilename: 'sw.js', // should be .js (as it's the distribution file, not the input file) manifestFilename: 'manifest.json', useCredentialsForManifestTag: false }, cfg.pwa ) if (!['GenerateSW', 'InjectManifest'].includes(cfg.pwa.workboxMode)) { const msg = `Workbox strategy "${cfg.pwa.workboxMode}" is invalid. ` + 'Valid quasar.config file > pwa > workboxMode options are: GenerateSW or InjectManifest.' if (failOnError === true) { fatal(msg, 'FAIL') } warn(msg + ' Please fix it.\n') return } cfg.build.env.SERVICE_WORKER_FILE = `${cfg.build.publicPath}${cfg.pwa.swFilename}` cfg.metaConf.pwaManifestFile = appPaths.resolve.app( cfg.sourceFiles.pwaManifestFile ) cfg.sourceFiles.pwaServiceWorker = appPaths.resolve.app( cfg.sourceFiles.pwaServiceWorker ) } else if (this.#ctx.mode.bex) { cfg.metaConf.bexManifestFile = appPaths.resolve.app( cfg.sourceFiles.bexManifestFile ) } if (this.#ctx.dev) { const getUrl = hostname => `http${cfg.devServer.https ? 's' : ''}://${hostname}:${cfg.devServer.port}${cfg.build.publicPath}` const hostname = cfg.devServer.host === '0.0.0.0' ? 'localhost' : cfg.devServer.host cfg.metaConf.APP_URL = this.#ctx.mode.bex ? 'index.html' : getUrl(hostname) cfg.metaConf.getUrl = getUrl } else if ( this.#ctx.mode.cordova || this.#ctx.mode.capacitor || this.#ctx.mode.bex ) { cfg.metaConf.APP_URL = 'index.html' } Object.assign(cfg.build.env, { NODE_ENV: this.#ctx.prod ? 'production' : 'development', CLIENT: true, SERVER: false, DEV: this.#ctx.dev === true, PROD: this.#ctx.prod === true, DEBUGGING: cfg.metaConf.debugging === true, MODE: this.#ctx.modeName, VUE_ROUTER_MODE: cfg.build.vueRouterMode, VUE_ROUTER_BASE: cfg.build.vueRouterBase }) if ( this.#ctx.mode.bex || this.#ctx.mode.capacitor || this.#ctx.mode.cordova ) { cfg.build.env.TARGET = this.#ctx.targetName } if (cfg.metaConf.APP_URL) { cfg.build.env.APP_URL = cfg.metaConf.APP_URL } // get the env variables from host project env files const { fileEnv, usedEnvFiles, envFromCache } = readFileEnv({ ctx: this.#ctx, quasarConf: cfg }) cfg.metaConf.fileEnv = fileEnv if (envFromCache === false && usedEnvFiles.length !== 0) { log(`Using .env files: ${usedEnvFiles.join(', ')}`) } if (this.#ctx.mode.electron) { cfg.sourceFiles.electronMain = appPaths.resolve.app( cfg.sourceFiles.electronMain ) if (!userCfg.electron?.preloadScripts) { cfg.electron.preloadScripts = ['electron-preload'] } if (this.#ctx.dev) { if (this.#electronInspectPort === void 0) { this.#electronInspectPort = await findClosestOpenPort( userCfg.electron?.inspectPort || 5858, '127.0.0.1' ) } cfg.electron.inspectPort = this.#electronInspectPort } else { const { ensureInstall, getDefaultName } = await this.#ctx.cacheProxy.getModule('electron') const icon = appPaths.resolve.electron('icons/icon.png') const builderIcon = process.platform === 'linux' ? // backward compatible (linux-512x512.png) existsSync(icon) === true ? icon : appPaths.resolve.electron('icons/linux-512x512.png') : appPaths.resolve.electron('icons/icon') cfg.electron = merge( { packager: { asar: true, icon: appPaths.resolve.electron('icons/icon'), overwrite: true }, builder: { appId: 'quasar-app', icon: builderIcon, productName: this.#ctx.pkg.appPkg.productName || this.#ctx.pkg.appPkg.name || 'Quasar App', directories: { buildResources: appPaths.resolve.electron('') } } }, cfg.electron, { packager: { dir: join(cfg.build.distDir, 'UnPackaged'), out: join(cfg.build.distDir, 'Packaged') }, builder: { directories: { app: join(cfg.build.distDir, 'UnPackaged'), output: join(cfg.build.distDir, 'Packaged') } } } ) if (cfg.ctx.bundlerName) { cfg.electron.bundler = cfg.ctx.bundlerName } else if (!cfg.electron.bundler) { cfg.electron.bundler = getDefaultName() } ensureElectronArgv(cfg.electron.bundler, this.#ctx) if (cfg.electron.bundler === 'packager') { if (cfg.ctx.targetName) { cfg.electron.packager.platform = cfg.ctx.targetName } if (cfg.ctx.archName) { cfg.electron.packager.arch = cfg.ctx.archName } } else { cfg.electron.builder = { config: cfg.electron.builder } if ( cfg.ctx.targetName === 'mac' || cfg.ctx.targetName === 'darwin' || cfg.ctx.targetName === 'all' ) { cfg.electron.builder.mac = [] } if (cfg.ctx.targetName === 'linux' || cfg.ctx.targetName === 'all') { cfg.electron.builder.linux = [] } if ( cfg.ctx.targetName === 'win' || cfg.ctx.targetName === 'win32' || cfg.ctx.targetName === 'all' ) { cfg.electron.builder.win = [] } if (cfg.ctx.archName) { cfg.electron.builder[cfg.ctx.archName] = true } if (cfg.ctx.publish) { cfg.electron.builder.publish = cfg.ctx.publish } } ensureInstall(cfg.electron.bundler) } } const entryScriptAbsolutePath = appPaths.resolve.entry('client-entry.js') const entryScriptWebPath = relative( appPaths.appDir, entryScriptAbsolutePath ) cfg.metaConf.entryScript = { absolutePath: entryScriptAbsolutePath, webPath: cfg.build.publicPath + entryScriptWebPath, // publicPath will be handled by Vite middleware: tag: `<script type="module" src="/${entryScriptWebPath}"></script>` } cfg.htmlVariables = merge( { ctx: cfg.ctx, process: { env: cfg.build.env }, productName: escapeHTMLTagContent(this.#ctx.pkg.appPkg.productName), productDescription: escapeHTMLAttribute( this.#ctx.pkg.appPkg.description ) }, cfg.htmlVariables ) if ( this.#ctx.mode.capacitor && cfg.metaConf.versions.capacitorPluginSplashscreen && cfg.capacitor.hideSplashscreen !== false ) { cfg.metaConf.needsAppMountHook = true } return cfg } }