mirror of
https://github.com/withastro/astro.git
synced 2025-03-10 23:01:26 -05:00
feat(fonts): better local provider (#13276)
This commit is contained in:
parent
868e2ab9ed
commit
8e89e807ed
5 changed files with 307 additions and 102 deletions
|
@ -2,7 +2,7 @@ import type * as unifont from 'unifont';
|
||||||
import type { LocalFontFamily } from '../types.js';
|
import type { LocalFontFamily } from '../types.js';
|
||||||
import { DEFAULTS } from '../constants.js';
|
import { DEFAULTS } from '../constants.js';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { extractFontType, type URLProxy } from '../utils.js';
|
import { extractFontType } from '../utils.js';
|
||||||
|
|
||||||
// https://fonts.nuxt.com/get-started/providers#local
|
// https://fonts.nuxt.com/get-started/providers#local
|
||||||
// https://github.com/nuxt/fonts/blob/main/src/providers/local.ts
|
// https://github.com/nuxt/fonts/blob/main/src/providers/local.ts
|
||||||
|
@ -14,21 +14,15 @@ type InitializedProvider = NonNullable<Awaited<ReturnType<unifont.Provider>>>;
|
||||||
|
|
||||||
type ResolveFontResult = NonNullable<Awaited<ReturnType<InitializedProvider['resolveFont']>>>;
|
type ResolveFontResult = NonNullable<Awaited<ReturnType<InitializedProvider['resolveFont']>>>;
|
||||||
|
|
||||||
interface Options {
|
|
||||||
root: URL;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ResolveOptions {
|
interface ResolveOptions {
|
||||||
proxyURL: URLProxy;
|
root: URL;
|
||||||
|
proxyURL: (value: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: dev watcher and ways to update during dev
|
export function resolveLocalFont(
|
||||||
export function createLocalProvider({ root }: Options) {
|
|
||||||
return {
|
|
||||||
resolveFont: async (
|
|
||||||
family: LocalFontFamily,
|
family: LocalFontFamily,
|
||||||
{ proxyURL }: ResolveOptions,
|
{ proxyURL, root }: ResolveOptions,
|
||||||
): Promise<ResolveFontResult> => {
|
): ResolveFontResult {
|
||||||
const fonts: ResolveFontResult['fonts'] = [];
|
const fonts: ResolveFontResult['fonts'] = [];
|
||||||
|
|
||||||
for (const src of family.src) {
|
for (const src of family.src) {
|
||||||
|
@ -51,6 +45,48 @@ export function createLocalProvider({ root }: Options) {
|
||||||
return {
|
return {
|
||||||
fonts,
|
fonts,
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
};
|
|
||||||
|
/**
|
||||||
|
* Orchestrates local font updates and deletions during development
|
||||||
|
*/
|
||||||
|
export class LocalFontsWatcher {
|
||||||
|
/**
|
||||||
|
* Watched fonts files
|
||||||
|
*/
|
||||||
|
#paths: Array<string>;
|
||||||
|
/**
|
||||||
|
* Action performed when a font file is updated
|
||||||
|
*/
|
||||||
|
#update: () => void;
|
||||||
|
|
||||||
|
constructor({ paths, update }: { paths: Array<string>; update: () => void }) {
|
||||||
|
this.#paths = paths;
|
||||||
|
this.#update = update;
|
||||||
|
}
|
||||||
|
|
||||||
|
#matches(path: string): boolean {
|
||||||
|
return this.#paths.includes(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to call whenever a file is updated
|
||||||
|
*/
|
||||||
|
onUpdate(path: string): void {
|
||||||
|
if (!this.#matches(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to call whenever a file is unlinked
|
||||||
|
*/
|
||||||
|
onUnlink(path: string): void {
|
||||||
|
if (!this.#matches(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: improve
|
||||||
|
throw new Error('File used for font deleted. Restore it or update your config');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,27 @@ export function createCache(storage: Storage) {
|
||||||
|
|
||||||
export type CacheHandler = ReturnType<typeof createCache>;
|
export type CacheHandler = ReturnType<typeof createCache>;
|
||||||
|
|
||||||
|
export interface ProxyURLOptions {
|
||||||
|
/**
|
||||||
|
* The original URL
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
/**
|
||||||
|
* Specifies how the hash is computed. Can be based on the value,
|
||||||
|
* a specific string for testing etc
|
||||||
|
*/
|
||||||
|
hashString: (value: string) => string;
|
||||||
|
/**
|
||||||
|
* Use the hook to save the associated value and hash, and possibly
|
||||||
|
* transform it (eg. apply a base)
|
||||||
|
*/
|
||||||
|
collect: (data: {
|
||||||
|
hash: string;
|
||||||
|
type: FontType;
|
||||||
|
value: string;
|
||||||
|
}) => string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The fonts data we receive contains urls or file paths we do no control.
|
* The fonts data we receive contains urls or file paths we do no control.
|
||||||
* However, we will emit font files ourselves so we store the original value
|
* However, we will emit font files ourselves so we store the original value
|
||||||
|
@ -83,24 +104,10 @@ export type CacheHandler = ReturnType<typeof createCache>;
|
||||||
* - `collect` will save the association of the original url and the new hash for later use
|
* - `collect` will save the association of the original url and the new hash for later use
|
||||||
* - the returned url will be `/_astro/fonts/<hash>.woff2`
|
* - the returned url will be `/_astro/fonts/<hash>.woff2`
|
||||||
*/
|
*/
|
||||||
export function createURLProxy({
|
export function proxyURL({ value, hashString, collect }: ProxyURLOptions): string {
|
||||||
hashString,
|
|
||||||
collect,
|
|
||||||
}: {
|
|
||||||
hashString: (value: string) => string;
|
|
||||||
collect: (data: {
|
|
||||||
hash: string;
|
|
||||||
type: FontType;
|
|
||||||
value: string;
|
|
||||||
}) => string;
|
|
||||||
}) {
|
|
||||||
return function proxyURL(value: string): string {
|
|
||||||
const type = extractFontType(value);
|
const type = extractFontType(value);
|
||||||
const hash = `${hashString(value)}.${type}`;
|
const hash = `${hashString(value)}.${type}`;
|
||||||
const url = collect({ hash, type, value });
|
const url = collect({ hash, type, value });
|
||||||
// Now that we collected the original url, we return our proxy so the consumer can override it
|
// Now that we collected the original url, we return our proxy so the consumer can override it
|
||||||
return url;
|
return url;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type URLProxy = ReturnType<typeof createURLProxy>;
|
|
||||||
|
|
|
@ -6,13 +6,14 @@ import type { FontFamily, FontProvider, FontType } from './types.js';
|
||||||
import xxhash from 'xxhash-wasm';
|
import xxhash from 'xxhash-wasm';
|
||||||
import { isAbsolute } from 'node:path';
|
import { isAbsolute } from 'node:path';
|
||||||
import { getClientOutputDirectory } from '../../prerender/utils.js';
|
import { getClientOutputDirectory } from '../../prerender/utils.js';
|
||||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
import {
|
import {
|
||||||
generateFontFace,
|
generateFontFace,
|
||||||
createCache,
|
createCache,
|
||||||
type CacheHandler,
|
type CacheHandler,
|
||||||
createURLProxy,
|
proxyURL,
|
||||||
extractFontType,
|
extractFontType,
|
||||||
|
type ProxyURLOptions,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import {
|
import {
|
||||||
DEFAULTS,
|
DEFAULTS,
|
||||||
|
@ -25,7 +26,7 @@ import { removeTrailingForwardSlash } from '@astrojs/internal-helpers/path';
|
||||||
import type { Logger } from '../../core/logger/core.js';
|
import type { Logger } from '../../core/logger/core.js';
|
||||||
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
|
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
|
||||||
import { createViteLoader } from '../../core/module-loader/vite.js';
|
import { createViteLoader } from '../../core/module-loader/vite.js';
|
||||||
import { createLocalProvider, LOCAL_PROVIDER_NAME } from './providers/local.js';
|
import { resolveLocalFont, LOCAL_PROVIDER_NAME, LocalFontsWatcher } from './providers/local.js';
|
||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { createStorage } from 'unstorage';
|
import { createStorage } from 'unstorage';
|
||||||
import fsLiteDriver from 'unstorage/drivers/fs-lite';
|
import fsLiteDriver from 'unstorage/drivers/fs-lite';
|
||||||
|
@ -152,7 +153,7 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
|
||||||
let isBuild: boolean;
|
let isBuild: boolean;
|
||||||
let cache: CacheHandler | null = null;
|
let cache: CacheHandler | null = null;
|
||||||
|
|
||||||
async function initialize({ resolveMod }: { resolveMod: ResolveMod }) {
|
async function initialize({ resolveMod, base }: { resolveMod: ResolveMod; base: URL }) {
|
||||||
const { h64ToString } = await xxhash();
|
const { h64ToString } = await xxhash();
|
||||||
|
|
||||||
const resolved = await resolveProviders({
|
const resolved = await resolveProviders({
|
||||||
|
@ -163,12 +164,7 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
|
||||||
|
|
||||||
const storage = createStorage({
|
const storage = createStorage({
|
||||||
driver: (fsLiteDriver as unknown as typeof fsLiteDriver.default)({
|
driver: (fsLiteDriver as unknown as typeof fsLiteDriver.default)({
|
||||||
base: fileURLToPath(
|
base: fileURLToPath(base),
|
||||||
// In dev, we cache fonts data in .astro so it can be easily inspected and cleared
|
|
||||||
isBuild
|
|
||||||
? new URL(CACHE_DIR, settings.config.cacheDir)
|
|
||||||
: new URL(CACHE_DIR, settings.dotAstroDir),
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -178,7 +174,6 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
|
||||||
resolved.map((e) => e.provider(e.config)),
|
resolved.map((e) => e.provider(e.config)),
|
||||||
{ storage },
|
{ storage },
|
||||||
);
|
);
|
||||||
const { resolveFont: resolveLocalFont } = createLocalProvider({ root: settings.config.root });
|
|
||||||
|
|
||||||
// We initialize shared variables here and reset them in buildEnd
|
// We initialize shared variables here and reset them in buildEnd
|
||||||
// to avoid locking memory
|
// to avoid locking memory
|
||||||
|
@ -187,17 +182,17 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
|
||||||
const preloadData: PreloadData = [];
|
const preloadData: PreloadData = [];
|
||||||
let css = '';
|
let css = '';
|
||||||
|
|
||||||
const proxyURL = createURLProxy({
|
// When going through the urls/filepaths returned by providers,
|
||||||
hashString: h64ToString,
|
// We save the hash and the associated original value so we can use
|
||||||
collect: ({ hash, type, value }) => {
|
// it in the vite middleware during development
|
||||||
|
const collect: ProxyURLOptions['collect'] = ({ hash, type, value }) => {
|
||||||
const url = baseUrl + hash;
|
const url = baseUrl + hash;
|
||||||
if (!hashToUrlMap!.has(hash)) {
|
if (!hashToUrlMap!.has(hash)) {
|
||||||
hashToUrlMap!.set(hash, value);
|
hashToUrlMap!.set(hash, value);
|
||||||
preloadData.push({ url, type });
|
preloadData.push({ url, type });
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: investigate using fontaine for fallbacks
|
// TODO: investigate using fontaine for fallbacks
|
||||||
for (const family of families) {
|
for (const family of families) {
|
||||||
|
@ -206,7 +201,26 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
|
||||||
css = '';
|
css = '';
|
||||||
|
|
||||||
if (family.provider === LOCAL_PROVIDER_NAME) {
|
if (family.provider === LOCAL_PROVIDER_NAME) {
|
||||||
const { fonts, fallbacks } = await resolveLocalFont(family, { proxyURL });
|
const { fonts, fallbacks } = resolveLocalFont(family, {
|
||||||
|
proxyURL: (value) => {
|
||||||
|
return proxyURL({
|
||||||
|
value,
|
||||||
|
// We hash based on the filepath and the contents, since the user could replace
|
||||||
|
// a given font file with completely different contents.
|
||||||
|
hashString: (v) => {
|
||||||
|
let content: string;
|
||||||
|
try {
|
||||||
|
content = readFileSync(value, 'utf-8');
|
||||||
|
} catch (e) {
|
||||||
|
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: e });
|
||||||
|
}
|
||||||
|
return h64ToString(v + content);
|
||||||
|
},
|
||||||
|
collect,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
root: settings.config.root,
|
||||||
|
});
|
||||||
for (const data of fonts) {
|
for (const data of fonts) {
|
||||||
css += generateFontFace(family.name, data);
|
css += generateFontFace(family.name, data);
|
||||||
}
|
}
|
||||||
|
@ -230,7 +244,12 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
|
||||||
if ('name' in source) {
|
if ('name' in source) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
source.url = proxyURL(source.url);
|
source.url = proxyURL({
|
||||||
|
value: source.url,
|
||||||
|
// We only use the url for hashing since the service returns urls with a hash already
|
||||||
|
hashString: h64ToString,
|
||||||
|
collect,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// TODO: support optional as prop
|
// TODO: support optional as prop
|
||||||
css += generateFontFace(family.name, data);
|
css += generateFontFace(family.name, data);
|
||||||
|
@ -250,6 +269,7 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
|
||||||
if (isBuild) {
|
if (isBuild) {
|
||||||
await initialize({
|
await initialize({
|
||||||
resolveMod: (id) => import(id),
|
resolveMod: (id) => import(id),
|
||||||
|
base: new URL(CACHE_DIR, settings.config.cacheDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -257,7 +277,23 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
|
||||||
const moduleLoader = createViteLoader(server);
|
const moduleLoader = createViteLoader(server);
|
||||||
await initialize({
|
await initialize({
|
||||||
resolveMod: (id) => moduleLoader.import(id),
|
resolveMod: (id) => moduleLoader.import(id),
|
||||||
|
// In dev, we cache fonts data in .astro so it can be easily inspected and cleared
|
||||||
|
base: new URL(CACHE_DIR, settings.dotAstroDir),
|
||||||
});
|
});
|
||||||
|
const localFontsWatcher = new LocalFontsWatcher({
|
||||||
|
// The map is always defined at this point. Its values contains urls from remote providers
|
||||||
|
// as well as local paths for the local provider. We filter them to only keep the filepaths
|
||||||
|
paths: [...hashToUrlMap!.values()].filter((url) => isAbsolute(url)),
|
||||||
|
// Whenever a local font file is updated, we restart the server so the user always has an up to date
|
||||||
|
// version of the font file
|
||||||
|
update: () => {
|
||||||
|
logger.info('assets', 'Font file updated');
|
||||||
|
server.restart();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
server.watcher.on('change', (path) => localFontsWatcher.onUpdate(path));
|
||||||
|
// We do not purge the cache in case the user wants to re-use the file later on
|
||||||
|
server.watcher.on('unlink', (path) => localFontsWatcher.onUnlink(path));
|
||||||
|
|
||||||
const logManager = createLogManager(logger);
|
const logManager = createLogManager(logger);
|
||||||
// Base is taken into account by default. The prefix contains a traling slash,
|
// Base is taken into account by default. The prefix contains a traling slash,
|
||||||
|
|
|
@ -3,11 +3,45 @@ import { describe, it } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { fontProviders } from '../../../../dist/config/entrypoint.js';
|
import { fontProviders } from '../../../../dist/config/entrypoint.js';
|
||||||
import { google } from '../../../../dist/assets/fonts/providers/google.js';
|
import { google } from '../../../../dist/assets/fonts/providers/google.js';
|
||||||
|
import {
|
||||||
|
LocalFontsWatcher,
|
||||||
|
resolveLocalFont,
|
||||||
|
} from '../../../../dist/assets/fonts/providers/local.js';
|
||||||
import * as adobeEntrypoint from '../../../../dist/assets/fonts/providers/entrypoints/adobe.js';
|
import * as adobeEntrypoint from '../../../../dist/assets/fonts/providers/entrypoints/adobe.js';
|
||||||
import * as bunnyEntrypoint from '../../../../dist/assets/fonts/providers/entrypoints/bunny.js';
|
import * as bunnyEntrypoint from '../../../../dist/assets/fonts/providers/entrypoints/bunny.js';
|
||||||
import * as fontshareEntrypoint from '../../../../dist/assets/fonts/providers/entrypoints/fontshare.js';
|
import * as fontshareEntrypoint from '../../../../dist/assets/fonts/providers/entrypoints/fontshare.js';
|
||||||
import * as fontsourceEntrypoint from '../../../../dist/assets/fonts/providers/entrypoints/fontsource.js';
|
import * as fontsourceEntrypoint from '../../../../dist/assets/fonts/providers/entrypoints/fontsource.js';
|
||||||
import { validateMod, resolveProviders } from '../../../../dist/assets/fonts/providers/utils.js';
|
import { validateMod, resolveProviders } from '../../../../dist/assets/fonts/providers/utils.js';
|
||||||
|
import { proxyURL } from '../../../../dist/assets/fonts/utils.js';
|
||||||
|
import { basename, extname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Parameters<typeof resolveLocalFont>[0]} family
|
||||||
|
* @param {URL} root
|
||||||
|
*/
|
||||||
|
function resolveLocalFontSpy(family, root) {
|
||||||
|
/** @type {Array<string>} */
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
const { fonts } = resolveLocalFont(family, {
|
||||||
|
proxyURL: (v) =>
|
||||||
|
proxyURL({
|
||||||
|
value: v,
|
||||||
|
hashString: (value) => basename(value, extname(value)),
|
||||||
|
collect: ({ hash, value }) => {
|
||||||
|
values.push(value);
|
||||||
|
return `/_astro/fonts/${hash}`;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
root,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
fonts,
|
||||||
|
values: [...new Set(values)],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe('fonts providers', () => {
|
describe('fonts providers', () => {
|
||||||
describe('config objects', () => {
|
describe('config objects', () => {
|
||||||
|
@ -32,7 +66,6 @@ describe('fonts providers', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('entrypoints', () => {
|
|
||||||
it('providers are correctly exported', () => {
|
it('providers are correctly exported', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
'provider' in adobeEntrypoint && typeof adobeEntrypoint.provider === 'function',
|
'provider' in adobeEntrypoint && typeof adobeEntrypoint.provider === 'function',
|
||||||
|
@ -51,9 +84,102 @@ describe('fonts providers', () => {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('resolveLocalFont()', () => {
|
||||||
|
const root = new URL(import.meta.url);
|
||||||
|
|
||||||
|
let { fonts, values } = resolveLocalFontSpy(
|
||||||
|
{
|
||||||
|
name: 'Custom',
|
||||||
|
provider: 'local',
|
||||||
|
src: [
|
||||||
|
{
|
||||||
|
paths: ['./src/fonts/foo.woff2', './src/fonts/foo.ttf'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(fonts, [
|
||||||
|
{
|
||||||
|
weight: '400',
|
||||||
|
style: 'normal',
|
||||||
|
src: [
|
||||||
|
{ url: '/_astro/fonts/foo.woff2', format: 'woff2' },
|
||||||
|
{ url: '/_astro/fonts/foo.ttf', format: 'ttf' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
weight: '400',
|
||||||
|
style: 'italic',
|
||||||
|
src: [
|
||||||
|
{ url: '/_astro/fonts/foo.woff2', format: 'woff2' },
|
||||||
|
{ url: '/_astro/fonts/foo.ttf', format: 'ttf' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.deepStrictEqual(values, [
|
||||||
|
fileURLToPath(new URL('./src/fonts/foo.woff2', root)),
|
||||||
|
fileURLToPath(new URL('./src/fonts/foo.ttf', root)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
({ fonts, values } = resolveLocalFontSpy(
|
||||||
|
{
|
||||||
|
name: 'Custom',
|
||||||
|
provider: 'local',
|
||||||
|
src: [
|
||||||
|
{
|
||||||
|
weights: ['600', '700'],
|
||||||
|
styles: ['oblique'],
|
||||||
|
paths: ['./src/fonts/bar.eot'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
root,
|
||||||
|
));
|
||||||
|
|
||||||
|
assert.deepStrictEqual(fonts, [
|
||||||
|
{
|
||||||
|
weight: '600',
|
||||||
|
style: 'oblique',
|
||||||
|
src: [{ url: '/_astro/fonts/bar.eot', format: 'eot' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
weight: '700',
|
||||||
|
style: 'oblique',
|
||||||
|
src: [{ url: '/_astro/fonts/bar.eot', format: 'eot' }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.deepStrictEqual(values, [fileURLToPath(new URL('./src/fonts/bar.eot', root))]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('LocalFontsWatcher', () => {
|
||||||
|
let updated = 0;
|
||||||
|
const watcher = new LocalFontsWatcher({
|
||||||
|
paths: ['foo', 'bar'],
|
||||||
|
update: () => {
|
||||||
|
updated++;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.onUpdate('baz');
|
||||||
|
assert.equal(updated, 0);
|
||||||
|
|
||||||
|
watcher.onUpdate('foo');
|
||||||
|
watcher.onUpdate('bar');
|
||||||
|
assert.equal(updated, 2);
|
||||||
|
|
||||||
|
assert.doesNotThrow(() => watcher.onUnlink('baz'));
|
||||||
|
try {
|
||||||
|
watcher.onUnlink('foo');
|
||||||
|
assert.fail();
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err instanceof Error, true);
|
||||||
|
assert.equal(err.message, 'File used for font deleted. Restore it or update your config');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: test local provider
|
|
||||||
describe('utils', () => {
|
describe('utils', () => {
|
||||||
it('validateMod()', () => {
|
it('validateMod()', () => {
|
||||||
const provider = () => {};
|
const provider = () => {};
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
isFontType,
|
isFontType,
|
||||||
extractFontType,
|
extractFontType,
|
||||||
createCache,
|
createCache,
|
||||||
createURLProxy,
|
proxyURL,
|
||||||
} from '../../../../dist/assets/fonts/utils.js';
|
} from '../../../../dist/assets/fonts/utils.js';
|
||||||
|
|
||||||
function createSpyCache() {
|
function createSpyCache() {
|
||||||
|
@ -43,16 +43,16 @@ function createSpyCache() {
|
||||||
* @param {string} value
|
* @param {string} value
|
||||||
*/
|
*/
|
||||||
function proxyURLSpy(id, value) {
|
function proxyURLSpy(id, value) {
|
||||||
/** @type {Parameters<Parameters<typeof createURLProxy>[0]['collect']>[0]} */
|
/** @type {Parameters<import('../../../../dist/assets/fonts/utils.js').ProxyURLOptions['collect']>[0]} */
|
||||||
let collected = /** @type {any} */ (undefined);
|
let collected = /** @type {any} */ (undefined);
|
||||||
const proxyURL = createURLProxy({
|
const url = proxyURL({
|
||||||
|
value,
|
||||||
hashString: () => id,
|
hashString: () => id,
|
||||||
collect: (data) => {
|
collect: (data) => {
|
||||||
collected = data;
|
collected = data;
|
||||||
return 'base/' + data.hash;
|
return 'base/' + data.hash;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const url = proxyURL(value);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url,
|
url,
|
||||||
|
@ -122,7 +122,7 @@ describe('fonts utils', () => {
|
||||||
assert.deepStrictEqual(getKeys(), ['foo']);
|
assert.deepStrictEqual(getKeys(), ['foo']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createURLProxy()', () => {
|
it('proxyURL()', () => {
|
||||||
let { url, collected } = proxyURLSpy(
|
let { url, collected } = proxyURLSpy(
|
||||||
'foo',
|
'foo',
|
||||||
'https://fonts.gstatic.com/s/roboto/v47/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkC3kaSTbQWt4N.woff2',
|
'https://fonts.gstatic.com/s/roboto/v47/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkC3kaSTbQWt4N.woff2',
|
||||||
|
|
Loading…
Add table
Reference in a new issue