UNPKG

@quasar/app-vite

Version:

Quasar Framework App CLI with Vite

187 lines (153 loc) 5.34 kB
import compileTemplate from 'lodash/template.js' import { minify } from 'html-minifier-terser' const absoluteUrlRE = /^(https?:\/\/|\/|data:)/i const ssrInterpolationsRE = /{{([\s\S]+?)}}/g const htmlStartTagRE = /(<html[^>]*)(>)/i const headStartTagRE = /(<head[^>]*)(>)/i const headEndRE = /(<\/head>)/i const bodyStartTagRE = /(<body[^>]*)(>)/i export const entryPointMarkup = '<!-- quasar:entry-point -->' export const attachMarkup = '<div id="q-app"></div>' function injectPublicPath(html, publicPath) { return html.replace( /(href|src)\s*=\s*(['"])(.+)(['"])/gi, (_, att, pre, val, post) => absoluteUrlRE.test(val.trim()) === true ? `${att}=${pre}${val}${post}` : `${att}=${pre}${publicPath + val}${post}` ) } function injectSsrRuntimeInterpolation(html) { return html .replace(htmlStartTagRE, (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( headStartTagRE, (_, start, end) => `${start}${end}{{ ssrContext._meta.headTags }}` ) .replace( headEndRE, (_, tag) => `{{ ssrContext._meta.endingHeadTags || '' }}${tag}` ) .replace(bodyStartTagRE, (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 }}` }) } function injectVueDevtools(html, { host, port }, nonce = '') { const scripts = `<script${nonce}>window.__VUE_DEVTOOLS_HOST__='${host}';window.__VUE_DEVTOOLS_PORT__='${port}';</script>` + `\n<script src="http://${host}:${port}"></script>` return html.replace(headEndRE, (_, tag) => `${scripts}${tag}`) } export async function transformHtml(template, quasarConf) { const compiled = compileTemplate(template) let html = compiled(quasarConf.htmlVariables) // should be dev only if (quasarConf.metaConf.vueDevtools !== false) { html = injectVueDevtools(html, quasarConf.metaConf.vueDevtools) } html = html.replace( entryPointMarkup, (quasarConf.ctx.mode.ssr === true ? entryPointMarkup : attachMarkup) + quasarConf.metaConf.entryScript.tag ) // publicPath will be handled by Vite middleware // if src/href are not relative, which is what we need if (quasarConf.build.publicPath) { html = injectPublicPath(html, '/') } if (quasarConf.ctx.mode.ssr !== true && quasarConf.build.minify !== false) { html = await minify(html, quasarConf.build.htmlMinifyOptions) } return html } /** * Used by production SSR only. * Gets index.html generated content as param. */ export async function transformProdSsrPwaOfflineHtml(html, quasarConf) { html = html.replace(entryPointMarkup, attachMarkup) if (quasarConf.build.minify !== false) { html = await minify(html, quasarConf.build.htmlMinifyOptions) } return html } /** * Used by dev SSR only * * const fn = getDevSsrTemplateFn(indexHtmlFileContent, quasarConf) * * // ...at runtime: * let html = fn(ssrContext) * html = await vite.transformIndexHtml(html) * html = html.replace('<!-- quasar:entry-point -->', '<div id="q-app">...</div>') */ export function getDevSsrTemplateFn(template, quasarConf) { const compiled = compileTemplate(template) let html = compiled(quasarConf.htmlVariables) // publicPath will be handled by Vite middleware // if src/href are not relative, which is what we need html = injectPublicPath(html, '/') html = injectSsrRuntimeInterpolation(html) if (quasarConf.metaConf.vueDevtools !== false) { html = injectVueDevtools( html, quasarConf.metaConf.vueDevtools, "{{ ssrContext.nonce ? ' nonce=\"' + ssrContext.nonce + '\"' : '' }}" ) } html = html.replace( entryPointMarkup, `${entryPointMarkup}${quasarConf.metaConf.entryScript.tag}` ) return compileTemplate(html, { interpolate: ssrInterpolationsRE, variable: 'ssrContext' }) } /** * Used by production SSR only * * const viteHtmlContent = // ...vite client generated index.html * // which went through transformHtml() already * * const fn = await getProdSsrTemplateFn(viteHtmlContent, quasarConf) * * // ... at runtime: * const html = fn(ssrContext) */ export async function getProdSsrTemplateFn(viteHtmlContent, quasarConf) { let html = injectSsrRuntimeInterpolation(viteHtmlContent) html = html.replace( entryPointMarkup, '<div id="q-app">{{ ssrContext._meta.runtimePageContent }}</div>' ) if (quasarConf.build.minify !== false) { html = await minify(html, { ...quasarConf.build.htmlMinifyOptions, ignoreCustomFragments: [ssrInterpolationsRE] }) } return compileTemplate(html, { interpolate: ssrInterpolationsRE, variable: 'ssrContext' }) }