Skip to content

Commit

Permalink
fix(v-font): new mechanics to write fonts in header
Browse files Browse the repository at this point in the history
  • Loading branch information
ThornWalli committed Apr 22, 2024
1 parent 0ac46c8 commit 8937852
Show file tree
Hide file tree
Showing 15 changed files with 194 additions and 34 deletions.
3 changes: 2 additions & 1 deletion docs/.vitepress/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ export default {
{
text: 'useBoosterComponentObserver',
link: '/composables/useBoosterComponentObserver'
}
},
{ text: '鈿狅笍 useBoosterHead', link: '/composables/useBoosterHead' }
]
},
{
Expand Down
13 changes: 13 additions & 0 deletions docs/src/composables/useBoosterHead.md
Original file line number Diff line number Diff line change
@@ -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.

:::
1 change: 0 additions & 1 deletion playground/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ useHead(
title: `${route.name} | nuxt-booster`,
meta: [
{
hid: 'description',
name: 'description',
content: `${route.name} - description`
}
Expand Down
1 change: 1 addition & 0 deletions playground/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export default defineNuxtConfig(async () => {
},

booster: {
debug: false,
// targetFormats: ['jpg|jpeg|png|gif'],
// densities: 'x1 x2',
detection: {
Expand Down
3 changes: 2 additions & 1 deletion src/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ export default defineNuxtModule({
'useBoosterComponentObserver',
'useBoosterCritical',
'useBoosterConfig',
'useBoosterFonts'
'useBoosterFonts',
'useBoosterHead'
].map(name => ({
name,
from: resolve(runtimeDir, 'composables/index')
Expand Down
14 changes: 14 additions & 0 deletions src/runtime/classes/Font.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
20 changes: 16 additions & 4 deletions src/runtime/classes/FontCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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()
)
]);
}
Expand All @@ -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())
};
}
}

Expand Down
32 changes: 14 additions & 18 deletions src/runtime/composables/fonts.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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,
Expand All @@ -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();
})
});
}
105 changes: 105 additions & 0 deletions src/runtime/composables/head.js
Original file line number Diff line number Diff line change
@@ -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()
}))
};
}
}
1 change: 1 addition & 0 deletions src/runtime/composables/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
19 changes: 13 additions & 6 deletions src/runtime/directives/vFont.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
},
Expand All @@ -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
Expand All @@ -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);
}
}
8 changes: 5 additions & 3 deletions src/runtime/utils/description.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,21 @@ export function getFontPreloadDescription(
};
}

export function getStyleDescription(children, noScript = false) {
export function getStyleDescription(children, noScript = false, key) {
if (noScript) {
return getNoScriptDescription(`<style>${children}</style>`);
return getNoScriptDescription(`<style>${children}</style>`, 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)
};
}
3 changes: 3 additions & 0 deletions src/runtime/utils/log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const logDebug = (...args) => {
console.log('[DEBUG][BOOSTER]:', ...args);
};
4 changes: 4 additions & 0 deletions src/tmpl/plugin.tmpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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),
Expand Down
1 change: 1 addition & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 8937852

Please sign in to comment.