refactor/code-splitting #31
@@ -4,6 +4,7 @@ import {
|
|||||||
type FontLoadStatus,
|
type FontLoadStatus,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
|
generateFontKey,
|
||||||
getEffectiveConcurrency,
|
getEffectiveConcurrency,
|
||||||
loadFont,
|
loadFont,
|
||||||
yieldToMainThread,
|
yieldToMainThread,
|
||||||
@@ -81,11 +82,6 @@ export class AppliedFontsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates font key: `{id}@vf` for variable, `{id}@{weight}` for static.
|
|
||||||
#getFontKey(id: string, weight: number, isVariable: boolean): string {
|
|
||||||
return isVariable ? `${id.toLowerCase()}@vf` : `${id.toLowerCase()}@${weight}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests fonts to be loaded. Updates usage tracking and queues new fonts.
|
* Requests fonts to be loaded. Updates usage tracking and queues new fonts.
|
||||||
*
|
*
|
||||||
@@ -93,34 +89,40 @@ export class AppliedFontsManager {
|
|||||||
* Scheduling: Prefers requestIdleCallback (150ms timeout), falls back to setTimeout(16ms).
|
* Scheduling: Prefers requestIdleCallback (150ms timeout), falls back to setTimeout(16ms).
|
||||||
*/
|
*/
|
||||||
touch(configs: FontLoadRequestConfig[]) {
|
touch(configs: FontLoadRequestConfig[]) {
|
||||||
if (this.#abortController.signal.aborted) return;
|
if (this.#abortController.signal.aborted) {
|
||||||
|
return;
|
||||||
const now = Date.now();
|
|
||||||
let hasNewItems = false;
|
|
||||||
|
|
||||||
for (const config of configs) {
|
|
||||||
const key = this.#getFontKey(config.id, config.weight, !!config.isVariable);
|
|
||||||
this.#usageTracker.set(key, now);
|
|
||||||
|
|
||||||
const status = this.statuses.get(key);
|
|
||||||
if (status === 'loaded' || status === 'loading' || this.#queue.has(key)) continue;
|
|
||||||
if (status === 'error' && (this.#retryCounts.get(key) ?? 0) >= this.#MAX_RETRIES) continue;
|
|
||||||
|
|
||||||
this.#queue.set(key, config);
|
|
||||||
hasNewItems = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasNewItems && !this.#timeoutId) {
|
try {
|
||||||
if (typeof requestIdleCallback !== 'undefined') {
|
const now = Date.now();
|
||||||
this.#timeoutId = requestIdleCallback(
|
let hasNewItems = false;
|
||||||
() => this.#processQueue(),
|
|
||||||
{ timeout: 150 },
|
for (const config of configs) {
|
||||||
) as unknown as ReturnType<typeof setTimeout>;
|
const key = generateFontKey(config);
|
||||||
this.#pendingType = 'idle';
|
this.#usageTracker.set(key, now);
|
||||||
} else {
|
|
||||||
this.#timeoutId = setTimeout(() => this.#processQueue(), 16);
|
const status = this.statuses.get(key);
|
||||||
this.#pendingType = 'timeout';
|
if (status === 'loaded' || status === 'loading' || this.#queue.has(key)) continue;
|
||||||
|
if (status === 'error' && (this.#retryCounts.get(key) ?? 0) >= this.#MAX_RETRIES) continue;
|
||||||
|
|
||||||
|
this.#queue.set(key, config);
|
||||||
|
hasNewItems = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasNewItems && !this.#timeoutId) {
|
||||||
|
if (typeof requestIdleCallback !== 'undefined') {
|
||||||
|
this.#timeoutId = requestIdleCallback(
|
||||||
|
() => this.#processQueue(),
|
||||||
|
{ timeout: 150 },
|
||||||
|
) as unknown as ReturnType<typeof setTimeout>;
|
||||||
|
this.#pendingType = 'idle';
|
||||||
|
} else {
|
||||||
|
this.#timeoutId = setTimeout(() => this.#processQueue(), 16);
|
||||||
|
this.#pendingType = 'timeout';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,17 +276,32 @@ export class AppliedFontsManager {
|
|||||||
|
|
||||||
/** Returns current loading status for a font, or undefined if never requested. */
|
/** Returns current loading status for a font, or undefined if never requested. */
|
||||||
getFontStatus(id: string, weight: number, isVariable = false) {
|
getFontStatus(id: string, weight: number, isVariable = false) {
|
||||||
return this.statuses.get(this.#getFontKey(id, weight, isVariable));
|
try {
|
||||||
|
const key = generateFontKey({ id, weight, isVariable });
|
||||||
|
return this.statuses.get(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Pins a font so it is never evicted by #purgeUnused(), regardless of TTL. */
|
/** Pins a font so it is never evicted by #purgeUnused(), regardless of TTL. */
|
||||||
pin(id: string, weight: number, isVariable?: boolean): void {
|
pin(id: string, weight: number, isVariable?: boolean): void {
|
||||||
this.#pinnedFonts.add(this.#getFontKey(id, weight, !!isVariable));
|
try {
|
||||||
|
const key = generateFontKey({ id, weight, isVariable: !!isVariable });
|
||||||
|
this.#pinnedFonts.add(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Unpins a font, allowing it to be evicted by #purgeUnused() once its TTL expires. */
|
/** Unpins a font, allowing it to be evicted by #purgeUnused() once its TTL expires. */
|
||||||
unpin(id: string, weight: number, isVariable?: boolean): void {
|
unpin(id: string, weight: number, isVariable?: boolean): void {
|
||||||
this.#pinnedFonts.delete(this.#getFontKey(id, weight, !!isVariable));
|
try {
|
||||||
|
const key = generateFontKey({ id, weight, isVariable: !!isVariable });
|
||||||
|
this.#pinnedFonts.delete(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Waits for all fonts to finish loading using document.fonts.ready. */
|
/** Waits for all fonts to finish loading using document.fonts.ready. */
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { generateFontKey } from './generateFontKey';
|
||||||
|
|
||||||
|
describe('generateFontKey', () => {
|
||||||
|
it('should throw an error if font id is not provided', () => {
|
||||||
|
const config = { weight: 400, isVariable: false };
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => generateFontKey(config)).toThrow('Font id is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a font key for a variable font', () => {
|
||||||
|
const config = { id: 'Roboto', weight: 400, isVariable: true };
|
||||||
|
expect(generateFontKey(config)).toBe('roboto@vf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if font weight is not provided and is not a variable font', () => {
|
||||||
|
const config = { id: 'Roboto', isVariable: false };
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => generateFontKey(config)).toThrow('Font weight is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a font key for a non-variable font', () => {
|
||||||
|
const config = { id: 'Roboto', weight: 400, isVariable: false };
|
||||||
|
expect(generateFontKey(config)).toBe('roboto@400');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { FontLoadRequestConfig } from '../../../../types';
|
||||||
|
|
||||||
|
export type PartialConfig = Pick<FontLoadRequestConfig, 'id' | 'weight' | 'isVariable'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a font key for a given font load request configuration.
|
||||||
|
* @param config - The font load request configuration.
|
||||||
|
* @returns The generated font key.
|
||||||
|
*/
|
||||||
|
export function generateFontKey(config: PartialConfig): string {
|
||||||
|
if (!config.id) {
|
||||||
|
throw new Error('Font id is required');
|
||||||
|
}
|
||||||
|
if (config.isVariable) {
|
||||||
|
return `${config.id.toLowerCase()}@vf`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.weight) {
|
||||||
|
throw new Error('Font weight is required');
|
||||||
|
}
|
||||||
|
return `${config.id.toLowerCase()}@${config.weight}`;
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export { generateFontKey } from './generateFontKey/generateFontKey';
|
||||||
export { getEffectiveConcurrency } from './getEffectiveConcurrency/getEffectiveConcurrency';
|
export { getEffectiveConcurrency } from './getEffectiveConcurrency/getEffectiveConcurrency';
|
||||||
export { loadFont } from './loadFont/loadFont';
|
export { loadFont } from './loadFont/loadFont';
|
||||||
export { yieldToMainThread } from './yieldToMainThread/yieldToMainThread';
|
export { yieldToMainThread } from './yieldToMainThread/yieldToMainThread';
|
||||||
|
|||||||
Reference in New Issue
Block a user