unplugin-fonts
Version:
Universal Webfont loader
175 lines (174 loc) • 6.88 kB
JavaScript
import { getHeadLinkTags } from "./loaders/index.mjs";
import { customVirtualModule, resolveFontFiles, resolveUserOption } from "./loaders/custom.mjs";
import { collectFallbackNames, generateAllFallbacks, transformFontFamilyDeclarations } from "./loaders/fallback.mjs";
import { fontsourceImports, fontsourceVirtualModule } from "./loaders/fontsource.mjs";
import MagicString from "magic-string";
import { basename, extname, join } from "pathe";
import { createUnplugin } from "unplugin";
//#region src/index.ts
const virtualStylesId = "unfonts.css";
const resolvedVirtualStylesId = `\0${virtualStylesId}`;
const virtualModuleId = "unplugin-fonts/head";
const resolvedVirtualModuleId = `\0${virtualModuleId}`;
const fontFileRegex = /\.(?:woff2?|ttf|eot|otf)(?:\?.*)?$/i;
var src_default = createUnplugin((userOptions) => {
const options = userOptions || {};
let root;
let base;
const fallbackNames = collectFallbackNames(options);
return {
name: "unplugin-fonts",
enforce: "pre",
resolveId(id) {
if (id.startsWith(virtualStylesId)) return resolvedVirtualStylesId;
if (id.startsWith(virtualModuleId)) return resolvedVirtualModuleId;
},
async load(id) {
if (id.startsWith(resolvedVirtualModuleId)) {
const tags = getHeadLinkTags(options);
const s = new MagicString(`export const links = ${JSON.stringify(tags)};\n`);
s.append(`export const importMap = ${JSON.stringify(fontsourceImports(options.fontsource))};\n`);
s.append(`export const styles = ${JSON.stringify(fontsourceVirtualModule(options.fontsource))};\n`);
return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
};
}
if (id.startsWith(resolvedVirtualStylesId)) {
const s = new MagicString("");
if (options.fontsource) s.append(`${fontsourceVirtualModule(options.fontsource)}\n`);
if (options.custom) s.append(`${customVirtualModule(options.custom, root)}\n`);
if (fallbackNames.size > 0) {
const fallbackCSS = await generateAllFallbacks(options, root);
if (fallbackCSS) s.append(`${fallbackCSS}\n`);
}
return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
};
}
},
transform(code, id) {
return transformFontFamilyDeclarations(code, id, fallbackNames, !!options.sourcemap);
},
vite: {
configResolved(viteConfig) {
root = viteConfig.root;
base = viteConfig.base;
},
generateBundle(_options, bundle) {
if ("VITEPRESS_CONFIG" in globalThis) {
generateVitepressBundle(options, base, root, bundle, globalThis.VITEPRESS_CONFIG);
return;
}
for (const chunk in bundle) {
const info = bundle[chunk];
if (info.name?.endsWith(".astro") || info.name === "pages/all") {}
}
},
transformIndexHtml: {
order: "post",
handler: (html, ctx) => {
const tags = getHeadLinkTags(options);
const customBasenames = resolveCustomFontBasenames(options.custom, root);
const files = Object.entries(ctx.bundle ?? {}).filter(([key, info]) => {
if (!fontFileRegex.test(key)) return false;
if (customBasenames.size === 0) return false;
return (info.originalFileNames ?? []).some((name) => {
return customBasenames.has(basename(name, extname(name)));
});
}).map(([key]) => key);
const { prefetch: wantPrefetch, preload: wantPreload } = options?.custom || {};
for (const file of files) {
if (!(wantPrefetch === true || wantPreload === true || wantPrefetch === void 0 && wantPreload === void 0)) continue;
const ext = extname(file);
tags.push({
tag: "link",
injectTo: options?.custom?.injectTo ?? "head-prepend",
attrs: {
rel: options?.custom?.prefetch ? "prefetch" : "preload",
as: "font",
type: `font/${ext.replace(".", "")}`,
href: join(base, file),
crossorigin: "anonymous"
}
});
}
if (options.inlineFontFace) {
const fontFaceRegex = /@font-face\s*\{[^}]*\}/g;
const fontFaceRules = [];
for (const info of Object.values(ctx.bundle ?? {})) if (info.type === "asset" && typeof info.fileName === "string" && info.fileName.endsWith(".css")) {
const css = typeof info.source === "string" ? info.source : "";
const matches = css.match(fontFaceRegex);
if (matches) {
fontFaceRules.push(...matches);
info.source = css.replace(fontFaceRegex, "");
}
}
if (fontFaceRules.length > 0) tags.push({
tag: "style",
injectTo: "head-prepend",
children: fontFaceRules.join("")
});
}
let tagsReturned = tags;
if (options?.custom?.linkFilter) {
const newTags = options?.custom?.linkFilter(tags);
if (Array.isArray(newTags)) tagsReturned = newTags;
else tagsReturned = newTags ? tags : [];
}
return tagsReturned;
}
}
}
};
});
let vitepressInjected = false;
function resolveCustomFontBasenames(customOptions, root) {
if (!customOptions) return /* @__PURE__ */ new Set();
const resolved = resolveUserOption(customOptions);
const basenames = /* @__PURE__ */ new Set();
for (const family of resolved.families) for (const face of resolveFontFiles(family, resolved, root)) basenames.add(face.basename);
return basenames;
}
function generateVitepressBundle(options, base, root, bundle, vitepressConfig) {
if (vitepressInjected) return;
vitepressInjected = true;
const tags = getHeadLinkTags(options);
const customBasenames = resolveCustomFontBasenames(options.custom, root);
const files = Object.entries(bundle ?? {}).filter(([key, info]) => {
if (!fontFileRegex.test(key)) return false;
if (customBasenames.size === 0) return false;
return (info.originalFileNames ?? []).some((name) => {
return customBasenames.has(basename(name, extname(name)));
});
}).map(([key]) => key);
const { prefetch: wantPrefetch, preload: wantPreload } = options?.custom || {};
for (const file of files) {
if (!(wantPrefetch === true || wantPreload === true || wantPrefetch === void 0 && wantPreload === void 0)) continue;
const ext = extname(file);
tags.push({
tag: "link",
injectTo: options.custom?.injectTo ?? "head-prepend",
attrs: {
rel: options.custom?.prefetch ? "prefetch" : "preload",
as: "font",
type: `font/${ext.replace(".", "")}`,
href: join(base, file),
crossorigin: "anonymous"
}
});
}
let tagsReturned = tags;
if (options?.custom?.linkFilter) {
const newTags = options?.custom?.linkFilter(tags);
if (Array.isArray(newTags)) tagsReturned = newTags;
else tagsReturned = newTags ? tags : [];
}
for (const tag of tagsReturned) vitepressConfig?.site?.head?.push([tag.tag, tag.attrs?.onload === "this.rel='stylesheet'" ? {
rel: "stylesheet",
href: tag.attrs?.href
} : tag.attrs]);
}
//#endregion
export { src_default as default };