diff --git a/package.json b/package.json index 607e10c..39a6ff9 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@babel/parser": "^7.24.7", "@babel/traverse": "^7.24.7", "@babel/types": "^7.24.7", + "@unhead/ssr": "^1.9.14", "node-html-parser": "^6.1.13" }, "devDependencies": { diff --git a/playground/pinia-example/vite.config.ts b/playground/pinia-example/vite.config.ts index cdaea6b..d6c1267 100644 --- a/playground/pinia-example/vite.config.ts +++ b/playground/pinia-example/vite.config.ts @@ -15,5 +15,5 @@ export default defineConfig({ }, build: { target: [ 'es2022', 'edge89', 'firefox89', 'chrome89', 'safari15' ], - } + }, }) diff --git a/playground/vue-router/vite.config.ts b/playground/vue-router/vite.config.ts index cdaea6b..d6c1267 100644 --- a/playground/vue-router/vite.config.ts +++ b/playground/vue-router/vite.config.ts @@ -15,5 +15,5 @@ export default defineConfig({ }, build: { target: [ 'es2022', 'edge89', 'firefox89', 'chrome89', 'safari15' ], - } + }, }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc6f9f4..0ebf5b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@babel/types': specifier: ^7.24.7 version: 7.24.7 + '@unhead/ssr': + specifier: ^1.9.14 + version: 1.9.14 node-html-parser: specifier: ^6.1.13 version: 6.1.13 @@ -904,6 +907,9 @@ packages: '@unhead/shared@1.9.14': resolution: {integrity: sha512-7ZIC7uDV8gp3KHm5JxJ/NXMENQgkh+SCyTcsILSpOhkAGeszMHABrB6vjeZDGM4J9mRUxwyPn24KI2zG/R+XiQ==} + '@unhead/ssr@1.9.14': + resolution: {integrity: sha512-OIBZu+WBiyCcDMJ4Ysu7uA6yMZ3fWXWyVrT2w0my5oQJgA0BS7lzfReRL8Sw6+ORlupn9Rn++HXfV0ixtxCxIA==} + '@unhead/vue@1.9.14': resolution: {integrity: sha512-Yc7Qv0ze+iLte4urHiA+ghkF7y+svrawrT+ZrCuGXkZ/eRTF/AY2SKex+rJQJZsP+fKEQ2pGb72IsI5kHFZT3A==} peerDependencies: @@ -2626,6 +2632,11 @@ snapshots: dependencies: '@unhead/schema': 1.9.14 + '@unhead/ssr@1.9.14': + dependencies: + '@unhead/schema': 1.9.14 + '@unhead/shared': 1.9.14 + '@unhead/vue@1.9.14(vue@3.4.30(typescript@5.3.3))': dependencies: '@unhead/schema': 1.9.14 diff --git a/src/plugin/generateHtml.ts b/src/plugin/generateHtml.ts index 164ba47..47717c7 100644 --- a/src/plugin/generateHtml.ts +++ b/src/plugin/generateHtml.ts @@ -1,7 +1,24 @@ -import { parse, HTMLElement } from 'node-html-parser' +import { parse } from 'node-html-parser' import type { VueHeadClient, MergeHead } from '@unhead/vue' +import { renderSSRHead } from '@unhead/ssr' import { State } from '../types' +function rawAttributesToAttributes(raw: string) { + const attrs: Record = {} + + const re = /([a-zA-Z()[\]#@$.?:][a-zA-Z0-9-_:()[\]#]*)(?:\s*=\s*((?:'[^']*')|(?:"[^"]*")|\S+))?/g + let match + + while ((match = re.exec(raw))) { + const key = match[1] + let val = match[2] || null + if (val && (val[0] === `'` || val[0] === `"`)) val = val.slice(1, val.length - 1) + attrs[key] = attrs[key] || val + } + + return attrs +} + export async function generateHtml(template: string, preloadLinks: string, rendered: string, @@ -22,6 +39,7 @@ export async function generateHtml(template: string, } const body = root.querySelector('body') + const html = root.querySelector('html') if (state.value !== undefined) { const { uneval } = await import('devalue') @@ -33,53 +51,17 @@ export async function generateHtml(template: string, body?.insertAdjacentHTML('beforeend', `
${teleports['#teleports']}
`) } - const resolvedTags = await head.resolveTags() - - const htmlTitle = root.querySelector('title') - - if (htmlTitle !== null) { - const title = resolvedTags.find(t => t.tag === 'title') - - if (title !== undefined) { - htmlTitle.textContent = title.textContent ?? '' - } - } - - const allowedTags = ['meta', 'link', 'base', 'style', 'script', 'noscript'] - - resolvedTags - .filter(tag => tag.tag === allowedTags.find(allowed => allowed === tag.tag)) - .forEach(tag => { - let props = '' - - for (const [key, value] of Object.entries(tag.props)) { - props = `${props} ${key}="${value}"` - } - - const el = new HTMLElement(tag.tag, {}, props) - el.textContent = tag.innerHTML - ?? tag.textContent - ?? '' + const payload = await renderSSRHead(head) - htmlHead?.appendChild(el) - }) - - const bodyAttrs = resolvedTags.find(t => t.tag === 'bodyAttrs') - - if (bodyAttrs !== undefined) { - for (const [key, value] of Object.entries(bodyAttrs.props)) { - body?.setAttribute(key, value) - } + if (payload.headTags.includes('')) { + root.querySelector('title')?.remove() } - const htmlAttrs = resolvedTags.find(t => t.tag === 'htmlAttrs') - const htmlRoot = root.querySelector('html') - - if (htmlAttrs !== undefined) { - for (const [key, value] of Object.entries(htmlAttrs.props)) { - htmlRoot?.setAttribute(key, value) - } - } + htmlHead?.insertAdjacentHTML('afterbegin', payload.headTags) + html?.setAttributes(rawAttributesToAttributes(payload.htmlAttrs)) + body?.setAttributes(rawAttributesToAttributes(payload.bodyAttrs)) + body?.insertAdjacentHTML('afterbegin', payload.bodyTagsOpen) + body?.insertAdjacentHTML('beforeend', payload.bodyTags) return root.toString() }