En ~1 mes, astro-font
ha aumentado a 57.000 descargas 🤯
Punto de control n.° 1
Todo comenzó cuando pensé en lo que le falta al ecosistema Astro. Después de revisar algunos de los sitios web (incluido mi propio sitio web híbrido Astro, launchfast), descubrí que faltaba una biblioteca de optimización de fuentes de extremo a extremo 👇
Punto de control #2
Así que me propuse crear un paquete Astro que cumpliera la promesa que pensé que sería súper fácil: usar los scripts next/font
de @vercel y enviarlo (¡y lo hice desde el principio!) ¡Funcionó muy bien para sitios web estáticos! PERO, ingrese a los sitios web de SSR👇
Barricada #1
next/font
, al estar acoplado con el proceso de compilación de Next, tiene acceso a los directorios de salida y a la configuración de tiempo de ejecución esperada y, por lo tanto, aloja automáticamente las fuentes incluso en los sitios web SSR-first. Este espacio se volvió más complejo para los sitios web de Astro SSR a medida que astro- font
es un componente de Astro y no una integración de Astro. Esto es lo que hice para resolver ese problema 👇
async function getOS(): Promise<typeof import('node:os') | undefined> { let os try { os = await import('node:os') return os } catch (e) {}}
// Check if writing is permitted by the file systemasync function ifFSOSWrites(dir: string): Promise<string | undefined> { try { const fs = await getFS() if (fs) { const testDir = join(dir, '.astro_font') if (!fs.existsSync(testDir)) fs.mkdirSync(testDir) fs.rmSync(testDir, { recursive: true, force: true }) return dir } } catch (e) {}}
Barricada #2
¡Excelente! Eso funcionó y me permitió determinar si la compilación SSR tenía fuentes incluidas y, por lo tanto, me permitió calcular la fuente alternativa en el tiempo de ejecución, PERO, algunos usuarios querían usar URL de CDN o estaban usando fuentes de fuente. No había forma de saberlo. qué Vite resolvió las fuentes internas. Por lo tanto, construí un analizador CSS en tiempo de ejecución similar a Google Fonts 👇
// Custom script to parseGoogleCSSfunction parseGoogleCSS(tmp: string) { let match const fontFaceMatches = [] const fontFaceRegex = /@font-face\s*{([^}]+)}/g while ((match = fontFaceRegex.exec(tmp)) !== null) { const fontFaceRule = match[1] const fontFaceObject: any = {} fontFaceRule.split(';').forEach((property) => { if (property.includes('src: ')) { const formatPosition = property.indexOf('for') fontFaceObject['path'] = property .trim() .substring(9, formatPosition ? formatPosition - 5 : property.length - 1) .trim() } if (property.includes('-style: ')) { fontFaceObject['style'] = property.split(':').map((i) => i.trim())[1] } if (property.includes('-weight: ')) { fontFaceObject['weight'] = property.split(':').map((i) => i.trim())[1] } if (property.includes('unicode-range: ')) { if (!fontFaceObject['css']) { fontFaceObject['css'] = {} } fontFaceObject['css']['unicode-range'] = property.split(':').map((i) => i.trim())[1] } }) fontFaceMatches.push(fontFaceObject) } return fontFaceMatches}
Punto de control n.º 3
Parece completo, ¿verdad? Ahora funciona con fuentes locales y fuentes a través de CDN, pero la búsqueda y computación en tiempo de ejecución nos costará tiempo SSR. Para resolver eso, ingrese el almacenamiento en caché de fuentes en tiempo de ejecución 👇
const [os, fs] = await Promise.all([getOS(), getFS()])if (fs) { if (os) { writeAllowed = await Promise.all([ifFSOSWrites(os.tmpdir()), ifFSOSWrites('/tmp')]) tmpDir = writeAllowed.find((i) => i !== undefined) cacheDir = fontCollection.cacheDir || tmpDir if (cacheDir) { // Create a json based on slugified path, style and weight const slugifyPath = (i: Source) => `${i.path}_${i.style}_${i.weight}` const slugifiedCollection = fontCollection.src.map(slugifyPath) const cachedFileName = simpleHash(slugifiedCollection.join('_')) + '.txt' cachedFilePath = join(cacheDir, cachedFileName) if (fs.existsSync(cachedFilePath)) { try { const tmpCachedFilePath = fs.readFileSync(cachedFilePath, 'utf8') return JSON.parse(tmpCachedFilePath) } catch (errorReadingCache) {} } } }}
Punto de control #4
¿Ahora? ¡Solo nos queda una cosa por hacer: Permitir precargas por fuente por configuración (y soporte hacia atrás para precargas globales (no))!
// If the parent preload is set to be false, look for true only preload valuesif (fontCollection.preload === false) { return fontCollection.src .filter((i) => i.preload === true) .map((i) => getRelativePath(getBasePath(fontCollection.basePath), i.path))}
// If the parent preload is set to be true (or not defined), look for non-false valuesreturn fontCollection.src .filter((i) => i.preload !== false) .map((i) => getRelativePath(getBasePath(fontCollection.basePath), i.path))
Y terminamos, y está en producción para muchos sitios web de Astro ✨
<typeof import(‘node:os’) | undefined><string | undefined>¡Gracias por ese increíble paquete que me ayudó a luchar contra el “cambio de diseño”!
Lo agregaré a Best of JS, ya que tenemos una etiquetaAstro
[[ HTML_TAG]]https://tco/38Akqa5IQd[[HTML_TAG] ]
— Michael Rambeau (@michaelrambeau) 17 de enero de 2024







