vite-plugin-webfont-dl
Version:
Vite plugin for downloading and injecting webfonts
15 lines (14 loc) • 16 kB
JavaScript
import{ClientRequest as K}from"node:http";import T from"picocolors";import{AxiosError as G}from"axios";var E={injectAsStyleTag:!0,minifyCss:!0,embedFonts:!1,async:!0,cache:!0,proxy:!1,assetsSubfolder:"",throwError:!1},$=(a={})=>({...E,...a});import{env as B,stdout as p}from"node:process";import H from"picocolors";var m=class{setResolvedLogger(e){this.resolvedLogger=e}isTty(){return p.isTTY&&!B.CI}info(e,t=!0){var s;this.clearLine(),(s=this.resolvedLogger)==null||s.info((t?this.prefix():"")+e)}error(e,t=!0){var s;this.clearLine(),(s=this.resolvedLogger)==null||s.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 H.dim("[webfont-dl] ")}};import{Axios as U,isAxiosError as j}from"axios";import{Agent as k}from"node:http";import{Agent as M}from"node:https";import u from"picocolors";var v=class{constructor(e,t){this.options=e;this.logger=t;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 U({timeout:this.timeout,proxy:this.options.proxy,httpAgent:new k({keepAlive:!0,family:4}),httpsAgent:new M({keepAlive:!0,family:4})})}async download(e,t,s=1){try{let r=await this.toRequest(e,t);return s>1&&this.logger.info(u.green(`\u2713 ${e}`)+" "+u.dim(`(try #${s})`)),r}catch(r){if(this.logger.error(u.red(`\u2717 ${e}`)+" "+u.dim(`(try #${s})`)+": "+(j(r)?r.message:r)),s<this.maxTries)return await new Promise(i=>setTimeout(i,this.randomWaitInterval())),this.download(e,t,s+1);throw r}}toRequest(e,t){return this.axios.get(e,{headers:{"User-Agent":this.userAgentWoff2},responseType:t!=null?t:"arraybuffer"})}randomWaitInterval(){return Math.floor(Math.random()*(this.waitBeforeRetry[0]-this.waitBeforeRetry[1]+1)+this.waitBeforeRetry[1])}};import{Buffer as W}from"node:buffer";import{create as q,clearCacheById as z}from"flat-cache";var L="3.10.4";var w=class{constructor(e){this.enabled=!0;this.cacheID=`plugin-webfont-dl_${L}.json`;this.hits={css:0,font:0};e.cache===!1&&(this.enabled=!1),this.cache=q({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"?W.from(s.data):s}save(e,t,s){this.enabled&&(this.cache.set(t,s),this.cache.save(!0))}clear(){z(this.cacheID)}};import{URL as N}from"node:url";import V from"clean-css";var b=class{constructor(e,t,s,r){this.options=e;this.logger=t;this.downloader=s;this.fileCache=r;this.fontUrlRegex=/[-a-z0-9@:%_+.~#?&/=]+\.(?:woff2?|eot|ttf|otf|svg)/gi}async loadAll(e,t){let s="";for(let r of e){let i=await this.load(r),o=this.normalizeUrls(i.trim(),r);s+=o+`
`}return this.formatCss(s,t)}formatCss(e,t){return!t&&this.options.minifyCss?this.minify(e):e.trim()}minify(e){return new V().minify(e).styles}normalizeUrls(e,t){return e=e.replaceAll(this.fontUrlRegex,s=>s.startsWith("http://")||s.startsWith("https://")?s:s.startsWith("//")?"https:"+s:new N(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"node:crypto";var y=class{constructor(){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.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){var f,c;let r=new Map,i=e.matchAll(this.fontSrcRegex),o=e.matchAll(this.googleFontsKitSrcRegex);if(i)for(let d of i){let g=d.toString(),n=g.match(this.fontFilenameRegex);if(n){let l=n[0];r.set(l,{url:g,filename:l,localPath:t+(s?s+"/":"")+l})}}if(o)for(let d of o){let g=d.toString(),n=(c=(f=g.match(this.googleFontsFileRegex))==null?void 0:f[1])==null?void 0:c.toString();if(n){n.length>50&&(n=_("sha1").update(n).digest("hex"));let l=n+".woff2";r.set(l,{url:g,filename:l,localPath:t+(s?s+"/":"")+l})}}return r}parseBundleCss(e,t,s){let r=new Map,i=new Set([]),o=[],f=/@import\s*(?:url\()?['"]?([^\s'"]+)['"]?\)?;/g,c=/@font-face\s*{[^}]*}/g,d=[...e.matchAll(f)];return[...e.matchAll(c)].forEach(n=>{let l=n[0];this.parse(l,t,s).forEach(D=>{this.webfontProviders.some(O=>O.test(D.url))&&(r.set(D.filename,D),o.push(n[0]))})}),d.forEach(n=>{let l=n[1];this.webfontProviders.some(A=>A.test(l))&&(i.add(l),o.push(n[0]))}),{fonts:r,webfontUrlsCss:i,matchedCssParts:o}}};var P=(o=>(o.woff2="font/woff2",o.woff="font/woff",o.ttf="font/ttf",o.otf="font/otf",o.svg="image/svg+xml",o.eot="application/vnd.ms-fontobject",o))(P||{}),F=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 r=s.url.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),i=new RegExp(`url\\(['"]?\\b${r}\\b['"]?\\)`,"gi");e=e.replaceAll(i,`url(data:${this.getFontMime(s)};base64,${s.binary.toString("base64")})`)}}),e}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 S=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.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 r=e.matchAll(s);if(r)for(let i of r)(!i[1]||i[1].includes("-->"))&&t.add(i[2])}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 h from"picocolors";var R=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=()=>{this.cssParser.parse(this.cssContent,this.base,this.assetsDir).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 r=this.saveFile((s?`${s}/`:"")+e.filename,t);if(this.options.injectAsStyleTag)e.localPath=this.base+r;else{let i=this.assetsDir+"/";e.localPath=r,e.localPath.startsWith(i)&&(e.localPath=e.localPath.substring(i.length))}}};this.replaceFontUrls=()=>{this.cssContent=this.cssTransformer.transform(this.cssContent,this.fonts)};!Array.isArray(e)&&typeof e!="string"&&(e=[]),typeof e=="string"&&e!==""&&(e=[e]),this.webfontUrls=new Set(e||[]),this.webfontUrlsHtml=new Set([]),this.webfontUrlsCss=new Set([]),this.options=$(t),this.logger=new m,this.downloader=new v(this.options,this.logger),this.fileCache=new w(this.options),this.cssLoader=new b(this.options,this.logger,this.downloader,this.fileCache),this.cssParser=new y,this.cssTransformer=new F(this.options),this.cssInjector=new x(this.options),this.fontLoader=new C(this.logger,this.downloader,this.fileCache),this.indexHtmlProcessor=new S,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)}collectWebfontsFromBundleCss(e){this.webfontUrlsCss.clear();for(let t in e)if(/\.css$/.exec(t)){let s=e[t].source.toString(),r=this.cssParser.parseBundleCss(s,this.base,this.assetsDir);r.matchedCssParts.length&&(r.fonts.forEach(i=>{this.fonts.set(i.filename,i)}),r.webfontUrlsCss.forEach(i=>{this.webfontUrlsCss.add(i)}),r.matchedCssParts.forEach(i=>{s=s.replaceAll(i,""),this.cssContent+=i+`
`}),e[t].source=s,this.cssContent=this.cssLoader.formatCss(this.cssContent,this.isDevServer))}}clearWebfontUrlsHtml(){this.webfontUrlsHtml.clear()}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.isDevServer||this.logger.info(h.green("\u2713")+" "+t.size.toString()+" webfont css downloaded. "+h.dim("("+h.bold(this.toDuration(e))+", "+(this.options.cache!==!1?`cache hit: ${h.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(h.green("\u2713")+" "+this.fonts.size+" webfonts downloaded. "+h.dim("("+h.bold(this.toDuration(e))+", "+(this.options.cache!==!1?`cache hit: ${h.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.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(h.red(t.message)),e.statusCode=502,e.setHeader("X-Error",t.message.replace(/^Error: /,"")),e.end()}}async getDevServerMiddlewareGeneral(e,t,s){var i;let r=(i=e.originalUrl)==null?void 0:i.replace(/[?#].*$/,"");if(r&&this.fontUrlsDevMap.has(r)){let o=await this.downloadFont(this.fontUrlsDevMap.get(r));t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Content-Type","font/"+((r==null?void 0:r.replace(/^.*\./,""))||"woff2")),t.end(o)}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 Me(a,e){let t=new R(a,e),s=new Map;return{name:"vite-plugin-webfont-dl",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(),(i,o)=>void t.getDevServerMiddlewareCss(o)),r.middlewares.use(t.getBase()+t.getCssFilename(),(i,o)=>void t.getDevServerMiddlewareCss(o)),r.middlewares.use((i,o,f)=>void t.getDevServerMiddlewareGeneral(i,o,f))},transformIndexHtml(r,i){return t.getIsDevServer()?(t.clearWebfontUrlsHtml(),t.collectWebfontsFromHtml(r),r=t.removeTagsFromHtml(r),r=t.injectToHtml(r)):s.set(i.path.replace(/^\//,""),r),r},async generateBundle(r,i){t.setEmitFileFunction(this.emitFile),t.setGetFilenameFunction(this.getFileName),t.clearWebfontUrlsHtml(),s.forEach(o=>t.collectWebfontsFromHtml(o)),t.collectWebfontsFromBundleCss(i);try{await t.downloadWebfontCss()&&(t.parseFontDefinitions(),await t.downloadFonts(),t.replaceFontUrls(),(!t.getOptions().injectAsStyleTag||!s.size)&&t.saveCss(),s.forEach((o,f)=>{let c=i[f];c!==void 0&&(c.source=t.removeTagsFromHtml(c.source),c.source=t.injectToHtml(c.source),s.set(f,c.source))}))}catch(o){if(t.logError(T.red(o.message)),o instanceof G&&o.request instanceof K&&t.logError(T.red(`${o.request.method} ${o.request.protocol}//${o.request.host}${o.request.path}`)),t.getOptions().throwError)throw o}}}}export{Me as ViteWebfontDownload,Me as default,Me as viteWebfontDl,Me as viteWebfontDownload,Me as webfontDl,Me as webfontDownload};