UNPKG

@quasar/app-vite

Version:

Quasar Framework App CLI with Vite

357 lines (309 loc) 9.94 kB
import { quasar as quasarVitePlugin } from '@quasar/vite-plugin' import vueVitePlugin from '@vitejs/plugin-vue' import { mergeConfig } from 'vite' import { merge } from 'webpack-merge' import { cliPkg } from './utils/cli-runtime.js' import { getPackage } from './utils/get-package.js' import { getBuildSystemDefine } from './utils/env.js' import { log, warn, tip } from './utils/logger.js' import { quasarViteIndexHtmlTransformPlugin } from './plugins/vite.index-html-transform.js' import { quasarViteStripFilenameHashesPlugin } from './plugins/vite.strip-filename-hashes.js' const cliPkgDependencies = Object.keys(cliPkg.dependencies || {}) /* Should match Vite's own hard-coded values */ export const BASELINE_WIDELY_AVAILABLE_TARGET_STRING = 'baseline-widely-available' const ESBUILD_BASELINE_WIDELY_AVAILABLE_TARGET = [ 'chrome111', 'edge111', 'firefox114', 'safari16.4', 'ios16.4' ] async function parseVitePlugins(entries, appDir, compileId) { const acc = [] let showTip = false for (const entry of entries) { if (!entry) { // example: // [ // ctx.dev ? [ ... ] : null, // // ... // ] continue } if (Array.isArray(entry) === false) { if (typeof entry === 'function') { showTip = true } acc.push(entry) continue } const [name, pluginOpts = {}, runOpts = { client: true, server: true }] = entry if (compileId === 'vite-ssr-server') { // if it's configured to not run on server, then skip it if (runOpts.server === false) continue } else if (runOpts.client === false) { // if it's configured to not run on client, then skip it continue } if (typeof name === 'function') { acc.push( // protect against the Vite plugin mutating its own options and triggering endless cfg diff loop name(merge({}, pluginOpts)) ) continue } if (Object(name) === name) { acc.push( // protect against the Vite plugin mutating its own options and triggering endless cfg diff loop merge({}, name) ) continue } if (typeof name !== 'string') { warn('quasar.config file > invalid Vite plugin specified: ' + name) warn( "Correct form: [ 'my-vite-plugin-name', { /* pluginOpts */ } ] or [ pluginFn, { /* pluginOpts */ } ]" ) continue } const plugin = await getPackage(name, appDir) if (!plugin) { warn( 'quasar.config file > invalid Vite plugin specified (cannot find it): ' + name ) continue } const pluginFn = plugin.default?.default || // example: vite-plugin-checker plugin.default || plugin acc.push( pluginFn( // protect against the Vite plugin mutating its own options and triggering endless cfg diff loop merge({}, pluginOpts) ) ) } if (showTip === true) { tip( 'If you want changes to quasar.config file > build > vitePlugins to be picked up,' + ' specify them in this form:' + "[ [ 'plugin-name', { /* pluginOpts */ }, { client: true, server: true } ], ... ]" + ' or [ [ pluginFn, { /* pluginOpts */ }, { client: true, server: true } ], ... ]' ) } return acc } function getQuasarVitePluginRunMode(compileId) { if (compileId === 'vite-ssr-client') return 'ssr-client' if (compileId === 'vite-ssr-server') return 'ssr-server' return 'web-client' } export async function createViteConfig(quasarConf, { compileId }) { const { ctx, build, metaConf } = quasarConf const { appPaths } = ctx const cacheDir = appPaths.resolve.cache(compileId) // protect against Vite mutating its own options and triggering endless cfg diff loop const vueVitePluginOptions = merge( compileId !== 'vite-ssr-server' ? {} : { ssr: true, template: { ssr: true } }, build.viteVuePluginOptions ) /** * @type {import('vite').UserConfig} */ const viteConf = { configFile: false, root: appPaths.appDir, base: build.publicPath, publicDir: build.ignorePublicFolder === true ? false : appPaths.publicDir, clearScreen: false, logLevel: 'warn', mode: ctx.dev === true ? 'development' : 'production', cacheDir, define: getBuildSystemDefine({ buildEnv: build.env, buildRawDefine: build.rawDefine, fileEnv: metaConf.fileEnv }), resolve: { alias: { ...build.alias } }, css: { preprocessorOptions: { sass: { silenceDeprecations: ['import', 'global-builtin'] }, scss: { silenceDeprecations: ['import', 'global-builtin'] } } }, build: { target: compileId === 'vite-ssr-server' ? build.target.node : build.target.browser, modulePreload: build.polyfillModulePreload === true ? true : { polyfill: false }, emptyOutDir: false, minify: build.minify, sourcemap: build.sourcemap }, optimizeDeps: { entries: ['index.html'] }, plugins: [ vueVitePlugin(vueVitePluginOptions), quasarVitePlugin({ runMode: getQuasarVitePluginRunMode(compileId), autoImportComponentCase: quasarConf.framework.autoImportComponentCase, autoImportVueExtensions: quasarConf.framework.autoImportVueExtensions, autoImportScriptExtensions: quasarConf.framework.autoImportScriptExtensions, devTreeshaking: quasarConf.framework.devTreeshaking === true, sassVariables: quasarConf.metaConf.css.variablesFile }), ...(await parseVitePlugins(build.vitePlugins, appPaths.appDir, compileId)) ] } if (compileId !== 'vite-ssr-server') { if (ctx.prod === true && quasarConf.build.useFilenameHashes !== true) { viteConf.plugins.push(quasarViteStripFilenameHashesPlugin()) } if (compileId !== 'vite-ssr-client' || quasarConf.ctx.prod === true) { viteConf.plugins.unshift(quasarViteIndexHtmlTransformPlugin(quasarConf)) } } if (ctx.dev) { const warmup = compileId !== 'vite-ssr-server' ? { clientFiles: [quasarConf.metaConf.entryScript.absolutePath] } : {} // protect against Vite (or a Vite plugin) mutating the original // and triggering endless cfg diff loop viteConf.server = merge({ warmup }, quasarConf.devServer) } else { viteConf.build.outDir = build.distDir const analyze = quasarConf.build.analyze if (analyze) { const { visualizer } = await import('rollup-plugin-visualizer') viteConf.plugins.push( visualizer({ open: true, filename: appPaths.resolve.cache('stats.html'), ...(Object(analyze) === analyze ? analyze : {}) }) ) } } return viteConf } export function extendViteConfig(viteConf, quasarConf, invokeParams) { const opts = { isClient: false, isServer: false, ...invokeParams } if (typeof quasarConf.build.extendViteConf === 'function') { const overrides = quasarConf.build.extendViteConf(viteConf, opts) if (overrides) { viteConf = mergeConfig(viteConf, overrides) } } const { appExt } = quasarConf.ctx const promise = appExt.runAppExtensionHook('extendViteConf', async hook => { log(`Extension(${hook.api.extId}): Extending Vite config`) await hook.fn(viteConf, opts, hook.api) }) return promise.then(() => viteConf) } export function createNodeEsbuildConfig(quasarConf, { format }) { const { ctx: { pkg: { appPkg }, cacheProxy } } = quasarConf const externalsList = cacheProxy.getRuntime('externalEsbuildParam', () => [ ...cliPkgDependencies, ...Object.keys(appPkg.dependencies || {}), ...Object.keys(appPkg.devDependencies || {}) ].filter( // the possible imports of '#q-app/wrappers' / '@quasar/app-vite/wrappers' dep => dep !== cliPkg.name ) ) return { platform: 'node', target: quasarConf.build.target.node, format, bundle: true, sourcemap: quasarConf.metaConf.debugging === true ? 'inline' : false, minify: quasarConf.build.minify !== false, alias: { ...quasarConf.build.alias }, resolveExtensions: format === 'esm' ? ['.mjs', '.js', '.cjs', '.ts', '.json'] : ['.cjs', '.js', '.mjs', '.ts', '.json'], // we use a fresh list since this can be tampered with by the user: external: [...externalsList], define: getBuildSystemDefine({ buildEnv: quasarConf.build.env, buildRawDefine: quasarConf.build.rawDefine, fileEnv: quasarConf.metaConf.fileEnv }), plugins: [] } } export function createBrowserEsbuildConfig(quasarConf) { const { browser } = quasarConf.build.target const target = browser === BASELINE_WIDELY_AVAILABLE_TARGET_STRING ? ESBUILD_BASELINE_WIDELY_AVAILABLE_TARGET : browser return { platform: 'browser', target, format: 'iife', bundle: true, sourcemap: quasarConf.metaConf.debugging === true ? 'inline' : false, minify: quasarConf.build.minify !== false, alias: { ...quasarConf.build.alias }, define: getBuildSystemDefine({ buildEnv: quasarConf.build.env, buildRawDefine: quasarConf.build.rawDefine, fileEnv: quasarConf.metaConf.fileEnv }), plugins: [] } } export function extendEsbuildConfig( esbuildConf, quasarConfTarget, ctx, methodName ) { // example: quasarConf.ssr.extendSSRWebserverConf if (typeof quasarConfTarget[methodName] === 'function') { quasarConfTarget[methodName](esbuildConf) } const promise = ctx.appExt.runAppExtensionHook(methodName, async hook => { log(`Extension(${hook.api.extId}): Running "${methodName}(esbuildConf)"`) await hook.fn(esbuildConf, hook.api) }) return promise.then(() => esbuildConf) }