quasar

Форк
0
/
quasar-config-file.js 
1081 строка · 32.0 Кб
1
import { join, isAbsolute, basename, dirname, relative } from 'node:path'
2
import { pathToFileURL } from 'node:url'
3
import { existsSync, readFileSync } from 'node:fs'
4
import { createRequire } from 'node:module'
5
import fse from 'fs-extra'
6
import { merge } from 'webpack-merge'
7
import debounce from 'lodash/debounce.js'
8
import { build as esBuild, context as esContextBuild } from 'esbuild'
9
import { transformAssetUrls } from '@quasar/vite-plugin'
10

11
import { log, warn, fatal, tip } from './utils/logger.js'
12
import { appFilesValidations } from './utils/app-files-validations.js'
13
import { getPackageMajorVersion } from './utils/get-package-major-version.js'
14
import { resolveExtension } from './utils/resolve-extension.js'
15
import { ensureElectronArgv } from './utils/ensure-argv.js'
16
import { quasarEsbuildInjectReplacementsDefine, quasarEsbuildInjectReplacementsPlugin } from './plugins/esbuild.inject-replacements.js'
17

18
const urlRegex = /^http(s)?:\/\//i
19
import { findClosestOpenPort, localHostList } from './utils/net.js'
20
import { isMinimalTerminal } from './utils/is-minimal-terminal.js'
21
import { readFileEnv } from './utils/env.js'
22

23
const defaultPortMapping = {
24
  spa: 9000,
25
  ssr: 9100, // 9150 for SSR + PWA
26
  pwa: 9200,
27
  electron: 9300,
28
  cordova: 9400,
29
  capacitor: 9500
30
}
31

32
const quasarComponentRE = /^(Q[A-Z]|q-)/
33
const quasarConfigBanner = `/* eslint-disable */
34
/**
35
 * THIS FILE IS GENERATED AUTOMATICALLY.
36
 * 1. DO NOT edit this file directly as it won't do anything.
37
 * 2. EDIT the original quasar.config file INSTEAD.
38
 * 3. DO NOT git commit this file. It should be ignored.
39
 *
40
 * This file is still here because there was an error in
41
 * the original quasar.config file and this allows you to
42
 * investigate the Node.js stack error.
43
 *
44
 * After you fix the original file, this file will be
45
 * deleted automatically.
46
 **/
47
`
48

49
function escapeHTMLTagContent (str) {
50
  return str ? str.replace(/[<>]/g, '') : ''
51
}
52
function escapeHTMLAttribute (str) {
53
  return str ? str.replace(/\"/g, '') : ''
54
}
55

56
function formatPublicPath (publicPath) {
57
  if (!publicPath) {
58
    return '/'
59
  }
60

61
  if (!publicPath.endsWith('/')) {
62
    publicPath = `${ publicPath }/`
63
  }
64

65
  if (urlRegex.test(publicPath) === true) {
66
    return publicPath
67
  }
68

69
  if (!publicPath.startsWith('/')) {
70
    publicPath = `/${ publicPath }`
71
  }
72

73
  return publicPath
74
}
75

76
function formatRouterBase (publicPath) {
77
  if (!publicPath || !publicPath.startsWith('http')) {
78
    return publicPath
79
  }
80

81
  const match = publicPath.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/)
82
  return formatPublicPath(match[ 5 ] || '')
83
}
84

85
function parseAssetProperty (prefix) {
86
  return asset => {
87
    if (typeof asset === 'string') {
88
      return {
89
        path: asset[ 0 ] === '~' ? asset.substring(1) : prefix + `/${ asset }`
90
      }
91
    }
92

93
    return {
94
      ...asset,
95
      path: typeof asset.path === 'string'
96
        ? (asset.path[ 0 ] === '~' ? asset.path.substring(1) : prefix + `/${ asset.path }`)
97
        : asset.path
98
    }
99
  }
100
}
101

102
function getUniqueArray (original) {
103
  return Array.from(new Set(original))
104
}
105

106
function uniquePathFilter (value, index, self) {
107
  return self.map(obj => obj.path).indexOf(value.path) === index
108
}
109

110
let cachedExternalHost, addressRunning = false
111

112
async function onAddress ({ host, port }, mode) {
113
  if (
114
    [ 'cordova', 'capacitor' ].includes(mode)
115
    && (!host || localHostList.includes(host.toLowerCase()))
116
  ) {
117
    if (cachedExternalHost) {
118
      host = cachedExternalHost
119
    }
120
    else {
121
      const { getExternalIP } = await import('./utils/get-external-ip.js')
122
      host = await getExternalIP()
123
      cachedExternalHost = host
124
    }
125
  }
126

127
  try {
128
    const openPort = await findClosestOpenPort(port, host)
129
    if (port !== openPort) {
130
      warn()
131
      warn(`️️Setting port to closest one available: ${ openPort }`)
132
      warn()
133

134
      port = openPort
135
    }
136
  }
137
  catch (e) {
138
    warn()
139

140
    if (e.message === 'ERROR_NETWORK_PORT_NOT_AVAIL') {
141
      warn('Could not find an open port. Please configure a lower one to start searching with.')
142
    }
143
    else if (e.message === 'ERROR_NETWORK_ADDRESS_NOT_AVAIL') {
144
      warn('Invalid host specified. No network address matches. Please specify another one.')
145
    }
146
    else {
147
      warn('Unknown network error occurred')
148
      console.error(e)
149
    }
150

151
    warn()
152

153
    if (addressRunning === false) {
154
      process.exit(1)
155
    }
156

157
    return null
158
  }
159

160
  addressRunning = true
161
  return { host, port }
162
}
163

