import { fileURLToPath } from 'url' import type { OnAppMountedHook, OnTemplateRenderedHook, VitrifyConfig, OnAppCreatedHook } from '../../vitrify-config.js' import type { VitrifyPlugin } from '../index.js' import { findDepPkgJsonPath } from 'vitefu' import { type QuasarComponents, type QuasarDirectives, type QuasarIconSets, type QuasarPlugins, type GlobalQuasarIconMapFn, type QuasarIconSet } from 'quasar' import type { QuasarFonts } from '@quasar/extras' import { QuasarResolver } from 'unplugin-vue-components/resolvers' export interface QuasarPluginOptions { framework: { components?: (keyof QuasarComponents)[] directives?: (keyof QuasarDirectives)[] plugins?: (keyof QuasarPlugins)[] lang?: string iconSet?: QuasarIconSets | QuasarIconSet iconMapFn?: GlobalQuasarIconMapFn } extras?: (QuasarIconSets | QuasarFonts)[] disableSass?: boolean } export const injectSsrContext: OnTemplateRenderedHook = ({ html, ssrContext }) => html .replace(/(]*)(>)/i, (found, start, end) => { let matches matches = found.match(/\sdir\s*=\s*['"]([^'"]*)['"]/i) if (matches) { start = start.replace(matches[0], '') } matches = found.match(/\slang\s*=\s*['"]([^'"]*)['"]/i) if (matches) { start = start.replace(matches[0], '') } return `${start} ${ssrContext?._meta.htmlAttrs || ''} ${end}` }) .replace( /(]*)(>)/i, (_, start, end) => `${start}${end}${ssrContext?._meta.headTags || ''}` ) .replace( /(<\/head>)/i, (_, tag) => `${ssrContext?._meta.resourceStyles || ''}${ ssrContext?._meta.endingHeadTags || '' }${tag}` ) .replace(/(]*)(>)/i, (found, start, end) => { let classes = ssrContext?._meta.bodyClasses || '' const matches = found.match(/\sclass\s*=\s*['"]([^'"]*)['"]/i) if (matches) { if (matches[1].length > 0) { classes += ` ${matches[1]}` } start = start.replace(matches[0], '') } return `${start} class="${classes.trim()}" ${ ssrContext?._meta.bodyAttrs || '' }${end}${ssrContext?._meta.bodyTags || ''}` }) export const QuasarPlugin: VitrifyPlugin = async ({ ssr = false, pwa = false, options }) => { let plugins: string[] = [] const quasarConf: QuasarPluginOptions = options return { plugins: [ { name: 'vite-plugin-quasar-transform', enforce: 'pre', transform: (code, id, options) => { code = code .replaceAll('__QUASAR_SSR__', ssr ? 'true' : 'false') .replaceAll( '__QUASAR_SSR_SERVER__', ssr ? '(import.meta.env.SSR === true)' : 'false' ) .replaceAll( '__QUASAR_SSR_CLIENT__', ssr ? '(import.meta.env.SSR === false)' : 'false' ) .replaceAll( '__QUASAR_SSR_PWA__', ssr && pwa ? '(import.meta.env.SSR === false)' : 'false' ) return code } }, { name: 'vite-plugin-quasar-setup', enforce: 'pre', config: async (config: VitrifyConfig, env): Promise => { const { vitrify: { urls } = {} } = config // if (quasar) quasarConf = quasar if (!quasarConf.framework.lang && config.vitrify?.lang) quasarConf.framework.lang = config.vitrify.lang const globalCss = quasarConf?.extras?.map( (extra) => `@quasar/extras/${extra}/${extra}.css` ) const localPackages = ['@quasar/extras', 'quasar'] // const localPackages: string[] = [] await (async () => { for (const val of localPackages) { const pkgDir = await findDepPkgJsonPath( val, fileURLToPath(config.vitrify!.urls!.app!) ) if (pkgDir) urls!.packages![val] = new URL(`file://${pkgDir}`) } })() const onAppMountedHooks: OnAppMountedHook[] = [ async ({ instance }) => { const { proxy: { $q } } = instance if ($q.onSSRHydrated !== void 0) $q.onSSRHydrated() } ] const onAppCreatedHooks: OnAppCreatedHook[] = [ async ({ app, ssrContext, staticImports }) => { // @ts-expect-error undefined const quasarPlugins = await import('virtual:quasar-plugins') // @ts-expect-error undefined const directives = await import('virtual:quasar-directives') // @ts-expect-error undefined const { default: lang } = await import('virtual:quasar-lang') const { default: iconSet } = await import( // @ts-expect-error undefined 'virtual:quasar-iconSet' ) const { default: iconMapFn } = await import( // @ts-expect-error undefined 'virtual:quasar-iconMapFn' ) const { installQuasar } = await import( // @ts-expect-error undefined 'virtual:quasar' ) app.use( installQuasar, { plugins: quasarPlugins, directives, lang, iconSet, config: { iconMapFn } }, ssrContext ) } ] /** * Importing package.json is problematic */ const version = '?' /** * All components should have been auto-imported */ if (quasarConf?.framework?.plugins) { if (!quasarConf.framework) quasarConf.framework = {} quasarConf.framework.plugins = [ ...new Set(quasarConf.framework.plugins) ] plugins = quasarConf?.framework.plugins } return { vitrify: { urls, globalCss, staticImports: { // quasar: ['Quasar'] }, hooks: { onAppCreated: onAppCreatedHooks, onAppMounted: onAppMountedHooks, onTemplateRendered: [injectSsrContext] }, sass: quasarConf.disableSass ? undefined : { global: ['quasar/src/css/index.sass'] } }, resolve: { alias: [ { find: 'quasar/src/', replacement: fileURLToPath( new URL('./src/', config.vitrify!.urls!.packages!.quasar) ) } ] }, optimizeDeps: { exclude: ['quasar'], include: [ 'quasar/src/components.js', 'quasar/src/plugins.js', 'quasar/src/directives.js', 'quasar/src/composables.js', 'quasar/src/utils.js', 'quasar/src/install-quasar.js' ] }, define: { __DEV__: process.env.NODE_ENV !== 'production' || true, __QUASAR_VERSION__: `'${version}'` }, ssr: { noExternal: ['quasar'] } } } }, { name: 'quasar-virtual-modules', enforce: 'pre', config: async (config, env) => ({ resolve: { alias: [ { find: new RegExp('^quasar$'), replacement: 'virtual:quasar' } // { find: new RegExp('^quasar$'), replacement: 'virtual:quasar' } ] } }), resolveId(id) { switch (id) { case 'virtual:quasar-plugins': return 'virtual:quasar-plugins' case 'virtual:quasar-directives': return 'virtual:quasar-directives' case 'virtual:quasar-lang': return 'virtual:quasar-lang' case 'virtual:quasar-iconSet': return 'virtual:quasar-iconSet' case 'virtual:quasar-iconMapFn': return 'virtual:quasar-iconMapFn' case 'virtual:quasar': return { id: 'virtual:quasar', moduleSideEffects: false } default: return } }, load(id) { if (id === 'virtual:quasar-plugins') { return `export { ${plugins.join(',')} } from 'quasar'` } else if (id === 'virtual:quasar-directives') { return `export * from 'quasar/src/directives.js'` } else if (id === 'virtual:quasar-lang') { return `import lang from 'quasar/lang/${ quasarConf?.framework?.lang || 'en-US' }'; export default lang` } else if (id === 'virtual:quasar-iconSet') { return `${ typeof quasarConf.framework.iconSet === 'string' ? `import iconSet from 'quasar/icon-set/${ quasarConf?.framework.iconSet || 'material-icons' }'; export default iconSet` : `export default ${ quasarConf.framework.iconSet ? JSON.stringify(quasarConf.framework.iconSet) : null }` }` } else if (id === 'virtual:quasar-iconMapFn') { return `export default ${ quasarConf?.framework.iconMapFn?.toString() ?? null }` } else if (id === 'virtual:quasar') { return `export * from 'quasar/src/plugins.js'; export * from 'quasar/src/components.js'; export * from 'quasar/src/composables.js'; export * from 'quasar/src/directives.js'; export * from 'quasar/src/utils.js'; export * from 'quasar/src/composables.js'; export { default as installQuasar } from 'quasar/src/install-quasar.js'` } return null } } ], config: { vitrify: { unpluginVueComponents: { resolvers: [QuasarResolver()] } } } } }