Provide an easier way to alter page content in ssg.beforePageRender hook
TechAkayy opened this issue · 5 comments
Currently, when building for production, we can use the ssg.beforePageRender
hook to alter the page content before it's generated.
While changing inner content of the body is quite uncommon, prepending & appending to head & body tag is quite common.
Describe the solution you'd like
It would be good to have an easier way to inject tags into body & head tags.
Describe alternatives you've considered
Currently, we have to use some library like node-html-parser to parse the html & modify tags selectively.
Additional context
Some inspirations:
- With Vite's transformIndexHtml, we can return an array of injects (https://vitejs.dev/guide/api-plugin.html#transformindexhtml) like this
transformIndexHtml = (html, context) => {
return {
html: html.replace('{{ title }}', 'Test HTML transforms'),
tags: [{
tag: 'meta',
attrs: { name: 'description', content: 'a vite app' },
// default injection is head-prepend
},
{
tag: 'meta',
attrs: { name: 'keywords', content: 'es modules' },
injectTo: 'head',
}]
}
}
- Nuxt 3, nitro's "render:html" hook provides the html with an interface - https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/core/runtime/nitro/renderer.ts#L27
And can be used like this:
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:html', (html, { event }) => {
html.head.push('<script src="/pgia/lib/index.js "> </script>')
html.bodyAppend.push('<hr>Appended by custom plugin')
})
})
prepending & appending to head & body tag is quite common.
But why would you do this in the hook instead of useHead
, <Head>
, etc.?
That was just some examples. This feature request is general in nature.
Thanks for the pointer. I just checked the docs and learnt about those composables. And they don't include adding a script to the body tag.
In my case, Im actually working on an Iles module, where I want to add some specific scripts to the pages during build.
In fact, my iles module include a combination of a closely coupled vite plugin & the ssg. beforePageRender hook. So, during development, the transformIndexHtml will do the script injection, and during build, the ssg.beforePageRender will do the same script injection.
As the script injection is the same during dev & build, I see its easy to append to body with the vite plugin, while with iles's ssg beforePageRender, I have to use a parser.
Meanwhile, here is my Iles module, where I'm using node-html-parser
to parse the index.html to inject (prepend/append) my scripts (body/head) at the moment. Linking this to related discord chat!
// @ts-nocheck
import type { IlesModule } from 'iles'
import type { UserConfig } from 'vite'
import htmlParser from 'node-html-parser'
// import { fileURLToPath, URL } from 'node:url'
import path from 'path'
import fs from 'fs'
export default async function (): IlesModule {
return {
name: '@pinegrow/iles-module',
config(config) {
config.vue.reactivityTransform = false
config.ssg.beforePageRender = (page /*, config*/) => {
const doc = htmlParser.parse(page.rendered, 'text/html')
const addons = [
{
name: 'pgia',
resources: [
{
// condition: 'interactions',
parentResource: 'public/pgia', // relative to project root, must exists
injectTo: 'head-append',
tag: 'script',
children: `(function(){ /* code */})()`,
},
{
// condition: 'interactions',
parentResource: `public/pgia`, // relative to project root, must exists
injectTo: 'body-append',
tag: 'script',
attrs: { src: '/pgia/lib/index.js' },
},
],
},
]
addons.forEach(addon => {
addon.resources.forEach(resource => {
if (!resource.condition /*|| this.customOptions[resource.condition] */) {
try {
let parentResourceExists = true
if (resource.parentResource) {
const projectRoot = process.cwd()
const resourcePath = path.resolve(projectRoot, resource.parentResource)
parentResourceExists = parentResourceExists && fs.existsSync(resourcePath)
}
if (parentResourceExists) {
const attrContent = Object.entries(resource.attrs || {}).reduce(
(acc, [key, value]) => {
return `${acc}${key}${value && value !== true ? `="${value}"` : ''}`
},
''
)
let parentNode, sourceNode
switch (resource.injectTo) {
case 'head-prepend':
parentNode = doc.querySelector('head')
sourceNode = htmlParser.parse(`<${resource.tag} ${attrContent}>
${resource.children || ''}
</${resource.tag}>`)
parentNode.childNodes.unshift(sourceNode)
break
case 'head-append':
parentNode = doc.querySelector('head')
sourceNode = htmlParser.parse(`<${resource.tag} ${attrContent}>
${resource.children || ''}
</${resource.tag}>`)
parentNode.appendChild(sourceNode)
break
case 'body-prepend':
parentNode = doc.querySelector('body')
sourceNode = htmlParser.parse(`<${resource.tag} ${attrContent}>
${resource.children || ''}
</${resource.tag}>`)
parentNode.childNodes.unshift(sourceNode)
break
case 'body-append':
parentNode = doc.querySelector('body')
sourceNode = htmlParser.parse(`<${resource.tag} ${attrContent}>
${resource.children || ''}
</${resource.tag}>`)
parentNode.appendChild(sourceNode)
break
}
}
} catch (err) {
// console.log(err)
}
}
})
})
page.rendered = doc.outerHTML
},
},
},
}
}
Thanks for the pointer. I just checked the docs and learnt about those composables. And they don't include adding a script to the body tag.
I have learnt that useHead composable can have tagPosition that can take options 'head' | 'bodyClose' | 'bodyOpen'
, so it is possible to add to body as well via the useHead. Still, would like to know how this can achieved when we want to achieve the same via an iles module. thanks.