164
export class QuasarConfigFile {
165
  #ctx
166
  #opts
167
  #versions = {}
168
  #address
169
  #isWatching = false
170

171
  #require
172
  #tempFile
173

174
  #cssVariables
175
  #storeProvider
176
  #vueDevtools
177
  #electronInspectPort
178

179
  constructor ({ ctx, host, port, verifyAddress, watch }) {
180
    this.#ctx = ctx
181
    this.#opts = { host, port, verifyAddress }
182

183
    if (watch !== void 0) {
184
      this.#opts.watch = debounce(watch, 550)
185
    }
186

187
    const { appPaths } = ctx
188

189
    this.#require = appPaths.quasarConfigOutputFormat === 'cjs'
190
      ? createRequire(import.meta.url)
191
      : () => {}
192

193
    const quasarConfigFileExtension = appPaths.quasarConfigOutputFormat === 'esm' ? 'mjs' : appPaths.quasarConfigOutputFormat
194

195
    // if filename syntax gets changed, then also update the "clean" cmd
196
    this.#tempFile = `${ appPaths.quasarConfigFilename }.temporary.compiled.${ Date.now() }.${ quasarConfigFileExtension }`
197

198
    log(`Using ${ basename(appPaths.quasarConfigFilename) } in "${ appPaths.quasarConfigInputFormat }" format`)
199
  }
200

201
  async init () {
202
    const { appPaths, cacheProxy, appExt } = this.#ctx
203

204
    this.#cssVariables = await cacheProxy.getModule('cssVariables')
205
    this.#storeProvider = await cacheProxy.getModule('storeProvider')
206

207
    await appExt.registerAppExtensions()
208

209
    if (this.#ctx.mode.pwa) {
210
      // Enable this when workbox bumps version (as of writing these lines, we're handling v6 & v7)
211
      // this.#versions.workbox = getPackageMajorVersion('workbox-build', appPaths.appDir)
212
    }
213
    else if (this.#ctx.mode.capacitor) {
214
      const { capVersion } = await cacheProxy.getModule('capCli')
215

216
      const getCapPluginVersion = capVersion <= 2
217
        ? () => true
218
        : name => {
219
          const version = getPackageMajorVersion(name, appPaths.capacitorDir)
220
          return version === void 0
221
            ? false
222
            : version || true
223
        }
224

225
      Object.assign(this.#versions, {
226
        capacitor: capVersion,
227
        capacitorPluginApp: getCapPluginVersion('@capacitor/app'),
228
        capacitorPluginSplashscreen: getCapPluginVersion('@capacitor/splash-screen')
229
      })
230
    }
231
  }
232

233
  read () {
234
    const esbuildConfig = this.#createEsbuildConfig()
235
    return this.#opts.watch !== void 0
236
      ? this.#buildAndWatch(esbuildConfig)
237
      : this.#build(esbuildConfig)
238
  }
239

240
  // start watching for changes
241
  watch () {
242
    this.#isWatching = true
243
  }
244

245
  #createEsbuildConfig () {
246
    const { appPaths } = this.#ctx
247

248
    return {
249
      platform: 'node',
250
      format: appPaths.quasarConfigOutputFormat,
251
      bundle: true,
252
      packages: 'external',
253
      alias: {
254
        'quasar/wrappers': appPaths.quasarConfigOutputFormat === 'esm' ? 'quasar/wrappers/index.mjs' : 'quasar/wrappers/index.js'
255
      },
256
      banner: {
257
        js: quasarConfigBanner
258
      },
259
      define: quasarEsbuildInjectReplacementsDefine,
260
      resolveExtensions: [ appPaths.quasarConfigOutputFormat === 'esm' ? '.mjs' : '.cjs', '.js', '.mts', '.ts', '.json' ],
261
      entryPoints: [ appPaths.quasarConfigFilename ],
262
      outfile: this.#tempFile,
263
      plugins: [ quasarEsbuildInjectReplacementsPlugin ]
264
    }
265
  }
266

267
  async #build (esbuildConfig) {
268
    try {
269
      await esBuild(esbuildConfig)
270
    }
