UNPKG

@quasar/app-webpack

Version:

Quasar Framework App CLI with Webpack

204 lines (167 loc) 6.97 kB
const compileTemplate = require('lodash/template.js') const { minify } = require('html-minifier-terser') const HtmlWebpackPlugin = require('html-webpack-plugin') const { HtmlTransformPlugin } = require('../plugins/webpack.html-transform.js') const absoluteUrlRE = /^(https?:\/\/|\/|data:)/i const ssrInterpolationsRE = /{{([\s\S]+?)}}/g const htmlStartTagRE = /(<html[^>]*)(>)/i const headStartTagRE = /(<head[^>]*)(>)/i const headStartRE = /(<head[^>]*>)/i const headEndRE = /(<\/head>)/i const bodyStartTagRE = /(<body[^>]*)(>)/i const bodyStartRE = /(<body[^>]*>)/i const entryPointMarkup = '<!-- quasar:entry-point -->' const attachMarkup = '<div id="q-app"></div>' module.exports.entryPointMarkup = entryPointMarkup module.exports.attachMarkup = attachMarkup 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 }\n{{ 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 injectPwaTags (html, quasarConf) { const { publicPath } = quasarConf.build const { pwaManifest } = quasarConf.htmlVariables const { useCredentialsForManifestTag, injectPwaMetaTags, manifestFilename } = quasarConf.pwa let headTags = `\n<link rel="manifest" href="${ publicPath }${ manifestFilename }"${ useCredentialsForManifestTag === true ? ' crossorigin="use-credentials"' : '' }>` if (injectPwaMetaTags === true) { headTags += (pwaManifest.theme_color !== void 0 ? `\n<meta name="theme-color" content="${ pwaManifest.theme_color }">` + `<link rel="mask-icon" href="${ publicPath }icons/safari-pinned-tab.svg" color="${ pwaManifest.theme_color }">` : '') + '\n<meta name="mobile-web-app-capable" content="yes">' + '\n<meta name="apple-mobile-web-app-status-bar-style" content="default">' + (pwaManifest.name !== void 0 ? `\n<meta name="apple-mobile-web-app-title" content="${ pwaManifest.name }">` : '') + `\n<meta name="msapplication-TileImage" content="${ publicPath }icons/ms-icon-144x144.png">` + '\n<meta name="msapplication-TileColor" content="#000000">' + `\n<link rel="apple-touch-icon" href="${ publicPath }icons/apple-icon-120x120.png">` + `\n<link rel="apple-touch-icon" sizes="152x152" href="${ publicPath }icons/apple-icon-152x152.png">` + `\n<link rel="apple-touch-icon" sizes="167x167" href="${ publicPath }icons/apple-icon-167x167.png">` + `\n<link rel="apple-touch-icon" sizes="180x180" href="${ publicPath }icons/apple-icon-180x180.png">` } else if (typeof injectPwaMetaTags === 'function') { headTags += injectPwaMetaTags({ publicPath, pwaManifest }) } return html.replace( headStartRE, (_, tag) => `${ tag }${ headTags }` ) } async function transformHtml ({ html, quasarConf, renderSsrPwaOffline }) { const { ctx } = quasarConf // should be dev only if (quasarConf.metaConf.vueDevtools !== false) { const { host, port } = quasarConf.metaConf.vueDevtools const nonce = ctx.mode.ssr === true ? '{{ ssrContext.nonce ? \' nonce="\' + ssrContext.nonce + \'"\' : \'\' }}' : '' const scripts = ( `<script${ nonce }>window.__VUE_DEVTOOLS_HOST__='${ host }';window.__VUE_DEVTOOLS_PORT__='${ port }';</script>` + `\n<script src="http://${ host }:${ port }"></script>` ) html = html.replace( headEndRE, (_, tag) => `${ scripts }\n${ tag }` ) } if (ctx.mode.cordova) { html = html.replace( bodyStartRE, (_, tag) => `${ tag }\n<script src="cordova.js"></script>` ) } else if (ctx.mode.pwa) { html = injectPwaTags(html, quasarConf) } html = html.replace( entryPointMarkup, renderSsrPwaOffline !== true && ctx.mode.ssr ? '<div id="q-app">{{ ssrContext._meta.runtimePageContent }}</div>{{ ssrContext._meta.afterRuntimePageContent }}' : attachMarkup ) if (quasarConf.build.publicPath) { html = injectPublicPath(html, quasarConf.build.publicPath) } if (quasarConf.build.minify) { const minifyOpts = { ...quasarConf.build.htmlMinifyOptions } if (ctx.mode.ssr) { minifyOpts.ignoreCustomFragments = [ ssrInterpolationsRE ] } html = await minify(html, minifyOpts) } return html } module.exports.injectWebpackHtml = function injectWebpackHtml (webpackChain, quasarConf, templateParam = quasarConf.htmlVariables) { const { appPaths } = quasarConf.ctx const renderSsrPwaOffline = quasarConf.ctx.mode.ssr && quasarConf.ctx.mode.pwa const filename = renderSsrPwaOffline === true ? quasarConf.ssr.pwaOfflineHtmlFilename : quasarConf.build.htmlFilename webpackChain.plugin('html-webpack') .use(HtmlWebpackPlugin, [ { filename, template: appPaths.resolve.app(quasarConf.sourceFiles.indexHtmlTemplate), minify: false, // important! we'll do it ourselves later in transformHtml() templateParameters: templateParam, chunksSortMode: 'none', publicPath: quasarConf.build.publicPath, inject: true, cache: true } ]) webpackChain.plugin('html-addons') .use(HtmlTransformPlugin, [ html => transformHtml({ html, quasarConf, renderSsrPwaOffline }) ]) } module.exports.getSsrHtmlTemplateFn = async function getSsrHtmlTemplateFn (template, quasarConf) { const compiled = compileTemplate(template) let html = compiled(quasarConf.htmlVariables) html = injectSsrRuntimeInterpolation(html) html = await transformHtml({ html, quasarConf }) return compileTemplate(html, { interpolate: ssrInterpolationsRE, variable: 'ssrContext' }) }