1
1
import * as fs from 'node:fs'
2
+ import { readFile , writeFile } from 'node:fs/promises'
2
3
import {
3
4
type AddComponentOptions ,
4
5
addComponent ,
@@ -18,7 +19,7 @@ import type { SatoriOptions } from 'satori'
18
19
import { installNuxtSiteConfig } from 'nuxt-site-config-kit'
19
20
import { isDevelopment } from 'std-env'
20
21
import { hash } from 'ohash'
21
- import { relative } from 'pathe'
22
+ import { basename , join , relative } from 'pathe'
22
23
import type { ResvgRenderOptions } from '@resvg/resvg-js'
23
24
import type { SharpOptions } from 'sharp'
24
25
import { defu } from 'defu'
@@ -43,7 +44,7 @@ import { setupDevHandler } from './build/dev'
43
44
import { setupGenerateHandler } from './build/generate'
44
45
import { setupPrerenderHandler } from './build/prerender'
45
46
import { setupBuildHandler } from './build/build'
46
- import { checkLocalChrome , checkPlaywrightDependency , isUndefinedOrTruthy } from './util'
47
+ import { checkLocalChrome , checkPlaywrightDependency , downloadFont , isUndefinedOrTruthy } from './util'
47
48
import { normaliseFontInput } from './runtime/utils.pure'
48
49
49
50
export interface ModuleOptions {
@@ -119,6 +120,12 @@ export interface ModuleOptions {
119
120
* Manually modify the compatibility.
120
121
*/
121
122
compatibility ?: CompatibilityFlagEnvOverrides
123
+ /**
124
+ * Use an alternative host for downloading Google Fonts. This is used to support China where Google Fonts is blocked.
125
+ *
126
+ * When `true` is set will use `fonts.font.im`, otherwise will use a string as the host.
127
+ */
128
+ googleFontMirror ?: true | string
122
129
}
123
130
124
131
export interface ModuleHooks {
@@ -259,35 +266,43 @@ export default defineNuxtModule<ModuleOptions>({
259
266
if ( preset === 'cloudflare' || preset === 'cloudflare-module' ) {
260
267
config . fonts = config . fonts . filter ( ( f ) => {
261
268
if ( typeof f !== 'string' && f . path ) {
262
- logger . warn ( `The ${ f . name } :${ f . weight } font was skipped because remote fonts are not available in Cloudflare Workers, please use a Google font .` )
269
+ logger . warn ( `The ${ f . name } :${ f . weight } font was skipped because remote fonts are not available in Cloudflare Workers, please use a Google Font .` )
263
270
return false
264
271
}
265
272
return true
266
273
} )
267
274
}
268
- if ( preset === 'stackblitz' ) {
269
- // TODO maybe only for stackblitz, this will increase server bundle size
270
- config . fonts = config . fonts . map ( ( f ) => {
271
- if ( typeof f === 'string' && f . startsWith ( 'Inter:' ) ) {
272
- const [ _ , weight ] = f . split ( ':' )
273
- return {
274
- name : 'Inter' ,
275
- weight,
276
- // nuxt server assets
277
- key : `nuxt-og-image:fonts:inter-latin-ext-${ weight } -normal.woff` ,
275
+ const serverFontsDir = resolve ( './runtime/nitro/fonts' )
276
+ config . fonts = await Promise . all ( normaliseFontInput ( config . fonts )
277
+ . map ( async ( f ) => {
278
+ if ( ! f . key && ! f . path ) {
279
+ if ( await downloadFont ( f , serverFontsDir , config . googleFontMirror ) ) {
280
+ // move file to serverFontsDir
281
+ f . key = `nuxt-og-image:fonts:${ f . name } -${ f . weight } .ttf.base64`
278
282
}
279
283
}
284
+ else if ( f . path ) {
285
+ // move to assets folder as base64 and set key
286
+ const fontPath = join ( nuxt . options . rootDir , nuxt . options . dir . public , f . path )
287
+ const fontData = await readFile ( fontPath , 'base64' )
288
+ f . key = `nuxt-og-image:fonts:${ f . name } -${ f . weight } .ttf.base64`
289
+ await writeFile ( resolve ( serverFontsDir , `${ basename ( f . path ) } .base64` ) , fontData )
290
+ }
291
+ return f
292
+ } ) )
293
+ config . fonts = config . fonts . map ( ( f ) => {
294
+ if ( preset === 'stackblitz' ) {
280
295
if ( typeof f === 'string' || ( ! f . path && ! f . key ) ) {
281
296
logger . warn ( `The ${ typeof f === 'string' ? f : `${ f . name } :${ f . weight } ` } font was skipped because remote fonts are not available in StackBlitz, please use a local font.` )
282
297
return false
283
298
}
284
- return f
285
- } ) . filter ( Boolean ) as InputFontConfig [ ]
286
- nuxt . hooks . hook ( 'nitro:config' , ( nitroConfig ) => {
287
- nitroConfig . serverAssets = nitroConfig . serverAssets || [ ]
288
- nitroConfig . serverAssets ! . push ( { baseName : 'nuxt-og-image: fonts' , dir : resolve ( './runtime/server/assets' ) } )
289
- } )
290
- }
299
+ }
300
+ return f
301
+ } ) . filter ( Boolean ) as InputFontConfig [ ]
302
+
303
+ // bundle fonts within nitro runtime
304
+ nuxt . options . nitro . serverAssets = nuxt . options . nitro . serverAssets || [ ]
305
+ nuxt . options . nitro . serverAssets ! . push ( { baseName : 'nuxt-og-image:fonts' , dir : serverFontsDir } )
291
306
292
307
nuxt . options . experimental . componentIslands = true
293
308
@@ -420,7 +435,7 @@ export default defineNuxtModule<ModuleOptions>({
420
435
// need to sort by longest first so we don't replace the wrong part of the string
421
436
. sort ( ( a , b ) => b . length - a . length )
422
437
. reduce ( ( name , dir ) => {
423
- // only replace from the start of the string
438
+ // only replace from the start of the string
424
439
return name . replace ( new RegExp ( `^${ dir } ` ) , '' )
425
440
} , component . pascalName )
426
441
return ` '${ name } ': typeof import('${ relativeComponentPath } ')['default']`
0 commit comments