Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide an easier way to alter page content in ssg.beforePageRender hook #238

Open
TechAkayy opened this issue Mar 23, 2023 · 5 comments
Open

Comments

@TechAkayy
Copy link
Collaborator

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:

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',
  	}]
  }
}
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')
  })
})
@ouuan
Copy link
Contributor

ouuan commented Mar 23, 2023

prepending & appending to head & body tag is quite common.

But why would you do this in the hook instead of useHead, <Head>, etc.?

@TechAkayy
Copy link
Collaborator Author

That was just some examples. This feature request is general in nature.

@TechAkayy
Copy link
Collaborator Author

TechAkayy commented Mar 23, 2023

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.

@TechAkayy
Copy link
Collaborator Author

TechAkayy commented Apr 10, 2023

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
				},
			},
		},
	}
}

@TechAkayy
Copy link
Collaborator Author

TechAkayy commented May 5, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants