Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(web-fonts): add downloadLocally options
- Loading branch information
Showing
12 changed files
with
212 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** | ||
* Inspired by: | ||
* https://github.com/feat-agency/vite-plugin-webfont-dl/blob/master/src/downloader.ts | ||
*/ | ||
import { $fetch } from 'ofetch' | ||
|
||
const fontUrlRegex = /[-a-z0-9@:%_+.~#?&/=]+\.(?:woff2?|eot|ttf|otf|svg)/gi | ||
|
||
const defaultFontCssFilename = 'fonts.css' | ||
|
||
// eslint-disable-next-line node/prefer-global/process | ||
const isNode = typeof process !== 'undefined' && process.stdout && !process.versions.deno | ||
|
||
interface UseLocalFontOptions { | ||
downloadDir: string | ||
} | ||
|
||
export async function useLocalFont(css: string, { downloadDir }: UseLocalFontOptions) { | ||
if (!isNode) | ||
return | ||
|
||
const { resolve } = await import('node:path') | ||
const { writeFile, mkdir } = await import('node:fs/promises') | ||
|
||
await mkdir(downloadDir, { recursive: true }) | ||
|
||
// Download the fonts locally and update the font.css file | ||
for (const url of css.match(fontUrlRegex) || []) { | ||
const path = resolve(downloadDir, url.split('/').pop()!) | ||
await saveFont(url, path) | ||
css = css.replaceAll(url, path) | ||
} | ||
|
||
// Save the updated font.css file | ||
const fontCssPath = resolve(downloadDir, defaultFontCssFilename) | ||
await writeFile(fontCssPath, css) | ||
} | ||
|
||
async function fileExists(path: string) { | ||
const { stat } = await import('node:fs/promises') | ||
const isFile = (await stat(path).catch(() => undefined))?.isFile() | ||
return isFile | ||
} | ||
|
||
async function saveFont(url: string, path: string) { | ||
if (await fileExists(path)) | ||
return | ||
const { writeFile } = await import('node:fs/promises') | ||
const { Buffer } = await import('node:buffer') | ||
|
||
const response = await $fetch(url, { headers: { responseType: 'arraybuffer' } }) as ArrayBuffer | ||
const content = new Uint8Array(response) | ||
await writeFile(path, Buffer.from(content)) | ||
} | ||
|
||
export async function readFontCSS(downloadDir: string) { | ||
if (!isNode) | ||
return '' | ||
|
||
const { resolve } = await import('node:path') | ||
const { readFile } = await import('node:fs/promises') | ||
|
||
const fontCssPath = resolve(downloadDir, defaultFontCssFilename) | ||
if (!await fileExists(fontCssPath)) | ||
return '/* [preset-web-font] This code will be replaced with the local CSS once it is downloaded */' | ||
|
||
return await readFile(fontCssPath, { encoding: 'utf-8' }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import type { ResolvedWebFontMeta, WebFontsOptions } from './types' | ||
|
||
type UseRemoteFontOptions = Required<Pick<WebFontsOptions, 'inlineImports' | 'customFetch'>> | ||
|
||
export async function getRemoteFontsCSS(fontObject: { [k: string]: ResolvedWebFontMeta[] }, { inlineImports, customFetch }: UseRemoteFontOptions) { | ||
const fonts = Object.values(fontObject).flatMap(i => i) | ||
|
||
const importCache: Record<string, Promise<string>> = {} | ||
|
||
async function importUrl(url: string) { | ||
if (inlineImports) { | ||
if (!importCache[url]) { | ||
importCache[url] = customFetch(url).catch((e) => { | ||
console.error('Failed to fetch web fonts') | ||
console.error(e) | ||
// eslint-disable-next-line node/prefer-global/process | ||
if (typeof process !== 'undefined' && process.env.CI) | ||
throw e | ||
}) | ||
} | ||
return await importCache[url] | ||
} | ||
else { | ||
return `@import url('${url}');` | ||
} | ||
} | ||
|
||
const preflights: (string | undefined)[] = [] | ||
const enabledProviders = new Set(fonts.map(i => i.provider)) | ||
|
||
for (const provider of enabledProviders) { | ||
const fontsForProvider = fonts.filter(i => i.provider.name === provider.name) | ||
|
||
if (provider.getImportUrl) { | ||
const url = provider.getImportUrl(fontsForProvider) | ||
if (url) | ||
preflights.push(await importUrl(url)) | ||
} | ||
|
||
preflights.push(provider.getPreflight?.(fontsForProvider)) | ||
} | ||
|
||
return preflights.filter(Boolean).join('\n') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// eslint-disable-next-line node/prefer-global/process | ||
const isNode = typeof process !== 'undefined' && process.stdout && !process.versions.deno | ||
|
||
export async function resolveDownloadDir(downloadDir?: string | (() => Promise<string>)) { | ||
if (!isNode) | ||
return '' | ||
|
||
const { resolve } = await import('node:path') | ||
const { cwd } = await import('node:process') | ||
|
||
if (typeof downloadDir === 'function') | ||
return await downloadDir() | ||
else if (typeof downloadDir === 'string') | ||
return resolve(cwd(), downloadDir) | ||
return `${cwd()}/public/fonts` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import type { Plugin } from 'vite' | ||
import { type PreflightContext, type Preset, type UnocssPluginContext, resolvePreset } from '@unocss/core' | ||
Check failure on line 2 in packages/vite/src/web-fonts.ts GitHub Actions / lint
Check failure on line 2 in packages/vite/src/web-fonts.ts GitHub Actions / lint
Check failure on line 2 in packages/vite/src/web-fonts.ts GitHub Actions / lint
|
||
import { useLocalFont } from '../../preset-web-fonts/src/local-font' | ||
import { resolveDownloadDir } from '../../preset-web-fonts/src/util' | ||
import { getRemoteFontsCSS } from '../../preset-web-fonts/src/remote-font' | ||
|
||
// eslint-disable-next-line node/prefer-global/process | ||
const isNode = typeof process !== 'undefined' && process.stdout && !process.versions.deno | ||
|
||
export function createWebFontPlugins(ctx: UnocssPluginContext): Plugin[] { | ||
return [ | ||
{ | ||
name: `unocss:web-fonts-local:dev`, | ||
enforce: 'pre', | ||
apply: 'serve', | ||
async configureServer(_server) { | ||
if (!isNode) | ||
return | ||
|
||
const webFontPreset = lookupPreset(ctx, '@unocss/preset-web-fonts') | ||
|
||
if (!webFontPreset || !webFontPreset.options?.downloadLocally) | ||
return | ||
|
||
const { $fetch } = await import('ofetch') | ||
const fontCSS = await getRemoteFontsCSS(webFontPreset.options.fontObject, { inlineImports: true, customFetch: $fetch }) | ||
const downloadDir = await resolveDownloadDir(webFontPreset.options.downloadDir) | ||
await useLocalFont(fontCSS, { downloadDir }) | ||
}, | ||
}, | ||
] | ||
} | ||
|
||
function lookupPreset<P extends Preset<any>>(ctx: UnocssPluginContext, presetName: P['name']) { | ||
const preset: P | undefined = ctx.uno.config?.presets?.find(p => p.name === presetName) as any | ||
return preset | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<template> | ||
<div class=":uno: font-sans leading-1em"> | ||
<div class=":uno: font-serif leading-[1em]"> | ||
<Playground /> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters