@quasar/app-vite
Version:
Quasar Framework App CLI with Vite
189 lines (155 loc) • 5.47 kB
JavaScript
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*(['"])(.+)(['"])/ig,
(_, 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' })
}