ElMassimo / iles

🏝 The joyful site generator
https://iles.pages.dev
MIT License
1.08k stars 32 forks source link

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

Open TechAkayy opened 1 year ago

TechAkayy commented 1 year ago

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 commented 1 year ago

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

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

TechAkayy commented 1 year ago

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

TechAkayy commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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.