diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index de09f3a..55e5810 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -6,8 +6,12 @@ export default defineNuxtConfig({ ], googleFonts: { families: { - Roboto: true, + Roboto: { + wght: [100, 400], + text: 'Roboto' + }, Mulish: true - } + }, + text: 'Hello World' } }) diff --git a/src/module.ts b/src/module.ts index af928e7..6ae8586 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,10 +1,11 @@ import { resolve } from 'pathe' import type { MetaObject } from '@nuxt/schema' import { defineNuxtModule, isNuxt2, resolvePath, useLogger } from '@nuxt/kit' -import { constructURL, download, isValidURL, parse, merge, DownloadOptions, GoogleFonts } from 'google-fonts-helper' +import { constructURL, download, isValidURL, parse, merge, DownloadOptions, GoogleFonts, FamilyWeight, FamilyStyles } from 'google-fonts-helper' import { name, version } from '../package.json' type NuxtAppHead = Required +type FamilyStylesExtended = FamilyStyles & { text?: string } export interface ModuleOptions extends DownloadOptions, GoogleFonts { prefetch?: boolean @@ -41,7 +42,7 @@ export default defineNuxtModule({ fontsDir: 'fonts', fontsPath: '../fonts' }, - async setup (options, nuxt) { + async setup(options, nuxt) { // If user hasn't set the display value manually and isn't using // a preload, set the default display value to 'swap' if (options.display === undefined && !options.preload) { @@ -65,12 +66,30 @@ export default defineNuxtModule({ .map(link => parse(String(link.href))) ) + // construct google fonts url - const url = constructURL(merge(options, ...fontsParsed)) + const fontOptionsList = Object.entries(merge(options, ...fontsParsed).families!).map(([font, value]: [string, FamilyStylesExtended|FamilyWeight] ) => { + let currentOptions = options + if (typeof value === 'object' && !Array.isArray(value) && value.text) { + value + currentOptions = { + ...currentOptions, + text: value.text + } + delete value.text + } - if (!url) { - logger.warn('No provided fonts.') + return { + ...currentOptions, + families: { [font]: value }, + } + }); + + const urls = fontOptionsList.map((result) => constructURL(result)).filter((url) => url !== false) as string[]; + + if (!urls.length) { + logger.warn('No provided fonts.') return } @@ -80,36 +99,39 @@ export default defineNuxtModule({ // download if (options.download) { const outputDir = await resolvePath(options.outputDir) + const outputFonts: string[] = [] try { - const downloader = download(url, { - base64: options.base64, - overwriting: options.overwriting, - outputDir, - stylePath: options.stylePath, - fontsDir: options.fontsDir, - fontsPath: options.fontsPath - }) - - const outputFonts: string[] = [] - - downloader.hook('download-css:done', (url) => { - logger.success(url) - }) - - downloader.hook('download-font:done', (font) => { - const fontName = font.outputFont.replace(`-${font.outputFont.replace(/.*-/, '')}`, '') - - if (!outputFonts.includes(fontName)) { - outputFonts.push(fontName) - logger.info(fontName) - } - }) + for (const url of urls) { + const downloader = download(url, { + base64: options.base64, + overwriting: options.overwriting, + outputDir, + stylePath: options.stylePath, + fontsDir: options.fontsDir, + fontsPath: options.fontsPath + }) + + + downloader.hook('download-css:done', (url) => { + logger.success(url) + }) + + downloader.hook('download-font:done', (font) => { + const fontName = font.outputFont.replace(`-${font.outputFont.replace(/.*-/, '')}`, '') + + if (!outputFonts.includes(fontName)) { + outputFonts.push(fontName) + logger.info(fontName) + } + }) + + logger.start('Downloading fonts...') + await downloader.execute() + logger.success('Download fonts completed.') + logger.log('') + } - logger.start('Downloading fonts...') - await downloader.execute() - logger.success('Download fonts completed.') - logger.log('') if (options.inject) { nuxt.options.css.push(resolve(outputDir, options.stylePath)) @@ -158,66 +180,73 @@ export default defineNuxtModule({ // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload // optionally increase loading priority if (options.preload) { - head.link.push({ - key: 'gf-preload', - rel: 'preload', - as: 'style', - href: url - }) + for (const url of urls) { + + head.link.push({ + key: 'gf-preload', + rel: 'preload', + as: 'style', + href: url + }) + } } // append CSS if (options.useStylesheet) { - head.link.push({ - key: 'gf-style', - rel: 'stylesheet', - href: url - }) - + for (const url of urls) { + head.link.push({ + key: 'gf-style', + rel: 'stylesheet', + href: url + }) + } return } if (isNuxt2()) { // JS to inject CSS - head.script.push({ + for (const url of urls) { + head.script.push({ + key: 'gf-script', + innerHTML: `(function(){var l=document.createElement('link');l.rel="stylesheet";l.href="${url}";document.querySelector("head").appendChild(l);})();` + }) + + // no-JS fallback + head.noscript.push({ + key: 'gf-noscript', + innerHTML: ``, + tagPosition: 'bodyOpen' + }) + + // Disable sanitazions + // @ts-ignore + head.__dangerouslyDisableSanitizersByTagID = head.__dangerouslyDisableSanitizersByTagID || {} + // @ts-ignore + head.__dangerouslyDisableSanitizersByTagID['gf-script'] = ['innerHTML'] + // @ts-ignore + head.__dangerouslyDisableSanitizersByTagID['gf-noscript'] = ['innerHTML'] + } + return + } + + // JS to inject CSS + for (const url of urls) { + head.script.unshift({ key: 'gf-script', - innerHTML: `(function(){var l=document.createElement('link');l.rel="stylesheet";l.href="${url}";document.querySelector("head").appendChild(l);})();` + children: `(function(){ + var h=document.querySelector("head"); + var m=h.querySelector('meta[name="head:count"]'); + if(m){m.setAttribute('content',Number(m.getAttribute('content'))+1);} + else{m=document.createElement('meta');m.setAttribute('name','head:count');m.setAttribute('content','1');h.append(m);} + var l=document.createElement('link');l.rel='stylesheet';l.href='${url}';h.appendChild(l); + })();` }) // no-JS fallback head.noscript.push({ - key: 'gf-noscript', - innerHTML: ``, + children: ``, tagPosition: 'bodyOpen' }) - - // Disable sanitazions - // @ts-ignore - head.__dangerouslyDisableSanitizersByTagID = head.__dangerouslyDisableSanitizersByTagID || {} - // @ts-ignore - head.__dangerouslyDisableSanitizersByTagID['gf-script'] = ['innerHTML'] - // @ts-ignore - head.__dangerouslyDisableSanitizersByTagID['gf-noscript'] = ['innerHTML'] - - return } - - // JS to inject CSS - head.script.unshift({ - key: 'gf-script', - children: `(function(){ - var h=document.querySelector("head"); - var m=h.querySelector('meta[name="head:count"]'); - if(m){m.setAttribute('content',Number(m.getAttribute('content'))+1);} - else{m=document.createElement('meta');m.setAttribute('name','head:count');m.setAttribute('content','1');h.append(m);} - var l=document.createElement('link');l.rel='stylesheet';l.href='${url}';h.appendChild(l); - })();` - }) - - // no-JS fallback - head.noscript.push({ - children: ``, - tagPosition: 'bodyOpen' - }) } })