271
    catch (e) {
272
      fse.removeSync(this.#tempFile)
273
      console.log()
274
      console.error(e)
275
      fatal('Could not compile the quasar.config file because it has errors.', 'FAIL')
276
    }
277

278
    let quasarConfigFn
279
    try {
280
      const fnResult = await import(
281
        pathToFileURL(this.#tempFile)
282
      )
283

284
      quasarConfigFn = fnResult.default || fnResult
285
    }
286
    catch (e) {
287
      console.log()
288
      console.error(e)
289
      fatal(
290
        'The quasar.config file has runtime errors. Please check the Node.js stack above against the'
291
        + ` temporarily created ${ basename(this.#tempFile) } file, fix the original file`
292
        + ' then DELETE the temporary one ("quasar clean --qconf" can be used).',
293
        'FAIL'
294
      )
295
    }
296

297
    return this.#computeConfig(quasarConfigFn, true)
298
  }
299

300
  async #buildAndWatch (esbuildConfig) {
301
    let firstBuildIsDone
302

303
    const { appPaths } = this.#ctx
304
    const { updateAppPackageJson } = this.#ctx.pkg
305
    const tempFile = this.#tempFile
306

307
    esbuildConfig.plugins.push({
308
      name: 'quasar:watcher',
309
      setup: build => {
310
        let isFirst = true
311

312
        build.onStart(() => {
313
          if (isFirst === false) {
314
            log()
315
            log('The quasar.config file (or its dependencies) changed. Reading it again...')
316
            updateAppPackageJson()
317
          }
318
        })
319

320
        build.onEnd(async result => {
321
          if (isFirst === false && this.#isWatching === false) {
322
            // not ready yet; watch() has not been issued yet
323
            return
324
          }
325

326
          if (result.errors.length !== 0) {
327
            fse.removeSync(tempFile)
328

329
            const msg = 'Could not compile the quasar.config file because it has errors.'
330

331
            if (isFirst === true) {
332
              fatal(msg, 'FAIL')
333
            }
334

335
            warn(msg + ' Please fix them.\n')
336
            return
337
          }
338

339
          let quasarConfigFn
340

341
          // ensure we grab the latest version
342
          if (appPaths.quasarConfigOutputFormat === 'cjs') {
343
            delete this.#require.cache[ tempFile ]
344
          }
345

346
          try {
347
            const result = appPaths.quasarConfigOutputFormat === 'esm'
348
              ? await import(pathToFileURL(tempFile) + '?t=' + Date.now()) // we also need to cache bust it, hence the ?t= param
349
              : this.#require(tempFile)
350

351
            quasarConfigFn = result.default || result
352
          }
353
          catch (e) {
354
            // free up memory immediately
355
            if (appPaths.quasarConfigOutputFormat === 'cjs') {
356
              delete this.#require.cache[ tempFile ]
357
            }
358

359
            console.log()
360
            console.error(e)
361

362
            const msg = 'Importing quasar.config file results in error. Please check the'
363
              + ` Node.js stack above against the temporarily created ${ basename(tempFile) } file`
364
              + ' and fix the original file then DELETE the temporary one ("quasar clean --qconf" can be used).'
365

366
            if (isFirst === true) {
367
              fatal(msg, 'FAIL')
368
            }
369

370
            warn(msg + '\n')
371
            return
372
          }
373

374
          // free up memory immediately
375
          if (appPaths.quasarConfigOutputFormat === 'cjs') {
376
            delete this.#require.cache[ tempFile ]
377
          }
378

379
          const quasarConf = await this.#computeConfig(quasarConfigFn, isFirst)
380

381
          if (quasarConf === void 0) {
382
            return
383
          }
384

385
          if (isFirst === true) {
386
            isFirst = false
387
            firstBuildIsDone(quasarConf)
388
            return
389
          }
390

391
          log('Scheduled to apply quasar.config changes in 550ms')
392
          this.#opts.watch(quasarConf)
393
        })
394
      }
395
    })
396

397
    const esbuildCtx = await esContextBuild(esbuildConfig)
398
    await esbuildCtx.watch()
399

400
    return new Promise(res => { // eslint-disable-line promise/param-names
401
      firstBuildIsDone = res
402
    })
403
  }
404

405
  // return void 0 if it encounters errors
406
  // and quasarConf otherwise
407
  async #computeConfig (quasarConfigFn, failOnError) {
