From 89378527d12e44b3d5b05bd20eebc44e620e034b Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Mon, 22 Apr 2024 18:38:37 +0200 Subject: [PATCH 01/13] fix(v-font): new mechanics to write fonts in header --- docs/.vitepress/navigation.js | 3 +- docs/src/composables/useBoosterHead.md | 13 +++ playground/layouts/default.vue | 1 - playground/nuxt.config.js | 1 + src/module.js | 3 +- src/runtime/classes/Font.js | 14 ++++ src/runtime/classes/FontCollection.js | 20 ++++- src/runtime/composables/fonts.js | 32 ++++---- src/runtime/composables/head.js | 105 +++++++++++++++++++++++++ src/runtime/composables/index.js | 1 + src/runtime/directives/vFont.js | 19 +++-- src/runtime/utils/description.js | 8 +- src/runtime/utils/log.js | 3 + src/tmpl/plugin.tmpl.js | 4 + src/utils.js | 1 + 15 files changed, 194 insertions(+), 34 deletions(-) create mode 100644 docs/src/composables/useBoosterHead.md create mode 100644 src/runtime/composables/head.js create mode 100644 src/runtime/utils/log.js diff --git a/docs/.vitepress/navigation.js b/docs/.vitepress/navigation.js index 0302053b06..2d6a4ea988 100644 --- a/docs/.vitepress/navigation.js +++ b/docs/.vitepress/navigation.js @@ -81,7 +81,8 @@ export default { { text: 'useBoosterComponentObserver', link: '/composables/useBoosterComponentObserver' - } + }, + { text: '⚠️ useBoosterHead', link: '/composables/useBoosterHead' } ] }, { diff --git a/docs/src/composables/useBoosterHead.md b/docs/src/composables/useBoosterHead.md new file mode 100644 index 0000000000..6dbc989729 --- /dev/null +++ b/docs/src/composables/useBoosterHead.md @@ -0,0 +1,13 @@ +--- +title: ⚠️ useBoosterHead +--- + +# {{$frontmatter.title}} + +::: warning + +`useBoosterhead` is an internally used composable that is called once in the plugin. + +Is required to create and manage a head entry. + +::: diff --git a/playground/layouts/default.vue b/playground/layouts/default.vue index 4e78e6d347..db41677e8d 100644 --- a/playground/layouts/default.vue +++ b/playground/layouts/default.vue @@ -29,7 +29,6 @@ useHead( title: `${route.name} | nuxt-booster`, meta: [ { - hid: 'description', name: 'description', content: `${route.name} - description` } diff --git a/playground/nuxt.config.js b/playground/nuxt.config.js index b38d97920c..dd7c1dc7d9 100644 --- a/playground/nuxt.config.js +++ b/playground/nuxt.config.js @@ -112,6 +112,7 @@ export default defineNuxtConfig(async () => { }, booster: { + debug: false, // targetFormats: ['jpg|jpeg|png|gif'], // densities: 'x1 x2', detection: { diff --git a/src/module.js b/src/module.js index 4df716e18d..02ec0dadfa 100644 --- a/src/module.js +++ b/src/module.js @@ -89,7 +89,8 @@ export default defineNuxtModule({ 'useBoosterComponentObserver', 'useBoosterCritical', 'useBoosterConfig', - 'useBoosterFonts' + 'useBoosterFonts', + 'useBoosterHead' ].map(name => ({ name, from: resolve(runtimeDir, 'composables/index') diff --git a/src/runtime/classes/Font.js b/src/runtime/classes/Font.js index f929daa090..a5f0cb76d4 100644 --- a/src/runtime/classes/Font.js +++ b/src/runtime/classes/Font.js @@ -22,6 +22,20 @@ export default class Font { this.loaded = new Deferred(); } + toJSON() { + return { + family: this.family, + style: this.style, + weight: this.weight, + src: this.src, + type: this.type, + fallbackFamily: this.fallbackFamily, + rootSelector: this.rootSelector, + selector: this.selector, + media: this.media + }; + } + async load() { const fonts = 'fonts' in window.document && (await window.document.fonts.ready); diff --git a/src/runtime/classes/FontCollection.js b/src/runtime/classes/FontCollection.js index 571844a45e..e77d67b32c 100644 --- a/src/runtime/classes/FontCollection.js +++ b/src/runtime/classes/FontCollection.js @@ -9,6 +9,10 @@ export default class FontCollection { this.list = []; } + getKey() { + return toFontHex(JSON.stringify(this.list.map(font => font.getKey()))); + } + add(fonts) { const rootSelector = { name: 'data-font', @@ -45,7 +49,9 @@ export default class FontCollection { getStyleDescriptions(options) { return getRelevantDescriptions([ getStyleDescription( - this.list.map(font => font.getCSSText(options)).join(' ') + this.list.map(font => font.getCSSText(options)).join(' '), + false, + this.getKey() ) ]); } @@ -54,14 +60,20 @@ export default class FontCollection { return getRelevantDescriptions([ getStyleDescription( this.list.map(font => font.getNoScriptCSSText()).join(' '), - true + true, + this.getKey() ) ]); } - // has to be declared -> https://github.com/vuex-orm/vuex-orm/issues/255 + get size() { + return this.list.length; + } + toJSON() { - return this.list; + return { + list: this.list.map(font => font.toJSON()) + }; } } diff --git a/src/runtime/composables/fonts.js b/src/runtime/composables/fonts.js index b0d401356e..ee7a498184 100644 --- a/src/runtime/composables/fonts.js +++ b/src/runtime/composables/fonts.js @@ -1,5 +1,5 @@ -import { computed, reactive } from 'vue'; -import { useHead, useBoosterCritical, useBoosterConfig } from '#imports'; +import { reactive } from 'vue'; +import { useBoosterCritical, useBoosterConfig } from '#imports'; import { useNuxtApp } from '#app'; import FontCollection from '#booster/classes/FontCollection'; @@ -12,7 +12,18 @@ export default function (context) { const fontCollection = reactive(new FontCollection()); - writeHead(isCritical, fontCollection, runtimeConfig); + const options = { usedFontaine: runtimeConfig.usedFontaine }; + + try { + const entry = nuxtApp.$booster.head.push( + fontCollection, + isCritical.value, + options + ); + onBeforeUnmount(() => entry.dispose()); + } catch (error) { + console.error(error); + } return { isCritical, @@ -27,18 +38,3 @@ export default function (context) { } }; } - -function writeHead(isCritical, fontCollection, runtimeConfig) { - const options = { usedFontaine: runtimeConfig.usedFontaine }; - useHead({ - link: computed(() => { - return fontCollection.getPreloadDescriptions(isCritical.value); - }), - style: computed(() => { - return fontCollection.getStyleDescriptions(options); - }), - noscript: computed(() => { - return fontCollection.getNoScriptStyleDescriptions(); - }) - }); -} diff --git a/src/runtime/composables/head.js b/src/runtime/composables/head.js new file mode 100644 index 0000000000..be162f6e07 --- /dev/null +++ b/src/runtime/composables/head.js @@ -0,0 +1,105 @@ +import { logDebug } from '../utils/log'; + +export default function () { + const head = injectHead(); + const nuxtApp = useNuxtApp(); + + const { debug } = useRuntimeConfig().public.booster; + const collection = ref(new FontsCollection()); + + let headEntry; + watch( + () => collection.value, + () => { + if (headEntry) { + headEntry.patch(createEntry(collection, debug)); + } else { + headEntry = head.push({}); + } + } + ); + + nuxtApp.$router.afterEach(() => { + nextTick(() => { + collection.value = new FontsCollection( + collection.value.list.filter(item => { + return !dispatchCollections.includes(item); + }) + ); + dispatchCollections = []; + }); + }); + + let dispatchCollections = []; + const push = (fontCollection, isCritical, options) => { + if (!collection) { + throw new Error('pushFontCollection must be called before setupHead'); + } + let value = { fontCollection, isCritical, options }; + collection.value = new FontsCollection([...collection.value.list, value]); + value = collection.value.list[collection.value.list.length - 1]; + return { + dispose: () => dispatchCollections.push(value) + }; + }; + return { push, collection }; +} + +const createEntry = (collection, debug) => { + return () => { + if (debug) { + logDebug('Head Font Collections:', collection.value.toJSON()); + } + + const items = collection.value.list.filter( + ({ fontCollection }) => fontCollection.size + ); + + const uniqList = items => + Array.from(new Map(items.map(item => [item.key, item])).values()); + + return { + link: uniqList( + items + .filter(({ fontCollection }) => fontCollection.size) + .map(({ fontCollection, isCritical }) => + fontCollection.getPreloadDescriptions(isCritical) + ) + .flat() + ), + style: uniqList( + items + .map(({ fontCollection, options }) => + fontCollection.getStyleDescriptions(options) + ) + .flat() + ), + noscript: uniqList( + items + .map(({ fontCollection }) => + fontCollection.getNoScriptStyleDescriptions() + ) + .flat() + ) + }; + }; +}; + +class FontsCollection { + constructor(list = []) { + this.list = list; + } + + get size() { + return this.list.length; + } + + toJSON() { + return { + list: this.list.map(item => ({ + ...item, + fontCollection: item.fontCollection.toJSON() + })) + }; + } +} diff --git a/src/runtime/composables/index.js b/src/runtime/composables/index.js index 60bdd599f5..625e695533 100644 --- a/src/runtime/composables/index.js +++ b/src/runtime/composables/index.js @@ -2,3 +2,4 @@ export { default as useBoosterComponentObserver } from './componentObserver'; export { default as useBoosterCritical } from './critical'; export { default as useBoosterConfig } from './config'; export { default as useBoosterFonts } from './fonts'; +export { default as useBoosterHead } from './head'; diff --git a/src/runtime/directives/vFont.js b/src/runtime/directives/vFont.js index b97c47c876..2a4c0dd8cf 100644 --- a/src/runtime/directives/vFont.js +++ b/src/runtime/directives/vFont.js @@ -52,19 +52,20 @@ export default { } }, - async mounted(el, binding) { + async mounted(el, binding, scope) { + if (scope.fontActive) return; const firstFont = getFirstFont(binding.value); if (firstFont) { const { isCritical, runtimeConfig } = getFirstFont(binding.value); if (isCritical || !isElementOutViewport(el)) { - activateFonts(el, binding); + activateFonts(el, binding, scope); } else { const observer = getElementObserver(el, { rootMargin: runtimeConfig.lazyOffsetAsset }); observers.set(el, observer); await observer.enterViewOnce(); - activateFonts(el, binding); + activateFonts(el, binding, scope); } } }, @@ -80,7 +81,7 @@ function getFirstFont(value) { return [].concat(value)[0]; } -async function activateFonts(el, binding) { +async function activateFonts(el, binding, scope) { const fonts = [].concat(binding.value).map(({ definition }) => definition); await Promise.all( fonts @@ -91,6 +92,12 @@ async function activateFonts(el, binding) { el.classList.add(CLASS_FONT_ACTIVE); binding.instance.fontActive = true; - // TODO: Wird hier sowohl eine Komponente und ein HTML-Tag beachtet? - // binding.instance.$emit('load:font', fonts); + // workaround for load:font emit + if ( + scope.props && + 'onLoad:font' in scope.props && + typeof scope.props['onLoad:font'] === 'function' + ) { + scope.props['onLoad:font'](fonts); + } } diff --git a/src/runtime/utils/description.js b/src/runtime/utils/description.js index 64e2cce445..c1d581b0ca 100644 --- a/src/runtime/utils/description.js +++ b/src/runtime/utils/description.js @@ -64,19 +64,21 @@ export function getFontPreloadDescription( }; } -export function getStyleDescription(children, noScript = false) { +export function getStyleDescription(children, noScript = false, key) { if (noScript) { - return getNoScriptDescription(``); + return getNoScriptDescription(``, key); } else { return { + key: key, type: 'text/css', children: minify(children) }; } } -export function getNoScriptDescription(textContent) { +export function getNoScriptDescription(textContent, key) { return { + key: key, innerHTML: minify(textContent) }; } diff --git a/src/runtime/utils/log.js b/src/runtime/utils/log.js new file mode 100644 index 0000000000..4381eb3a15 --- /dev/null +++ b/src/runtime/utils/log.js @@ -0,0 +1,3 @@ +export const logDebug = (...args) => { + console.log('[DEBUG][BOOSTER]:', ...args); +}; diff --git a/src/tmpl/plugin.tmpl.js b/src/tmpl/plugin.tmpl.js index c54e10f422..7710e9cd8a 100644 --- a/src/tmpl/plugin.tmpl.js +++ b/src/tmpl/plugin.tmpl.js @@ -3,6 +3,7 @@ export default options => { import { isSupportedBrowser } from '#booster/utils/browser'; import FontList from '#booster/classes/FontList'; import hydrate from '#booster/hydrate'; +import userBoosterHead from '#booster/composables/head'; import './fonts.css'; export default defineNuxtPlugin({ @@ -15,7 +16,10 @@ export default defineNuxtPlugin({ ); const fontList = new FontList(fontConfig); + const head = userBoosterHead(); + nuxtApp.provide('booster', { + head, getImageSize, hydrate, getFont: fontList.getFont.bind(fontList), diff --git a/src/utils.js b/src/utils.js index de1af078cd..b6c032d48c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -23,6 +23,7 @@ export function isViteBuild(nuxt) { export const setPublicRuntimeConfig = (nuxt, options) => { nuxt.options.runtimeConfig.public.booster = { + debug: options.debug, lazyOffsetComponent: options.lazyOffset.component, lazyOffsetAsset: options.lazyOffset.asset, usedFontaine: !options.disableNuxtFontaine From ef9a49eff48d06dc3afdc2ceac9ca4badb38a7bf Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Mon, 22 Apr 2024 19:48:27 +0200 Subject: [PATCH 02/13] fix(head): renaming --- src/runtime/composables/head.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runtime/composables/head.js b/src/runtime/composables/head.js index be162f6e07..d27c57bd18 100644 --- a/src/runtime/composables/head.js +++ b/src/runtime/composables/head.js @@ -23,14 +23,14 @@ export default function () { nextTick(() => { collection.value = new FontsCollection( collection.value.list.filter(item => { - return !dispatchCollections.includes(item); + return !disposeCollections.includes(item); }) ); - dispatchCollections = []; + disposeCollections = []; }); }); - let dispatchCollections = []; + let disposeCollections = []; const push = (fontCollection, isCritical, options) => { if (!collection) { throw new Error('pushFontCollection must be called before setupHead'); @@ -39,7 +39,7 @@ export default function () { collection.value = new FontsCollection([...collection.value.list, value]); value = collection.value.list[collection.value.list.length - 1]; return { - dispose: () => dispatchCollections.push(value) + dispose: () => disposeCollections.push(value) }; }; return { push, collection }; From 2d4c28c96464aa160fa46c3015be1ced8f13ceb9 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Wed, 24 Apr 2024 19:14:44 +0200 Subject: [PATCH 03/13] fix(head): improve and added test pages --- playground/components/base/Headline.vue | 38 ++++--- playground/components/tests/TestHeadlineA.vue | 10 ++ playground/components/tests/TestHeadlineB.vue | 10 ++ playground/components/tests/TestHeadlineC.vue | 10 ++ playground/layouts/transition.vue | 17 +++ playground/pages/tests/useBoosterHead/1.vue | 37 ++++++ playground/pages/tests/useBoosterHead/2.vue | 37 ++++++ .../pages/tests/useBoosterHead/index.vue | 41 +++++++ src/runtime/classes/FontCollection.js | 7 +- src/runtime/composables/fonts.js | 2 +- src/runtime/composables/head.js | 107 ++++++++++-------- src/runtime/utils/description.js | 4 +- 12 files changed, 246 insertions(+), 74 deletions(-) create mode 100644 playground/components/tests/TestHeadlineA.vue create mode 100644 playground/components/tests/TestHeadlineB.vue create mode 100644 playground/components/tests/TestHeadlineC.vue create mode 100644 playground/layouts/transition.vue create mode 100644 playground/pages/tests/useBoosterHead/1.vue create mode 100644 playground/pages/tests/useBoosterHead/2.vue create mode 100644 playground/pages/tests/useBoosterHead/index.vue diff --git a/playground/components/base/Headline.vue b/playground/components/base/Headline.vue index dac4389186..d0301376c1 100644 --- a/playground/components/base/Headline.vue +++ b/playground/components/base/Headline.vue @@ -1,9 +1,5 @@ @@ -11,19 +7,25 @@ - diff --git a/playground/components/tests/TestHeadlineA.vue b/playground/components/tests/TestHeadlineA.vue new file mode 100644 index 0000000000..5c6336fa51 --- /dev/null +++ b/playground/components/tests/TestHeadlineA.vue @@ -0,0 +1,10 @@ + + + diff --git a/playground/components/tests/TestHeadlineB.vue b/playground/components/tests/TestHeadlineB.vue new file mode 100644 index 0000000000..e9685eb0a6 --- /dev/null +++ b/playground/components/tests/TestHeadlineB.vue @@ -0,0 +1,10 @@ + + + diff --git a/playground/components/tests/TestHeadlineC.vue b/playground/components/tests/TestHeadlineC.vue new file mode 100644 index 0000000000..281d0156f0 --- /dev/null +++ b/playground/components/tests/TestHeadlineC.vue @@ -0,0 +1,10 @@ + + + diff --git a/playground/layouts/transition.vue b/playground/layouts/transition.vue new file mode 100644 index 0000000000..92077fcf3d --- /dev/null +++ b/playground/layouts/transition.vue @@ -0,0 +1,17 @@ + + + diff --git a/playground/pages/tests/useBoosterHead/1.vue b/playground/pages/tests/useBoosterHead/1.vue new file mode 100644 index 0000000000..be3d62903d --- /dev/null +++ b/playground/pages/tests/useBoosterHead/1.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/playground/pages/tests/useBoosterHead/2.vue b/playground/pages/tests/useBoosterHead/2.vue new file mode 100644 index 0000000000..d36c0727cf --- /dev/null +++ b/playground/pages/tests/useBoosterHead/2.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/playground/pages/tests/useBoosterHead/index.vue b/playground/pages/tests/useBoosterHead/index.vue new file mode 100644 index 0000000000..a8ac549b35 --- /dev/null +++ b/playground/pages/tests/useBoosterHead/index.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/runtime/classes/FontCollection.js b/src/runtime/classes/FontCollection.js index e77d67b32c..e282ed5021 100644 --- a/src/runtime/classes/FontCollection.js +++ b/src/runtime/classes/FontCollection.js @@ -18,12 +18,13 @@ export default class FontCollection { name: 'data-font', value: `${toFontHex(JSON.stringify(fonts.map(font => font.getKey())))}` }; - this.list = [].concat(this.list).concat( - fonts.map(font => { + this.list = [ + ...this.list, + ...fonts.map(font => { font.setRootSelector(rootSelector); return font; }) - ); + ]; return rootSelector; } diff --git a/src/runtime/composables/fonts.js b/src/runtime/composables/fonts.js index ee7a498184..158b60c85f 100644 --- a/src/runtime/composables/fonts.js +++ b/src/runtime/composables/fonts.js @@ -20,7 +20,7 @@ export default function (context) { isCritical.value, options ); - onBeforeUnmount(() => entry.dispose()); + onBeforeUnmount(() => nextTick(() => entry.dispose())); } catch (error) { console.error(error); } diff --git a/src/runtime/composables/head.js b/src/runtime/composables/head.js index d27c57bd18..c23ad6167c 100644 --- a/src/runtime/composables/head.js +++ b/src/runtime/composables/head.js @@ -4,87 +4,94 @@ export default function () { const head = injectHead(); const nuxtApp = useNuxtApp(); - const { debug } = useRuntimeConfig().public.booster; + const { + public: { + booster: { debug } + } + } = useRuntimeConfig(); const collection = ref(new FontsCollection()); let headEntry; watch( () => collection.value, - () => { - if (headEntry) { - headEntry.patch(createEntry(collection, debug)); - } else { - headEntry = head.push({}); - } + value => { + const data = createEntry(value, debug); + headEntry?.dispose(); + nextTick(() => { + headEntry = head.push(() => data); + }); } ); - nuxtApp.$router.afterEach(() => { + nuxtApp.$router.beforeEach(() => { nextTick(() => { collection.value = new FontsCollection( collection.value.list.filter(item => { - return !disposeCollections.includes(item); + return !disposeCollections.value.includes(item); }) ); - disposeCollections = []; + disposeCollections.value = []; }); }); - let disposeCollections = []; + let disposeCollections = ref([]); const push = (fontCollection, isCritical, options) => { if (!collection) { throw new Error('pushFontCollection must be called before setupHead'); } - let value = { fontCollection, isCritical, options }; - collection.value = new FontsCollection([...collection.value.list, value]); - value = collection.value.list[collection.value.list.length - 1]; + const notEmpty = !fontCollection.list.length; + let value; + if (notEmpty) { + value = { fontCollection, isCritical, options }; + collection.value = new FontsCollection([...collection.value.list, value]); + value = collection.value.list[collection.value.list.length - 1]; + } return { - dispose: () => disposeCollections.push(value) + dispose: () => notEmpty && disposeCollections.value.push(value) }; }; return { push, collection }; } const createEntry = (collection, debug) => { - return () => { - if (debug) { - logDebug('Head Font Collections:', collection.value.toJSON()); - } - - const items = collection.value.list.filter( - ({ fontCollection }) => fontCollection.size - ); - - const uniqList = items => - Array.from(new Map(items.map(item => [item.key, item])).values()); + if (debug) { + logDebug('Head Font Collections:', collection.toJSON()); + } - return { - link: uniqList( - items - .filter(({ fontCollection }) => fontCollection.size) - .map(({ fontCollection, isCritical }) => - fontCollection.getPreloadDescriptions(isCritical) - ) - .flat() - ), - style: uniqList( - items - .map(({ fontCollection, options }) => - fontCollection.getStyleDescriptions(options) - ) - .flat() - ), - noscript: uniqList( - items - .map(({ fontCollection }) => - fontCollection.getNoScriptStyleDescriptions() - ) - .flat() - ) - }; + const items = collection.list.filter( + ({ fontCollection }) => fontCollection.size + ); + return { + link: prepareItems( + items + .filter(({ fontCollection }) => fontCollection.size) + .map(({ fontCollection, isCritical }) => + fontCollection.getPreloadDescriptions(isCritical) + ) + .flat() + ), + style: prepareItems( + items + .map(({ fontCollection, options }) => + fontCollection.getStyleDescriptions(options) + ) + .flat() + ), + noscript: prepareItems( + items + .map(({ fontCollection }) => + fontCollection.getNoScriptStyleDescriptions() + ) + .flat() + ) }; }; +const prepareItems = items => + Array.from( + new Map(items.map(item => [item.key, { ...item, key: undefined }])).values() + ); + class FontsCollection { constructor(list = []) { this.list = list; diff --git a/src/runtime/utils/description.js b/src/runtime/utils/description.js index c1d581b0ca..92d416e9fd 100644 --- a/src/runtime/utils/description.js +++ b/src/runtime/utils/description.js @@ -69,7 +69,7 @@ export function getStyleDescription(children, noScript = false, key) { return getNoScriptDescription(``, key); } else { return { - key: key, + key, type: 'text/css', children: minify(children) }; @@ -78,7 +78,7 @@ export function getStyleDescription(children, noScript = false, key) { export function getNoScriptDescription(textContent, key) { return { - key: key, + key, innerHTML: minify(textContent) }; } From b45366acf9cf1edfb6227f3f7318a34263f9933e Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Fri, 26 Apr 2024 19:14:52 +0200 Subject: [PATCH 04/13] fix(description): improve params --- src/runtime/utils/description.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/runtime/utils/description.js b/src/runtime/utils/description.js index 92d416e9fd..e7952c9cb5 100644 --- a/src/runtime/utils/description.js +++ b/src/runtime/utils/description.js @@ -64,7 +64,11 @@ export function getFontPreloadDescription( }; } -export function getStyleDescription(children, noScript = false, key) { +export function getStyleDescription( + children, + noScript = false, + key = undefined +) { if (noScript) { return getNoScriptDescription(``, key); } else { @@ -76,7 +80,7 @@ export function getStyleDescription(children, noScript = false, key) { } } -export function getNoScriptDescription(textContent, key) { +export function getNoScriptDescription(textContent, key = undefined) { return { key, innerHTML: minify(textContent) From f8f17fc82f4cd3e33009243238eca8de64ffe255 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Sat, 27 Apr 2024 15:33:57 +0200 Subject: [PATCH 05/13] test(browser): added test for transitions --- playground/components/base/Headline.vue | 13 +++- playground/components/tests/TestHeadlineA.vue | 10 --- playground/components/tests/TestHeadlineB.vue | 10 --- playground/components/tests/TestHeadlineC.vue | 10 --- playground/layouts/default.vue | 4 +- playground/layouts/test.vue | 5 ++ .../{transition.vue => testTransition.vue} | 0 playground/nuxt.config.js | 5 ++ .../pages/tests/booster-layer/index.vue | 2 +- .../pages/tests/booster-loader/index.vue | 2 +- playground/pages/tests/index.vue | 2 +- playground/pages/tests/useBoosterHead.vue | 59 +++++++++++++++ playground/pages/tests/useBoosterHead/1.vue | 45 ++++-------- playground/pages/tests/useBoosterHead/2.vue | 48 +++++-------- .../pages/tests/useBoosterHead/empty-1.vue | 10 +++ .../pages/tests/useBoosterHead/empty-2.vue | 10 +++ .../pages/tests/useBoosterHead/index.vue | 53 ++++++++------ .../tests/weak-hardware-overlay/index.vue | 2 +- test/nuxt.config.js | 8 +-- test/tests/browser.js | 71 +++++++++++++++++++ 20 files changed, 243 insertions(+), 126 deletions(-) delete mode 100644 playground/components/tests/TestHeadlineA.vue delete mode 100644 playground/components/tests/TestHeadlineB.vue delete mode 100644 playground/components/tests/TestHeadlineC.vue create mode 100644 playground/layouts/test.vue rename playground/layouts/{transition.vue => testTransition.vue} (100%) create mode 100644 playground/pages/tests/useBoosterHead.vue create mode 100644 playground/pages/tests/useBoosterHead/empty-1.vue create mode 100644 playground/pages/tests/useBoosterHead/empty-2.vue diff --git a/playground/components/base/Headline.vue b/playground/components/base/Headline.vue index 66e23d3858..571184604e 100644 --- a/playground/components/base/Headline.vue +++ b/playground/components/base/Headline.vue @@ -22,8 +22,17 @@ const $props = defineProps({ } }); const preparedFont = computed(() => { - if ($props.font) { - return $props.font; + let font = $props.font; + if (font) { + if (!(Array.isArray(font) && Array.isArray(font[0]))) { + font = [font]; + } + return [].concat(font).map(font => { + if (!Array.isArray(font) && typeof font === 'object') { + font = [font.name, font.weight, font.style, font.selector]; + } + return $getFont(...font); + }); } return $getFont('Quicksand', 700, 'normal'); }); diff --git a/playground/components/tests/TestHeadlineA.vue b/playground/components/tests/TestHeadlineA.vue deleted file mode 100644 index 5c6336fa51..0000000000 --- a/playground/components/tests/TestHeadlineA.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/playground/components/tests/TestHeadlineB.vue b/playground/components/tests/TestHeadlineB.vue deleted file mode 100644 index e9685eb0a6..0000000000 --- a/playground/components/tests/TestHeadlineB.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/playground/components/tests/TestHeadlineC.vue b/playground/components/tests/TestHeadlineC.vue deleted file mode 100644 index 281d0156f0..0000000000 --- a/playground/components/tests/TestHeadlineC.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/playground/layouts/default.vue b/playground/layouts/default.vue index 23ed1379fd..77663a245e 100644 --- a/playground/layouts/default.vue +++ b/playground/layouts/default.vue @@ -127,10 +127,10 @@ body { .page-enter-active, .page-leave-active { - transition: opacity 0.5s; + transition: all 0.3s; } -.page-enter, +.page-enter-from, .page-leave-to { opacity: 0; } diff --git a/playground/layouts/test.vue b/playground/layouts/test.vue new file mode 100644 index 0000000000..56a8b72d4a --- /dev/null +++ b/playground/layouts/test.vue @@ -0,0 +1,5 @@ + diff --git a/playground/layouts/transition.vue b/playground/layouts/testTransition.vue similarity index 100% rename from playground/layouts/transition.vue rename to playground/layouts/testTransition.vue diff --git a/playground/nuxt.config.js b/playground/nuxt.config.js index dd7c1dc7d9..fd77686c3c 100644 --- a/playground/nuxt.config.js +++ b/playground/nuxt.config.js @@ -31,6 +31,11 @@ export default defineNuxtConfig(async () => { app: { baseURL: getBaseUrl(), + + pageTransition: { + name: 'page', + mode: 'out-in' + }, head: { htmlAttrs: { lang: 'en' diff --git a/playground/pages/tests/booster-layer/index.vue b/playground/pages/tests/booster-layer/index.vue index d7ec9356e1..7c3e9446da 100644 --- a/playground/pages/tests/booster-layer/index.vue +++ b/playground/pages/tests/booster-layer/index.vue @@ -42,6 +42,6 @@ const imageTextA = { }; definePageMeta({ - layout: 'blank' + layout: 'test' }); diff --git a/playground/pages/tests/booster-loader/index.vue b/playground/pages/tests/booster-loader/index.vue index 4f571861f7..6ab77d511a 100644 --- a/playground/pages/tests/booster-loader/index.vue +++ b/playground/pages/tests/booster-loader/index.vue @@ -12,6 +12,6 @@ const Critical = boosterHydrate(() => import('./components/Critical')); const Lazy = boosterHydrate(() => import('./components/Lazy')); definePageMeta({ - layout: 'blank' + layout: 'test' }); diff --git a/playground/pages/tests/index.vue b/playground/pages/tests/index.vue index 1372542b46..231c66a469 100644 --- a/playground/pages/tests/index.vue +++ b/playground/pages/tests/index.vue @@ -4,6 +4,6 @@ diff --git a/playground/pages/tests/useBoosterHead.vue b/playground/pages/tests/useBoosterHead.vue new file mode 100644 index 0000000000..f03eed3def --- /dev/null +++ b/playground/pages/tests/useBoosterHead.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/playground/pages/tests/useBoosterHead/1.vue b/playground/pages/tests/useBoosterHead/1.vue index be3d62903d..14044515be 100644 --- a/playground/pages/tests/useBoosterHead/1.vue +++ b/playground/pages/tests/useBoosterHead/1.vue @@ -1,37 +1,20 @@ - - diff --git a/playground/pages/tests/useBoosterHead/2.vue b/playground/pages/tests/useBoosterHead/2.vue index d36c0727cf..674d6a1b99 100644 --- a/playground/pages/tests/useBoosterHead/2.vue +++ b/playground/pages/tests/useBoosterHead/2.vue @@ -1,37 +1,23 @@ - - diff --git a/playground/pages/tests/useBoosterHead/empty-1.vue b/playground/pages/tests/useBoosterHead/empty-1.vue new file mode 100644 index 0000000000..cd9f5b7029 --- /dev/null +++ b/playground/pages/tests/useBoosterHead/empty-1.vue @@ -0,0 +1,10 @@ + + + diff --git a/playground/pages/tests/useBoosterHead/empty-2.vue b/playground/pages/tests/useBoosterHead/empty-2.vue new file mode 100644 index 0000000000..8e1be5fdb6 --- /dev/null +++ b/playground/pages/tests/useBoosterHead/empty-2.vue @@ -0,0 +1,10 @@ + + + diff --git a/playground/pages/tests/useBoosterHead/index.vue b/playground/pages/tests/useBoosterHead/index.vue index a8ac549b35..52bff61401 100644 --- a/playground/pages/tests/useBoosterHead/index.vue +++ b/playground/pages/tests/useBoosterHead/index.vue @@ -1,31 +1,42 @@ + diff --git a/playground/pages/tests/booster-layer/index.vue b/playground/pages/tests/booster-layer/index.vue index 7c3e9446da..d7ec9356e1 100644 --- a/playground/pages/tests/booster-layer/index.vue +++ b/playground/pages/tests/booster-layer/index.vue @@ -42,6 +42,6 @@ const imageTextA = { }; definePageMeta({ - layout: 'test' + layout: 'blank' }); diff --git a/playground/pages/tests/booster-loader/index.vue b/playground/pages/tests/booster-loader/index.vue index 6ab77d511a..4f571861f7 100644 --- a/playground/pages/tests/booster-loader/index.vue +++ b/playground/pages/tests/booster-loader/index.vue @@ -12,6 +12,6 @@ const Critical = boosterHydrate(() => import('./components/Critical')); const Lazy = boosterHydrate(() => import('./components/Lazy')); definePageMeta({ - layout: 'test' + layout: 'blank' }); diff --git a/playground/pages/tests/index.vue b/playground/pages/tests/index.vue index 231c66a469..1372542b46 100644 --- a/playground/pages/tests/index.vue +++ b/playground/pages/tests/index.vue @@ -4,6 +4,6 @@ diff --git a/playground/pages/tests/weak-hardware-overlay/index.vue b/playground/pages/tests/weak-hardware-overlay/index.vue index 28001e24dc..99b1b7e41c 100644 --- a/playground/pages/tests/weak-hardware-overlay/index.vue +++ b/playground/pages/tests/weak-hardware-overlay/index.vue @@ -19,6 +19,6 @@ onMounted(() => { }); definePageMeta({ - layout: 'test' + layout: 'blank' }); diff --git a/src/runtime/hydrate.js b/src/runtime/hydrate.js index 580ec7b630..59b476dbf4 100644 --- a/src/runtime/hydrate.js +++ b/src/runtime/hydrate.js @@ -3,18 +3,18 @@ import { hydrateWhenVisible } from 'vue3-lazy-hydration'; const isDev = process.env.NODE_ENV === 'development'; export default component => { - if (isDev || import.meta.server) { - if (typeof component === 'function') { - component = defineAsyncComponent(component); - } - return component; - } - const runtimeConfig = useRuntimeConfig(); - return hydrateWhenVisible(component, { + return hydrateWhenVisible(wrapComponent(component), { observerOptions: { rootMargin: runtimeConfig.public.booster.lazyOffsetComponent || '0%' } }); }; + +const wrapComponent = component => { + if ((isDev || import.meta.server) && typeof component === 'function') { + return defineAsyncComponent(component); + } + return component; +}; diff --git a/test/tests/browser.js b/test/tests/browser.js index 094f469981..322f82ecce 100644 --- a/test/tests/browser.js +++ b/test/tests/browser.js @@ -28,7 +28,7 @@ export default runtime => { // #region /tests/useBoosterHead - it('Transition Page Change', async () => { + it('Transition (Page Change)', async () => { const page = await createPage('/useBoosterHead/'); expect( From d3d5ef5ed3c379e9e0736e618d2c81506b1f8e4f Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Sat, 27 Apr 2024 18:17:33 +0200 Subject: [PATCH 07/13] fix(v-font): update font:load event --- docs/src/directives/v-font.md | 6 +++++- playground/pages/tests/useBoosterHead.vue | 2 +- src/runtime/directives/vFont.js | 25 +++++++++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/src/directives/v-font.md b/docs/src/directives/v-font.md index 7497c43f84..36fc9fb935 100644 --- a/docs/src/directives/v-font.md +++ b/docs/src/directives/v-font.md @@ -16,7 +16,8 @@ It is recommended to use the property `critical` for components that are already With critical component the fonts are preloaded and are initially active. More information on critical components can be found [here](/guide/usage#critical-prop-for-critical-components). -For multiple fonts, a list (`Array`) can be passed. +- For multiple fonts, a list (`Array`) can be passed. +- The `load:font` event can be used to react to the successful loading of the fonts. ````html @@ -28,6 +29,9 @@ For multiple fonts, a list (`Array`) can be passed. $getFont(…), $getFont(…) ]"> + + + ```` ## `$getFont(family, [weight, style, options])` diff --git a/playground/pages/tests/useBoosterHead.vue b/playground/pages/tests/useBoosterHead.vue index f03eed3def..6fc2e8a3b5 100644 --- a/playground/pages/tests/useBoosterHead.vue +++ b/playground/pages/tests/useBoosterHead.vue @@ -47,7 +47,7 @@ const links = ref( ); definePageMeta({ - layout: 'test-transition' + layout: 'blank' }); diff --git a/src/runtime/directives/vFont.js b/src/runtime/directives/vFont.js index 2a4c0dd8cf..a707a7fce9 100644 --- a/src/runtime/directives/vFont.js +++ b/src/runtime/directives/vFont.js @@ -27,6 +27,9 @@ export default { .filter(Boolean) .join(' '); } + if (isCritical) { + emit(vnode.props, 'onLoad:font', definitions); + } } }, @@ -46,9 +49,14 @@ export default { } }, - updated(el, binding) { + updated(el, binding, vnode) { if (binding.instance.fontsReady.get(el)) { el.classList.add(CLASS_FONT_ACTIVE); + emit( + vnode.props, + 'onLoad:font', + [].concat(binding.value).map(value => value.definition) + ); } }, @@ -92,12 +100,11 @@ async function activateFonts(el, binding, scope) { el.classList.add(CLASS_FONT_ACTIVE); binding.instance.fontActive = true; - // workaround for load:font emit - if ( - scope.props && - 'onLoad:font' in scope.props && - typeof scope.props['onLoad:font'] === 'function' - ) { - scope.props['onLoad:font'](fonts); - } + emit(scope.props, 'onLoad:font', fonts); } + +const emit = (props, name, fonts) => { + if (typeof props?.[String(name)] === 'function') { + props[String(name)](fonts); + } +}; From a80a30ee61e405e439a525373684cde88b9f19f5 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Sat, 27 Apr 2024 19:58:23 +0200 Subject: [PATCH 08/13] fix(preload): render link preload tags only in ssr --- eslint.config.js | 3 ++- src/runtime/components/BoosterImage/Base.vue | 1 + .../components/BoosterPicture/Source.vue | 1 + src/runtime/composables/head.js | 18 ++++++++++-------- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index b8f86744d3..3ea4f64b48 100755 --- a/eslint.config.js +++ b/eslint.config.js @@ -36,6 +36,7 @@ export default withNuxt({ ignores: [] } ], - 'vue/multi-word-component-names': 'off' + 'vue/multi-word-component-names': 'off', + 'vue/html-self-closing': 'off' // prettier conflict } }).prepend(pluginSecurity.configs.recommended, eslintPluginPrettierRecommended); diff --git a/src/runtime/components/BoosterImage/Base.vue b/src/runtime/components/BoosterImage/Base.vue index 2a92fe3b25..750e7e2123 100644 --- a/src/runtime/components/BoosterImage/Base.vue +++ b/src/runtime/components/BoosterImage/Base.vue @@ -87,6 +87,7 @@ export default { ].filter(Boolean), link: [ !(!config || !isCritical.value) && + (import.meta.server || process.env.prerender) && new Source(source).getPreload( config.srcset, config.sizes, diff --git a/src/runtime/components/BoosterPicture/Source.vue b/src/runtime/components/BoosterPicture/Source.vue index ffa1ce3962..3ea43abef5 100644 --- a/src/runtime/components/BoosterPicture/Source.vue +++ b/src/runtime/components/BoosterPicture/Source.vue @@ -48,6 +48,7 @@ export default { link: [ config && imageSource.preload && + (import.meta.server || process.env.prerender) && imageSource.getPreload( config.srcset, config.sizes, diff --git a/src/runtime/composables/head.js b/src/runtime/composables/head.js index c23ad6167c..b2513ccfe9 100644 --- a/src/runtime/composables/head.js +++ b/src/runtime/composables/head.js @@ -62,14 +62,16 @@ const createEntry = (collection, debug) => { ({ fontCollection }) => fontCollection.size ); return { - link: prepareItems( - items - .filter(({ fontCollection }) => fontCollection.size) - .map(({ fontCollection, isCritical }) => - fontCollection.getPreloadDescriptions(isCritical) - ) - .flat() - ), + link: + (import.meta.server || process.env.prerender) && + prepareItems( + items + .filter(({ fontCollection }) => fontCollection.size) + .map(({ fontCollection, isCritical }) => + fontCollection.getPreloadDescriptions(isCritical) + ) + .flat() + ), style: prepareItems( items .map(({ fontCollection, options }) => From e2b75f299a40ad45811a3ca0841c7513a4678555 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Sun, 28 Apr 2024 17:39:16 +0200 Subject: [PATCH 09/13] fix(booster-layer): fix missing nojs message --- playground/components/InfoLayer.vue | 1 + playground/components/base/Button.vue | 36 +++++++++++-------- playground/components/base/LinkList.vue | 36 +++++++++---------- .../components/fragments/ScrollContainer.vue | 2 ++ src/runtime/components/BoosterLayer.vue | 5 +-- 5 files changed, 44 insertions(+), 36 deletions(-) diff --git a/playground/components/InfoLayer.vue b/playground/components/InfoLayer.vue index e59f0ddc42..a245883875 100644 --- a/playground/components/InfoLayer.vue +++ b/playground/components/InfoLayer.vue @@ -35,6 +35,7 @@ import { getStyleDescription } from '#booster/utils/description'; import BoosterLayer from '#booster/components/BoosterLayer'; import BaseButton from '@/components/base/Button'; + const { $getFont } = useBoosterFonts(); useHead({ diff --git a/playground/components/base/Button.vue b/playground/components/base/Button.vue index c33dca8127..ace5152fa0 100644 --- a/playground/components/base/Button.vue +++ b/playground/components/base/Button.vue @@ -1,6 +1,6 @@