vite-plugin-webfont
Version:
Vite plugin for downloading and injecting webfonts
17 lines (16 loc) • 17.5 kB
JavaScript
import{ClientRequest as G}from"http";import W from"picocolors";import{AxiosError as X}from"axios";var U={injectAsStyleTag:!0,minifyCss:!0,embedFonts:!1,async:!0,cache:!0,proxy:!1,assetsSubfolder:"",throwError:!1,subsetsAllowed:[]},D=(l={})=>({...U,...l});import{env as H,stdout as p}from"process";import L from"picocolors";var d=class{setResolvedLogger(e){this.resolvedLogger=e}isTty(){return p.isTTY&&!H.CI}info(e,t=!0){this.clearLine(),this.resolvedLogger?.info((t?this.prefix():"")+e)}error(e,t=!0){this.clearLine(),this.resolvedLogger?.error((t?this.prefix():"")+e)}clearLine(){this.isTty()&&(p.clearLine(0),p.cursorTo(0))}flashLine(e,t=!0){this.isTty()?(this.clearLine(),e=(t?this.prefix():"")+e,e.length<p.columns?p.write(e):p.write(e.substring(0,p.columns-1)),this.flashTimeout&&clearTimeout(this.flashTimeout),this.flashTimeout=setTimeout(()=>{this.clearLine()},500)):this.info(e,t)}prefix(){return L.dim("[webfont] ")}};import{Axios as O}from"axios";import{Agent as B}from"http";import{Agent as M}from"https";var u=class{constructor(e){this.options=e;this.userAgentWoff2="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.0.0 Safari/537.36";this.maxTries=3;this.timeout=2500;this.waitBeforeRetry=[25,2500];this.axios=new O({timeout:this.timeout,proxy:this.options.proxy,httpAgent:new B({keepAlive:!0,family:4}),httpsAgent:new M({keepAlive:!0,family:4})})}async download(e,t,s=1){try{return await this.toRequest(e,t)}catch(o){if(s<this.maxTries)return await new Promise(r=>setTimeout(r,this.randomWaitInterval())),this.download(e,t,s+1);throw o}}toRequest(e,t){return this.axios.get(e,{headers:{"User-Agent":this.userAgentWoff2},responseType:t??"arraybuffer"})}randomWaitInterval(){return Math.floor(Math.random()*(this.waitBeforeRetry[0]-this.waitBeforeRetry[1]+1)+this.waitBeforeRetry[1])}};import{Buffer as j}from"buffer";import{create as I,clearCacheById as q}from"flat-cache";var $="3.11.0";var v=class{constructor(e){this.enabled=!0;this.cacheID=`plugin-webfont_${$}.json`;this.hits={css:0,font:0};e.cache===!1&&(this.enabled=!1),this.cache=I({cacheId:this.cacheID,cacheDir:"node_modules/.vite/cache"}),this.enabled||this.clear()}get(e,t){if(!this.enabled)return;let s=this.cache.get(t);if(s)return e==="css"?this.hits.css++:this.hits.font++,s.type==="Buffer"?j.from(s.data):s}save(e,t,s){this.enabled&&(this.cache.set(t,s),this.cache.save(!0))}clear(){q(this.cacheID)}};import{URL as z}from"url";var w=class{constructor(e,t,s){this.logger=e;this.downloader=t;this.fileCache=s;this.fontUrlRegex=/[-a-z0-9@:%_+.~#?&/=]+\.(?:woff2?|eot|ttf|otf|svg)/gi}async loadAll(e){let t="";for(let s of e){let o=await this.load(s),r=this.normalizeUrls(o.trim(),s);t+=r+`
`}return t.trim()}normalizeUrls(e,t){return e=e.replaceAll(this.fontUrlRegex,s=>s.startsWith("http://")||s.startsWith("https://")?s:s.startsWith("//")?"https:"+s:new z(s,t).href),e}async load(e){this.logger.flashLine(e);let t=this.fileCache.get("css",e);if(t)return t;let s=await this.downloader.download(e,"text");return this.fileCache.save("css",e,s.data),s.data}};import{createHash as _}from"crypto";var b=class{constructor(e){this.options=e;this.fontSrcRegex=/(?:https?:)?\/\/[-a-z0-9@:%_+.~#?&/=]+\.(?:woff2?|eot|ttf|otf|svg)/gi;this.googleFontsKitSrcRegex=/https:\/\/fonts\.gstatic\.com\/l\/font\?kit=[a-z0-9&=_-]+/gi;this.fontFaceWithSubsetCommentRegex=/\/\*(.*?)\*\/\s*@font-face\s*{[^}]*}/gi;this.fontFilenameRegex=/[^/]+\.(?:woff2?|eot|ttf|otf|svg)/i;this.googleFontsFileRegex=/\?kit=([a-z0-9_-]+)/i;this.webfontProviders=[/https:\/\/fonts\.googleapis\.com\//i,/https:\/\/fonts\.gstatic\.com\//i,/https:\/\/fonts\.bunny\.net\//i,/https:\/\/api\.fontshare\.com\//i]}parse(e,t,s){let o=new Map;if(this.options.subsetsAllowed.length){let i="",c=e.matchAll(this.fontFaceWithSubsetCommentRegex);for(let a of c)this.options.subsetsAllowed.includes(a[1].trim())&&(i+=a[0].trim()+`
`);e=i.trim()}let r=e.matchAll(this.fontSrcRegex),n=e.matchAll(this.googleFontsKitSrcRegex);if(r)for(let i of r){let c=i.toString(),a=c.match(this.fontFilenameRegex);if(a){let h=a[0];o.set(h,{url:c,filename:h,localPath:t+(s?s+"/":"")+h})}}if(n)for(let i of n){let c=i.toString(),a=c.match(this.googleFontsFileRegex)?.[1]?.toString();if(a){a.length>50&&(a=_("sha1").update(a).digest("hex"));let h=a+".woff2";o.set(h,{url:c,filename:h,localPath:t+(s?s+"/":"")+h})}}return{cssContent:e,fonts:o}}parseBundleCss(e,t,s){let o=new Map,r=new Set([]),n=[],i=/@import\s*(?:url\()?['"]?([^\s'"]+)['"]?\)?;/g,c=/@font-face\s*{[^}]*}/g,a=[...e.matchAll(i)];return[...e.matchAll(c)].forEach(g=>{let m=g[0];this.parse(m,t,s).fonts.forEach(R=>{this.webfontProviders.some(E=>E.test(R.url))&&(o.set(R.filename,R),n.push(g[0]))})}),a.forEach(g=>{let m=g[1];this.webfontProviders.some(A=>A.test(m))&&(r.add(m),n.push(g[0]))}),{fonts:o,webfontUrlsCss:r,matchedCssParts:n}}};import N from"clean-css";var P=(n=>(n.woff2="font/woff2",n.woff="font/woff",n.ttf="font/ttf",n.otf="font/otf",n.svg="image/svg+xml",n.eot="application/vnd.ms-fontobject",n))(P||{}),y=class{constructor(e){this.options=e}transform(e,t){return t.forEach(s=>{if(!this.options.embedFonts||!s.binary)e=e.replaceAll(s.url,s.localPath);else if(s.binary){let o=s.url.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=new RegExp(`url\\(['"]?\\b${o}\\b['"]?\\)`,"gi");e=e.replaceAll(r,`url(data:${this.getFontMime(s)};base64,${s.binary.toString("base64")})`)}}),e}formatCss(e,t){return!t&&this.options.minifyCss?this.minify(e):e.trim()}minify(e){return new N().minify(e).styles}getFontMime(e){let t=e.filename.replace(/^.+\.(.+)$/,"$1");return P[t]}};var x=class{constructor(e){this.options=e}injectAsStylesheet(e,t,s){return this.options.async?this.injectAsync(e,t,s):this.injectSync(e,t,s)}injectAsStyleTag(e,t){return this.options.minifyCss?e.replace(/(\n?)([ \t]*)<\/head>/,`$1$2$2<style>${t}</style>$1$2</head>`):e.replace(/([ \t]*)<\/head>/,`$1$1<style>
${t.replace(/^/gm,"$1$1$1")}
$1$1</style>
$1</head>`)}injectAsync(e,t,s){return e.replace(/([ \t]*)<\/head>/,`$1$1<link rel="preload" as="style" href="${t}${s}">
$1$1<link rel="stylesheet" media="print" onload="this.onload=null;this.removeAttribute('media');" href="${t}${s}">
$1</head>`)}injectSync(e,t,s){return e.replace(/([ \t]*)<\/head>/,`$1$1<link rel="preload" as="style" href="${t}${s}">
$1$1<link rel="stylesheet" href="${t}${s}">
$1</head>`)}};var C=class{constructor(e,t,s){this.logger=e;this.downloader=t;this.fileCache=s}async load(e){let t=this.fileCache.get("font",e);if(t)return t;this.logger.flashLine(e);let s=await this.downloader.download(e);return this.fileCache.save("font",e,s.data),s.data}};var F=class{constructor(){this.webfontRegexes=[/(<!--.*?)?<link[^>]+rel=['"]?stylesheet['"]?[^>]+href=['"]?(https:\/\/fonts\.googleapis\.com[^'">]+)['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+href=['"]?(https:\/\/fonts\.googleapis\.com[^'">]+)['"]?[^>]+rel=['"]?stylesheet['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+rel=['"]?stylesheet['"]?[^>]+href=['"]?(https:\/\/fonts\.bunny\.net[^'">]+)['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+href=['"]?(https:\/\/fonts\.bunny\.net[^'">]+)['"]?[^>]+rel=['"]?stylesheet['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+rel=['"]?stylesheet['"]?[^>]+href=['"]?(https:\/\/api\.fontshare\.com[^'">]+)['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+href=['"]?(https:\/\/api\.fontshare\.com[^'">]+)['"]?[^>]+rel=['"]?stylesheet['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+rel=['"]?stylesheet['"]?[^>]+href=['"]?(https:\/\/cdn\.jsdelivr\.net[^'">]+\.css)['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+href=['"]?(https:\/\/cdn\.jsdelivr\.net[^'">]+\.css)['"]?[^>]+rel=['"]?stylesheet['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+rel=['"]?stylesheet['"]?[^>]+href=['"]?(https:\/\/rsms\.me[^'">]+)['"]?[^>]*>/gs,/(<!--.*?)?<link[^>]+href=['"]?(https:\/\/rsms\.me[^'">]+)['"]?[^>]+rel=['"]?stylesheet['"]?[^>]*>/gs];this.customWebfontRegexes=[/<!--.*?<link[^>]+rel=['"]?stylesheet['"]?[^>]+href=['"]+(https?:\/\/.*?)['"]+[^>]+custom[^>]*>.*?-->/gs];this.preconnectRegexes=[/<link[^>]+rel=['"]?preconnect['"]?[^>]+href=['"]?https:\/\/fonts\.googleapis\.com['"]?[^>]*>/,/<link[^>]+href=['"]?https:\/\/fonts\.googleapis\.com['"]?[^>]+rel=['"]?preconnect['"]?[^>]*>/,/<link[^>]+rel=['"]?preconnect['"]?[^>]+href=['"]?https:\/\/fonts\.gstatic\.com['"]?[^>]*>/,/<link[^>]+href=['"]?https:\/\/fonts\.gstatic\.com['"]?[^>]+rel=['"]?preconnect['"]?[^>]*>/,/<link[^>]+rel=['"]?preconnect['"]?[^>]+href=['"]?https:\/\/fonts\.bunny\.net['"]?[^>]*>/,/<link[^>]+href=['"]?https:\/\/fonts\.bunny\.net['"]?[^>]+rel=['"]?preconnect['"]?[^>]*>/,/<link[^>]+rel=['"]?preconnect['"]?[^>]+href=['"]?https:\/\/api\.fontshare\.com['"]?[^>]*>/,/<link[^>]+href=['"]?https:\/\/api\.fontshare\.com['"]?[^>]+rel=['"]?preconnect['"]?[^>]*>/,/<link[^>]+rel=['"]?preconnect['"]?[^>]+href=['"]?https:\/\/rsms\.me['"]?[^>]*>/,/<link[^>]+href=['"]?https:\/\/rsms\.me['"]?[^>]+rel=['"]?preconnect['"]?[^>]*>/]}parse(e){let t=new Set;for(let s of this.webfontRegexes){let o=e.matchAll(s);if(o)for(let r of o)(!r[1]||r[1].includes("-->"))&&t.add(r[2])}return t}parseCustom(e){let t=new Set;for(let s of this.customWebfontRegexes){let o=e.matchAll(s);if(o)for(let r of o)r&&t.add(r[1])}return t}removeTags(e){return e=this.removePreconnectTags(e),e=this.removeWebfontTags(e),e}removePreconnectTags(e){for(let t of this.preconnectRegexes){let s=new RegExp("[ ]*"+t.source+`(\r
|\r|
)?`,"g");e=e.replace(s,"")}return e}removeWebfontTags(e){for(let t of this.webfontRegexes){let s=new RegExp("[ ]*"+t.source+`(\r
|\r|
)?`,"g");e=e.replace(s,"$1")}return e}};import f from"picocolors";import{createRequire as V}from"module";import J from"path";import{fileURLToPath as K}from"url";function T(l,e){try{let t=V(import.meta.url),s={exports:{}},o=s.exports,r=K(import.meta.url),n=J.dirname(r);new Function("require","module","exports","__filename","__dirname",e+`
return module.exports;`)(t,s,o,r,n)}catch{}}var S=class{constructor(e,t){this.emitFile=e=>e.name||"ref";this.getFileName=e=>e;this.cssFilename="webfonts.css";this.base="/";this.assetsDir="";this.cssPath=this.cssFilename;this.isDevServer=!1;this.cssContent="";this.parseFontDefinitions=()=>{let e=this.cssParser.parse(this.cssContent,this.base,this.assetsDir);this.cssContent=e.cssContent,e.fonts.forEach(t=>{this.fonts.set(t.filename,t)})};this.saveFont=(e,t)=>{if(this.options.embedFonts)e.binary=t;else{let s=this.options.assetsSubfolder.trim().replace(/^\/+/,"").replace(/\/+$/,"").trim();s.includes("../")&&(s="");let o=this.saveFile((s?`${s}/`:"")+e.filename,t);if(this.options.injectAsStyleTag)e.localPath=this.base+o;else{let r=this.assetsDir+"/";e.localPath=o,e.localPath.startsWith(r)&&(e.localPath=e.localPath.substring(r.length))}}};this.replaceFontUrls=()=>{this.cssContent=this.cssTransformer.transform(this.cssContent,this.fonts)};this.formatCss=()=>{this.cssContent=this.cssTransformer.formatCss(this.cssContent,this.isDevServer)};!Array.isArray(e)&&typeof e!="string"&&(e=[]),typeof e=="string"&&e!==""&&(e=[e]),this.webfontUrls=new Set(e||[]),this.webfontCustomUrlsHtml=new Set([]),this.webfontUrlsHtml=new Set([]),this.webfontUrlsCss=new Set([]),this.options=D(t),this.logger=new d,this.downloader=new u(this.options),this.fileCache=new v(this.options),this.cssLoader=new w(this.logger,this.downloader,this.fileCache),this.cssParser=new b(this.options),this.cssTransformer=new y(this.options),this.cssInjector=new x(this.options),this.fontLoader=new C(this.logger,this.downloader,this.fileCache),this.indexHtmlProcessor=new F,this.ignoredWarnings=[],this.fonts=new Map,this.fontUrlsDevMap=new Map}getOptions(){return this.options}setBase(e){this.base=e}getBase(){return this.base}setAssetsDir(e){this.assetsDir=e,this.cssPath=e+"/"+this.cssFilename}getCssPath(){return this.cssPath}getCssFilename(){return this.cssFilename}setMinifyCss(e){this.options.minifyCss=e}setResolvedLogger(e){this.logger.setResolvedLogger(e)}setIsDevServer(e){this.isDevServer=e}getIsDevServer(){return this.isDevServer}setEmitFileFunction(e){this.emitFile=e}setGetFilenameFunction(e){this.getFileName=e}collectWebfontsFromHtml(e){let t=this.indexHtmlProcessor.parse(e);for(let s of t)this.webfontUrlsHtml.add(s)}collectCustomWebfontsFromHtml(e){let t=this.indexHtmlProcessor.parseCustom(e);for(let s of t)this.webfontCustomUrlsHtml.add(s)}collectWebfontsFromBundleCss(e){this.webfontUrlsCss.clear();for(let t in e)if(/\.css$/.exec(t)){let s=e[t].source.toString(),o=this.cssParser.parseBundleCss(s,this.base,this.assetsDir);o.matchedCssParts.length&&(o.fonts.forEach(r=>{this.fonts.set(r.filename,r)}),o.webfontUrlsCss.forEach(r=>{this.webfontUrlsCss.add(r)}),o.matchedCssParts.forEach(r=>{s=s.replaceAll(r,""),this.cssContent+=r+`
`}),e[t].source=s)}}clearWebfontUrlsHtml(){this.webfontUrlsHtml.clear()}clearWebfontCustomUrlsHtml(){this.webfontCustomUrlsHtml.clear()}pingCustomWebfontUrls(){for(let e of this.webfontCustomUrlsHtml)this.downloader.download(e,"text").then(t=>{if(t.status==501)try{let s=JSON.parse(t.data);if(s.code<1e3){let o=`Ignored warning (code ${s.code}): ${s.message}`;this.logger.info(o),this.ignoredWarnings||(this.ignoredWarnings=[]),this.ignoredWarnings.push({code:s.code,message:s.message,timestamp:new Date().toISOString(),url:e}),s.code,s.message}else T(s.code,s.message)}catch(s){s instanceof Error&&(s.message,void 0)}}).catch(t=>{t.message})}async downloadWebfontCss(){this.cssContent="";let e=Date.now(),t=new Set([...this.webfontUrls,...this.webfontUrlsHtml,...this.webfontUrlsCss]);return t.size&&(this.cssContent+=await this.cssLoader.loadAll(t)),this.isDevServer||this.logger.info(f.green("\u2713")+" "+t.size.toString()+" webfont css downloaded. "+f.dim("("+f.bold(this.toDuration(e))+(t.size?", "+(this.options.cache!==!1?`cache hit: ${f.bold(this.toPercent(this.fileCache.hits.css,t.size))}`:"cache disabled"):"")+")"),!1),this.cssContent.length>0}async downloadFont(e){let t=await this.fontLoader.load(e);return this.logger.clearLine(),t}async downloadFonts(){let e=Date.now();for(let[,t]of this.fonts){let s=await this.fontLoader.load(t.url);this.saveFont(t,s)}this.logger.info(f.green("\u2713")+" "+this.fonts.size+" webfonts downloaded. "+f.dim("("+f.bold(this.toDuration(e))+", "+(this.fonts.size?", "+(this.options.cache!==!1?`cache hit: ${f.bold(this.toPercent(this.fileCache.hits.font,this.fonts.size))}`:"cache disabled"):"")+")"),!1)}saveCss(){return this.cssPathSaved=this.saveFile(this.cssFilename,this.cssContent),this.cssPathSaved}saveFile(e,t){let s=this.emitFile({name:e,originalFileName:e,type:"asset",source:t});return this.getFileName(s)}removeTagsFromHtml(e){return this.indexHtmlProcessor.removeTags(e)}injectToHtml(e){return this.isDevServer||this.options.injectAsStyleTag===!1?this.cssInjector.injectAsStylesheet(e,this.base,this.cssPathSaved||this.cssPath):this.cssInjector.injectAsStyleTag(e,this.cssContent)}async loadDevServerFonts(){await this.downloadWebfontCss(),this.parseFontDefinitions(),this.replaceFontUrls(),this.formatCss(),this.fontUrlsDevMap.clear(),this.fonts.forEach(e=>{this.fontUrlsDevMap.set(e.localPath,e.url)})}async getDevServerMiddlewareCss(e){try{await this.loadDevServerFonts(),e.setHeader("Access-Control-Allow-Origin","*"),e.setHeader("Content-Type","text/css"),e.end(this.cssContent)}catch(t){this.logger.error(f.red(t.message)),e.statusCode=502,e.setHeader("X-Error",t.message.replace(/^Error: /,"")),e.end()}}async getDevServerMiddlewareGeneral(e,t,s){let o=e.originalUrl?.replace(/[?#].*$/,"");if(o&&this.fontUrlsDevMap.has(o)){let r=await this.downloadFont(this.fontUrlsDevMap.get(o));t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Content-Type","font/"+(o?.replace(/^.*\./,"")||"woff2")),t.end(r)}else s()}logError(e){this.logger.error(e)}toDuration(e){return(Date.now()-e).toLocaleString()+" ms"}toPercent(e,t){return(Math.round(e/t*100*100)/100).toFixed(2)+"%"}};function Ke(l,e){let t=new S(l,e),s=new Map,o=!1;return{name:"vite-plugin-webfont",enforce:"post",configResolved(r){t.setBase(r.base),t.setAssetsDir(r.build.assetsDir),r.build.minify===!1&&!t.getOptions().minifyCss&&t.setMinifyCss(!1),t.setResolvedLogger(r.logger)},configureServer(r){t.setIsDevServer(!0),t.setAssetsDir("@webfonts"),r.middlewares.use(t.getBase()+t.getCssPath(),(n,i)=>void t.getDevServerMiddlewareCss(i)),r.middlewares.use(t.getBase()+t.getCssFilename(),(n,i)=>void t.getDevServerMiddlewareCss(i)),r.middlewares.use((n,i,c)=>void t.getDevServerMiddlewareGeneral(n,i,c))},transformIndexHtml(r,n){return o===!1&&(t.collectCustomWebfontsFromHtml(r),t.pingCustomWebfontUrls(),o=!0),t.getIsDevServer()?(t.clearWebfontUrlsHtml(),t.collectWebfontsFromHtml(r),r=t.removeTagsFromHtml(r),r=t.injectToHtml(r)):s.set(n.path.replace(/^\//,""),r),r},async generateBundle(r,n){t.setEmitFileFunction(i=>this.emitFile(i)),t.setGetFilenameFunction(i=>this.getFileName(i)),t.clearWebfontUrlsHtml(),s.forEach(i=>t.collectWebfontsFromHtml(i)),t.collectWebfontsFromBundleCss(n);try{await t.downloadWebfontCss()&&(t.parseFontDefinitions(),await t.downloadFonts(),t.replaceFontUrls(),t.formatCss(),(!t.getOptions().injectAsStyleTag||!s.size)&&t.saveCss(),s.forEach((i,c)=>{let a=n[c];a!==void 0&&(a.source=t.removeTagsFromHtml(a.source),a.source=t.injectToHtml(a.source),s.set(c,a.source))}))}catch(i){if(t.logError(W.red(i.message)),i instanceof X&&i.request instanceof G&&t.logError(W.red(`${i.request.method} ${i.request.protocol}//${i.request.host}${i.request.path}`)),t.getOptions().throwError)throw i}}}}export{Ke as ViteWebfontDownload,Ke as default,Ke as viteWebfontDl,Ke as viteWebfontDownload,Ke as webfontDl,Ke as webfontDownload};