408
    if (typeof quasarConfigFn !== 'function') {
409
      fse.removeSync(this.#tempFile)
410

411
      const msg = 'The default export value of the quasar.config file is not a function.'
412

413
      if (failOnError === true) {
414
        fatal(msg, 'FAIL')
415
      }
416

417
      warn(msg + ' Please fix it.\n')
418
      return
419
    }
420

421
    let userCfg
422

423
    try {
424
      userCfg = await quasarConfigFn(this.#ctx)
425
    }
426
    catch (e) {
427
      console.log()
428
      console.error(e)
429

430
      const msg = 'The quasar.config file has runtime errors.'
431
        + ' Please check the Node.js stack above against the'
432
        + ` temporarily created ${ basename(this.#tempFile) } file`
433
        + ' then DELETE it ("quasar clean --qconf" can be used).'
434

435
      if (failOnError === true) {
436
        fatal(msg, 'FAIL')
437
      }
438

439
      warn(msg + ' Please fix the errors in the original file.\n')
440
      return
441
    }
442

443
    if (Object(userCfg) !== userCfg) {
444
      fse.removeSync(this.#tempFile)
445

446
      const msg = 'The quasar.config file does not default exports an Object.'
447

448
      if (failOnError === true) {
449
        fatal(msg, 'FAIL')
450
      }
451

452
      warn(msg + ' Please fix it.\n')
453
      return
454
    }
455

456
    fse.removeSync(this.#tempFile)
457

458
    const { appPaths } = this.#ctx
459

460
    const rawQuasarConf = merge({
461
      ctx: this.#ctx,
462

463
      boot: [],
464
      css: [],
465
      extras: [],
466
      animations: [],
467

468
      framework: {
469
        components: [],
470
        directives: [],
471
        plugins: [],
472
        config: {}
473
      },
474

475
      sourceFiles: {},
476
      bin: {},
477
      htmlVariables: {},
478

479
      devServer: {
480
        fs: {}
481
      },
482

483
      build: {
484
        target: {},
485
        viteVuePluginOptions: {},
486
        vitePlugins: [],
487
        env: {},
488
        rawDefine: {},
489
        envFiles: [],
490
        resolve: {},
491
        htmlMinifyOptions: {}
492
      },
493

494
      ssr: {
495
        middlewares: []
496
      },
497
      pwa: {},
498
      electron: {
499
        preloadScripts: [],
500
        unPackagedInstallParams: [],
501
        packager: {},
502
        builder: {}
503
      },
504
      cordova: {},
505
      capacitor: {
506
        capacitorCliPreparationParams: []
507
      },
508
      bex: {
509
        contentScripts: []
510
      }
511
    }, userCfg)
512

513
    const metaConf = {
514
      debugging: this.#ctx.dev === true || this.#ctx.debug === true,
515
      needsAppMountHook: false,
516
      vueDevtools: false,
517
      versions: { ...this.#versions }, // used by entry templates
518
      css: { ...this.#cssVariables }
519
    }
520

521
    if (rawQuasarConf.animations === 'all') {
522
      rawQuasarConf.animations = await this.#ctx.cacheProxy.getModule('animations')
523
    }
524

525
    try {
526
      await this.#ctx.appExt.runAppExtensionHook('extendQuasarConf', async hook => {
527
        log(`Extension(${ hook.api.extId }): Extending quasar.config file configuration...`)
528
        await hook.fn(rawQuasarConf, hook.api)
529
      })
530
    }
531
    catch (e) {
532
      console.log()
533
      console.error(e)
534

535
      if (failOnError === true) {
536
        fatal('One of your installed App Extensions failed to run', 'FAIL')
537
      }
538

539
      warn('One of your installed App Extensions failed to run.\n')
540
      return
541
    }
542

543
    const cfg = {
544
      ...rawQuasarConf,
545
      metaConf
546
    }
547

548
    // we need to know if using SSR + PWA immediately
549
    if (this.#ctx.mode.ssr) {
550
      cfg.ssr = merge({
551
        pwa: false,
552
        pwaOfflineHtmlFilename: 'offline.html',
553
        manualStoreHydration: false,
554
        manualPostHydrationTrigger: false,
555
        prodPort: 3000 // gets superseded in production by an eventual process.env.PORT
556
      }, cfg.ssr)
557
    }
558

559
    // if DEV and not BEX mode (BEX does not use a regular devserver)
560
    if (this.#ctx.dev && this.#ctx.mode.bex !== true) {
561
      if (this.#opts.host) {
562
        cfg.devServer.host = this.#opts.host
563
      }
564
      else if (!cfg.devServer.host) {
565
        cfg.devServer.host = '0.0.0.0'
566
      }
567

568
      if (this.#opts.port) {
569
        cfg.devServer.port = this.#opts.port
570
        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')
571
      }
572
      else if (!cfg.devServer.port) {
573
        cfg.devServer.port = defaultPortMapping[ this.#ctx.modeName ]
574
          + (this.#ctx.mode.ssr === true && cfg.ssr.pwa === true ? 50 : 0)
575
      }
576
      else {
577
        tip(
578
          'You (or an AE) specified an explicit quasar.config file > devServer > port. It is recommended to use'
579
          + ' a different devServer > port for each Quasar mode to avoid browser cache issues.'
580
          + ' Example: ctx.mode.ssr ? 9100 : ...'
581
        )
582
      }
583

584
      if (
585
        this.#address
586
        && this.#address.from.host === cfg.devServer.host
587
        && this.#address.from.port === cfg.devServer.port
588
      ) {
589
        cfg.devServer.host = this.#address.to.host
590
        cfg.devServer.port = this.#address.to.port
591
      }
592
      else {
593
        const addr = {
594
          host: cfg.devServer.host,
595
          port: cfg.devServer.port
596
        }
597
        const to = this.#opts.verifyAddress === true
598
          ? await onAddress(addr, this.#ctx.modeName)
599
          : addr
600

601
        // if network error while running
602
        if (to === null) {
603
          const msg = 'Network error encountered while following the quasar.config file host/port config.'
604

605
          if (failOnError === true) {
606
            fatal(msg, 'FAIL')
607
          }
608

609
          warn(msg + ' Reconfigure and save the file again.\n')
610
          return
611
        }
612

613
        cfg.devServer = merge({ open: true }, cfg.devServer, to)
614
        this.#address = {
615
          from: addr,
616
          to: {
617
            host: cfg.devServer.host,
618
            port: cfg.devServer.port
619
          }
620
        }
621
      }
622
    }
623

624
    if (cfg.css.length > 0) {
625
      cfg.css = cfg.css.filter(_ => _)
626
        .map(parseAssetProperty('src/css'))
627
        .filter(asset => asset.path)
628
        .filter(uniquePathFilter)
629
    }
630

631
    if (cfg.boot.length > 0) {
632
      cfg.boot = cfg.boot.filter(_ => _)
633
        .map(parseAssetProperty('boot'))
634
        .filter(asset => asset.path)
635
        .filter(uniquePathFilter)
636
    }
637

638
    if (cfg.extras.length > 0) {
639
      cfg.extras = getUniqueArray(cfg.extras)
640
    }
641

642
    if (cfg.animations.length > 0) {
643
      cfg.animations = getUniqueArray(cfg.animations)
644
    }
645

646
    if (![ 'kebab', 'pascal', 'combined' ].includes(cfg.framework.autoImportComponentCase)) {
647
      cfg.framework.autoImportComponentCase = 'kebab'
648
    }
649

650
    // special case where a component can be designated for a framework > config prop
651
    const { config } = cfg.framework
652

653
    if (config.loading) {
654
      const { spinner } = config.loading
655
      if (quasarComponentRE.test(spinner)) {
656
        cfg.framework.components.push(spinner)
657
      }
658
    }
659

660
    if (config.notify) {
661
      const { spinner } = config.notify
662
      if (quasarComponentRE.test(spinner)) {
663
        cfg.framework.components.push(spinner)
664
      }
665
    }
666

667
    cfg.framework.components = getUniqueArray(cfg.framework.components)
668
    cfg.framework.directives = getUniqueArray(cfg.framework.directives)
669
    cfg.framework.plugins = getUniqueArray(cfg.framework.plugins)
670

671
    Object.assign(cfg.metaConf, {
672
      packageTypeBasedExtension: this.#ctx.pkg.appPkg.type === 'module' ? 'js' : 'mjs',
673
      hasLoadingBarPlugin: cfg.framework.plugins.includes('LoadingBar'),
674
      hasMetaPlugin: cfg.framework.plugins.includes('Meta')
675
    })
676

677
    cfg.build = merge({
678
      viteVuePluginOptions: {
679
        isProduction: this.#ctx.prod === true,
680
        template: {
681
          isProd: this.#ctx.prod === true,
682
          transformAssetUrls
683
        }
684
      },
685
      vueOptionsAPI: true,
686
      vueRouterMode: 'hash',
687

688
      minify: cfg.metaConf.debugging !== true
689
        && (this.#ctx.mode.bex !== true || cfg.bex.minify === true),
690

691
      sourcemap: cfg.metaConf.debugging === true,
692

693
      useFilenameHashes: true,
694
      polyfillModulePreload: false,
695
      distDir: join('dist', this.#ctx.modeName),
696

697
      htmlMinifyOptions: {
698
        removeComments: true,
699
        collapseWhitespace: true,
700
        removeAttributeQuotes: true,
701
        collapseBooleanAttributes: true,
702
        removeScriptTypeAttributes: true
703
        // more options:
704
        // https://github.com/kangax/html-minifier#options-quick-reference
705
      },
706

707
      rawDefine: {
708
        // vue
709
        __VUE_OPTIONS_API__: cfg.build.vueOptionsAPI !== false,
710
        __VUE_PROD_DEVTOOLS__: cfg.metaConf.debugging,
711
        __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: cfg.metaConf.debugging, // Vue 3.4+
712

713
        // vue-i18n
714
        __VUE_I18N_FULL_INSTALL__: true,
715
        __VUE_I18N_LEGACY_API__: true,
716
        __VUE_I18N_PROD_DEVTOOLS__: cfg.metaConf.debugging,
717
        __INTLIFY_PROD_DEVTOOLS__: cfg.metaConf.debugging
718
      },
719

720
      alias: {
721
        src: appPaths.srcDir,
722
        app: appPaths.appDir,
723
        components: appPaths.resolve.src('components'),
724
        layouts: appPaths.resolve.src('layouts'),
725
        pages: appPaths.resolve.src('pages'),
726
        assets: appPaths.resolve.src('assets'),
727
        boot: appPaths.resolve.src('boot'),
728
        stores: appPaths.resolve.src('stores')
729
      }
730
    }, cfg.build)
731

732
    if (!cfg.build.target.browser) {
733
      cfg.build.target.browser = [ 'es2022', 'firefox115', 'chrome115', 'safari14' ]
734
    }
735

736
    if (!cfg.build.target.node) {
737
      cfg.build.target.node = 'node18'
738
    }
739

740
    if (this.#ctx.mode.ssr) {
741
      cfg.build.vueRouterMode = 'history'
742
    }
743
    else if (this.#ctx.mode.cordova || this.#ctx.mode.capacitor || this.#ctx.mode.electron || this.#ctx.mode.bex) {
744
      cfg.build.vueRouterMode = 'hash'
745
    }
746

747
    if (this.#ctx.dev === true && this.#ctx.mode.bex) {
748
      // we want to differentiate the folder
749
      // otherwise we can't run dev and build simultaneously;
750
      // it's better regardless because it's easier to select the dev folder
751
      // when loading the browser extension
752

753
      const name = basename(cfg.build.distDir)
754

755
      cfg.build.distDir = join(
756
        dirname(cfg.build.distDir),
757
        name === 'bex' ? 'bex--dev' : `bex-dev--${ name }`
758
      )
759
    }
760

761
    if (!isAbsolute(cfg.build.distDir)) {
762
      cfg.build.distDir = appPaths.resolve.app(cfg.build.distDir)
763
    }
764

765
    cfg.build.publicPath
766
      = cfg.build.publicPath && [ 'spa', 'pwa', 'ssr' ].includes(this.#ctx.modeName)
767
        ? formatPublicPath(cfg.build.publicPath)
768
        : ([ 'capacitor', 'cordova', 'electron', 'bex' ].includes(this.#ctx.modeName) ? '' : '/')
769

770
    /* careful if you configure the following; make sure that you really know what you are doing */
771
    cfg.build.vueRouterBase = cfg.build.vueRouterBase !== void 0
772
      ? cfg.build.vueRouterBase
773
      : formatRouterBase(cfg.build.publicPath)
774

775
    // when adding new props here be sure to update
776
    // all impacted devserver diffs (look for this.registerDiff() calls)
777
    cfg.sourceFiles = merge({
778
      rootComponent: 'src/App.vue',
779
      router: 'src/router/index',
780
      store: `src/${ this.#storeProvider.pathKey }/index`,
781
      pwaRegisterServiceWorker: 'src-pwa/register-service-worker',
782
      pwaServiceWorker: 'src-pwa/custom-service-worker',
783
      pwaManifestFile: 'src-pwa/manifest.json',
784
      electronMain: 'src-electron/electron-main',
785
      bexManifestFile: 'src-bex/manifest.json'
786
    }, cfg.sourceFiles)
787

788
    if (appFilesValidations(appPaths) === false) {
789
      if (failOnError === true) {
790
        fatal('Files validation not passed successfully', 'FAIL')
791
      }
792

793
      warn('Files validation not passed successfully. Please fix the issues.\n')
794
      return
795
    }
796

797
    // do we have a store?
798
    const storePath = appPaths.resolve.app(cfg.sourceFiles.store)
799
    Object.assign(cfg.metaConf, {
800
      hasStore: resolveExtension(storePath) !== void 0,
801
      storePackage: this.#storeProvider.name
802
    })
803

804
    // make sure we have preFetch in config
805
    cfg.preFetch = cfg.preFetch || false
806

807
    if (this.#ctx.mode.capacitor & cfg.capacitor.capacitorCliPreparationParams.length === 0) {
808
      cfg.capacitor.capacitorCliPreparationParams = [ 'sync', this.#ctx.targetName ]
809
    }
810

811
    if (this.#ctx.dev) {
812
      if (cfg.devServer.https === true) {
813
        const { getCertificate } = await import('@quasar/ssl-certificate')
814
        const sslCertificate = getCertificate({ log, fatal })
815
        cfg.devServer.https = {
816
          key: sslCertificate,
817
          cert: sslCertificate
818
        }
819
      }
820
      else if (Object(cfg.devServer.https) === cfg.devServer.https) {
821
        const { https } = cfg.devServer
822

823
        // we now check if config is specifying a file path
824
        // and we actually read the contents so we can later supply correct
825
        // params to the node HTTPS server
826
        ;[ 'ca', 'pfx', 'key', 'cert' ].forEach(prop => {
827
          if (typeof https[ prop ] === 'string') {
828
            try {
829
              https[ prop ] = readFileSync(https[ prop ])
830
            }
831
            catch (e) {
832
              console.error(e)
833
              console.log()
834
              delete https[ prop ]
835
              warn(`The devServer.https.${ prop } file could not be read. Removed the config.`)
836
            }
837
          }
838
        })
839
      }
840
    }
841

842
    if (this.#ctx.mode.ssr) {
843
      if (cfg.ssr.manualPostHydrationTrigger !== true) {
844
        cfg.metaConf.needsAppMountHook = true
845
      }
846

847
      if (cfg.ssr.middlewares.length > 0) {
848
        cfg.ssr.middlewares = cfg.ssr.middlewares.filter(_ => _)
849
          .map(parseAssetProperty('app/src-ssr/middlewares'))
850
          .filter(asset => asset.path)
851
          .filter(uniquePathFilter)
852
      }
853

854
      if (cfg.ssr.pwa === true) {
855
        // install pwa mode if it's missing
856
        const { addMode } = await import('../lib/modes/pwa/pwa-installation.js')
857
        await addMode({ ctx: this.#ctx, silent: true })
858
      }
859

860
      this.#ctx.mode.pwa = cfg.ctx.mode.pwa = cfg.ssr.pwa === true
861
    }
862

863
    if (this.#ctx.dev) {
864
      if (this.#ctx.vueDevtools === true || cfg.devServer.vueDevtools === true) {
865
        if (this.#vueDevtools === void 0) {
866
          const host = localHostList.includes(cfg.devServer.host.toLowerCase())
867
            ? 'localhost'
868
            : cfg.devServer.host
869

870
          this.#vueDevtools = {
871
            host,
872
            port: await findClosestOpenPort(11111, '0.0.0.0')
873
          }
874
        }
875

876
        cfg.metaConf.vueDevtools = { ...this.#vueDevtools }
877
      }
878

879
      if (this.#ctx.mode.cordova || this.#ctx.mode.capacitor || this.#ctx.mode.electron) {
880
        if (this.#ctx.mode.electron) {
881
          cfg.devServer.https = false
882
        }
883
      }
884
      else if (cfg.devServer.open) {
885
        cfg.metaConf.openBrowser = !isMinimalTerminal
886
          ? cfg.devServer.open
887
          : false
888
      }
889

890
      delete cfg.devServer.open
891
    }
892

893
    if (this.#ctx.mode.pwa) {
894
      cfg.pwa = merge({
895
        workboxMode: 'GenerateSW',
896
        injectPwaMetaTags: true,
897
        swFilename: 'sw.js', // should be .js (as it's the distribution file, not the input file)
898
        manifestFilename: 'manifest.json',
899
        useCredentialsForManifestTag: false
900
      }, cfg.pwa)
901

902
      if (![ 'GenerateSW', 'InjectManifest' ].includes(cfg.pwa.workboxMode)) {
903
        const msg = `Workbox strategy "${ cfg.pwa.workboxMode }" is invalid. `
904
          + 'Valid quasar.config file > pwa > workboxMode options are: GenerateSW or InjectManifest.'
905

906
        if (failOnError === true) {
907
          fatal(msg, 'FAIL')
908
        }
909

910
        warn(msg + ' Please fix it.\n')
911
        return
912
      }
913

914
      cfg.build.env.SERVICE_WORKER_FILE = `${ cfg.build.publicPath }${ cfg.pwa.swFilename }`
915
      cfg.metaConf.pwaManifestFile = appPaths.resolve.app(cfg.sourceFiles.pwaManifestFile)
916

917
      // resolve extension
918
      const swPath = appPaths.resolve.app(cfg.sourceFiles.pwaServiceWorker)
919
      cfg.sourceFiles.pwaServiceWorker = resolveExtension(swPath) || cfg.sourceFiles.pwaServiceWorker
920
    }
921
    else if (this.#ctx.mode.bex) {
922
      cfg.metaConf.bexManifestFile = appPaths.resolve.app(cfg.sourceFiles.bexManifestFile)
923
    }
924

925
    if (this.#ctx.dev) {
926
      const getUrl = hostname => `http${ cfg.devServer.https ? 's' : '' }://${ hostname }:${ cfg.devServer.port }${ cfg.build.publicPath }`
927
      const hostname = cfg.devServer.host === '0.0.0.0'
928
        ? 'localhost'
929
        : cfg.devServer.host
930

931
      cfg.metaConf.APP_URL = getUrl(hostname)
932
      cfg.metaConf.getUrl = getUrl
933
    }
934
    else if (this.#ctx.mode.cordova || this.#ctx.mode.capacitor || this.#ctx.mode.bex) {
935
      cfg.metaConf.APP_URL = 'index.html'
936
    }
937

938
    Object.assign(cfg.build.env, {
939
      NODE_ENV: this.#ctx.prod ? 'production' : 'development',
940
      CLIENT: true,
941
      SERVER: false,
942
      DEV: this.#ctx.dev === true,
943
      PROD: this.#ctx.prod === true,
944
      DEBUGGING: cfg.metaConf.debugging === true,
945
      MODE: this.#ctx.modeName,
946
      VUE_ROUTER_MODE: cfg.build.vueRouterMode,
947
      VUE_ROUTER_BASE: cfg.build.vueRouterBase
948
    })
949

950
    if (cfg.metaConf.APP_URL) {
951
      cfg.build.env.APP_URL = cfg.metaConf.APP_URL
952
    }
953

954
    // get the env variables from host project env files
955
    const { fileEnv, usedEnvFiles, envFromCache } = readFileEnv({
956
      ctx: this.#ctx,
957
      quasarConf: cfg
958
    })
959

960
    cfg.metaConf.fileEnv = fileEnv
961

962
    if (envFromCache === false && usedEnvFiles.length !== 0) {
963
      log(`Using .env files: ${ usedEnvFiles.join(', ') }`)
964
    }
965

966
    if (this.#ctx.mode.electron) {
967
      if (!userCfg.electron?.preloadScripts) {
968
        cfg.electron.preloadScripts = [ 'electron-preload' ]
969
      }
970

971
      if (this.#ctx.dev) {
972
        if (this.#electronInspectPort === void 0) {
973
          this.#electronInspectPort = await findClosestOpenPort(userCfg.electron?.inspectPort || 5858, '127.0.0.1')
974
        }
975

976
        cfg.electron.inspectPort = this.#electronInspectPort
977
      }
978
      else {
979
        const { ensureInstall, getDefaultName } = await this.#ctx.cacheProxy.getModule('electron')
980

981
        const icon = appPaths.resolve.electron('icons/icon.png')
982
        const builderIcon = process.platform === 'linux'
983
          // backward compatible (linux-512x512.png)
984
          ? (existsSync(icon) === true ? icon : appPaths.resolve.electron('icons/linux-512x512.png'))
985
          : appPaths.resolve.electron('icons/icon')
986

987
        cfg.electron = merge({
988
          packager: {
989
            asar: true,
990
            icon: appPaths.resolve.electron('icons/icon'),
991
            overwrite: true
992
          },
993
          builder: {
994
            appId: 'quasar-app',
995
            icon: builderIcon,
996
            productName: this.#ctx.pkg.appPkg.productName || this.#ctx.pkg.appPkg.name || 'Quasar App',
997
            directories: {
998
              buildResources: appPaths.resolve.electron('')
999
            }
1000
          }
1001
        }, cfg.electron, {
1002
          packager: {
1003
            dir: join(cfg.build.distDir, 'UnPackaged'),
1004
            out: join(cfg.build.distDir, 'Packaged')
1005
          },
1006
          builder: {
1007
            directories: {
1008
              app: join(cfg.build.distDir, 'UnPackaged'),
1009
              output: join(cfg.build.distDir, 'Packaged')
1010
            }
1011
          }
1012
        })
1013

1014
        if (cfg.ctx.bundlerName) {
1015
          cfg.electron.bundler = cfg.ctx.bundlerName
1016
        }
1017
        else if (!cfg.electron.bundler) {
1018
          cfg.electron.bundler = getDefaultName()
1019
        }
1020

1021
        ensureElectronArgv(cfg.electron.bundler, this.#ctx)
1022

1023
        if (cfg.electron.bundler === 'packager') {
1024
          if (cfg.ctx.targetName) {
1025
            cfg.electron.packager.platform = cfg.ctx.targetName
1026
          }
1027
          if (cfg.ctx.archName) {
1028
            cfg.electron.packager.arch = cfg.ctx.archName
1029
          }
1030
        }
1031
        else {
1032
          cfg.electron.builder = {
1033
            config: cfg.electron.builder
1034
          }
1035

1036
          if (cfg.ctx.targetName === 'mac' || cfg.ctx.targetName === 'darwin' || cfg.ctx.targetName === 'all') {
1037
            cfg.electron.builder.mac = []
1038
          }
1039

1040
          if (cfg.ctx.targetName === 'linux' || cfg.ctx.targetName === 'all') {
1041
            cfg.electron.builder.linux = []
1042
          }
1043

1044
          if (cfg.ctx.targetName === 'win' || cfg.ctx.targetName === 'win32' || cfg.ctx.targetName === 'all') {
1045
            cfg.electron.builder.win = []
1046
          }
1047

1048
          if (cfg.ctx.archName) {
1049
            cfg.electron.builder[ cfg.ctx.archName ] = true
1050
          }
1051

1052
          if (cfg.ctx.publish) {
1053
            cfg.electron.builder.publish = cfg.ctx.publish
1054
          }
1055
        }
1056

1057
        ensureInstall(cfg.electron.bundler)
1058
      }
1059
    }
1060

1061
    const entryScriptWebPath = relative(appPaths.appDir, appPaths.resolve.entry('client-entry.js')).replaceAll('\\', '/')
1062
    Object.assign(cfg.metaConf, {
1063
      entryScriptWebPath: cfg.build.publicPath + entryScriptWebPath,
1064
      // publicPath will be handled by Vite middleware:
1065
      entryScriptTag: `<script type="module" src="/${ entryScriptWebPath }"></script>`
1066
    })
1067

1068
    cfg.htmlVariables = merge({
1069
      ctx: cfg.ctx,
1070
      process: { env: cfg.build.env },
1071
      productName: escapeHTMLTagContent(this.#ctx.pkg.appPkg.productName),
1072
      productDescription: escapeHTMLAttribute(this.#ctx.pkg.appPkg.description)
1073
    }, cfg.htmlVariables)
1074

1075
    if (this.#ctx.mode.capacitor && cfg.metaConf.versions.capacitorPluginSplashscreen && cfg.capacitor.hideSplashscreen !== false) {
1076
      cfg.metaConf.needsAppMountHook = true
1077
    }
1078

1079
    return cfg
1080
  }
1081
}
1082

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.