This commit is contained in:
207
frontend/vite-plugins/fontawesome-subset.ts
Normal file
207
frontend/vite-plugins/fontawesome-subset.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { Plugin } from "vite";
|
||||
import * as fs from "fs";
|
||||
import { createRequire } from "module";
|
||||
import * as path from "path";
|
||||
import { fontawesomeSubset as createFontawesomeSubset } from "fontawesome-subset";
|
||||
|
||||
function parseIcons(iconSet: string): string[] {
|
||||
const require = createRequire(import.meta.url);
|
||||
const path = require.resolve(
|
||||
`@fortawesome/fontawesome-free/js/${iconSet}.js`,
|
||||
);
|
||||
const file: string | null = fs.readFileSync(path).toString();
|
||||
|
||||
return file
|
||||
?.match(/"(.*)": \[.*\],/g)
|
||||
?.map((it) => it.substring(1, it.indexOf(":") - 1)) as string[];
|
||||
}
|
||||
|
||||
type FontawesomeConfig = {
|
||||
/* used regular icons without `fa-` prefix*/
|
||||
regular: string[];
|
||||
/* used solid icons without `fa-` prefix*/
|
||||
solid: string[];
|
||||
/* used brands icons without `fa-` prefix*/
|
||||
brands: string[];
|
||||
};
|
||||
|
||||
type FileObject = { name: string; isDirectory: boolean };
|
||||
|
||||
const iconSet = {
|
||||
solid: parseIcons("solid"),
|
||||
regular: parseIcons("regular"),
|
||||
brands: parseIcons("brands"),
|
||||
};
|
||||
/**
|
||||
* Map containing reserved classes by module
|
||||
*/
|
||||
const modules2 = {
|
||||
animated: ["spin", "pulse"],
|
||||
"bordererd-pulled": ["border", "pull-left", "pull-right"],
|
||||
"fixed-width": ["fw"],
|
||||
larger: [
|
||||
"lg",
|
||||
"xs",
|
||||
"sm",
|
||||
"1x",
|
||||
"2x",
|
||||
"3x",
|
||||
"4x",
|
||||
"5x",
|
||||
"6x",
|
||||
"7x",
|
||||
"8x",
|
||||
"9x",
|
||||
"10x",
|
||||
],
|
||||
"rotated-flipped": [
|
||||
"rotate-90",
|
||||
"rotate-180",
|
||||
"rotate-270",
|
||||
"flip-horizontal",
|
||||
"flip-vertical",
|
||||
"flip-both",
|
||||
],
|
||||
stacked: ["stack", "stack-1x", "stack-2x", "inverse"],
|
||||
};
|
||||
|
||||
function toFileAndDir(dir: string, file: string): FileObject {
|
||||
const name = path.join(dir, file);
|
||||
return { name, isDirectory: fs.statSync(name).isDirectory() };
|
||||
}
|
||||
|
||||
function findAllFiles(
|
||||
dir: string,
|
||||
filter: (filename: string) => boolean = (_it): boolean => true,
|
||||
): string[] {
|
||||
const files = fs
|
||||
.readdirSync(dir)
|
||||
.map((it) => toFileAndDir(dir, it))
|
||||
.filter((file) => file.isDirectory || filter(file.name));
|
||||
|
||||
const out: string[] = [];
|
||||
for (const file of files) {
|
||||
if (file.isDirectory) {
|
||||
out.push(...findAllFiles(file.name, filter));
|
||||
} else {
|
||||
out.push(file.name);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect used fontawesome icons in the directories `src/**` and `static/**{.html|.css}`
|
||||
* @param {boolean} debug - Enable debug output
|
||||
* @returns {FontawesomeConfig} - used icons
|
||||
*/
|
||||
function getFontawesomeConfig(debug = false): FontawesomeConfig {
|
||||
const time = Date.now();
|
||||
const srcFiles = findAllFiles(
|
||||
"./src",
|
||||
(filename) =>
|
||||
!filename.endsWith("fontawesome-5.scss") &&
|
||||
!filename.endsWith("fontawesome-6.scss"), //ignore our own css
|
||||
);
|
||||
const staticFiles = findAllFiles(
|
||||
"./static",
|
||||
(filename) => filename.endsWith(".html") || filename.endsWith(".css"),
|
||||
);
|
||||
|
||||
const allFiles = [...srcFiles, ...staticFiles];
|
||||
const usedClassesSet: Set<string> = new Set();
|
||||
|
||||
const regex = /\bfa-[a-z0-9-]+\b/g;
|
||||
|
||||
for (const file of allFiles) {
|
||||
const fileContent = fs.readFileSync("./" + file).toString();
|
||||
const matches = fileContent.match(regex);
|
||||
|
||||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
const [icon] = match.split(" ");
|
||||
usedClassesSet.add((icon as string).substring(3));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const usedClasses = [...usedClassesSet].sort();
|
||||
const allModuleClasses = new Set(Object.values(modules2).flatMap((it) => it));
|
||||
const icons = usedClasses.filter((it) => !allModuleClasses.has(it));
|
||||
|
||||
const solid = icons.filter((it) => iconSet.solid.includes(it));
|
||||
const regular = icons.filter((it) => iconSet.regular.includes(it));
|
||||
const brands = usedClasses.filter((it) => iconSet.brands.includes(it));
|
||||
|
||||
const leftOvers = icons.filter(
|
||||
(it) =>
|
||||
!(solid.includes(it) || regular.includes(it) || brands.includes(it)),
|
||||
);
|
||||
if (leftOvers.length !== 0) {
|
||||
throw new Error(
|
||||
"Fontawesome failed with unknown icons: " + leftOvers.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
console.debug(
|
||||
"Make sure fontawesome modules are active: ",
|
||||
Object.entries(modules2)
|
||||
.filter((it) => usedClasses.filter((c) => it[1].includes(c)).length > 0)
|
||||
.map((it) => it[0])
|
||||
.filter((it) => it !== "brands")
|
||||
.join(", "),
|
||||
);
|
||||
|
||||
console.debug(
|
||||
"Here is your config: \n",
|
||||
JSON.stringify({
|
||||
regular,
|
||||
solid,
|
||||
brands,
|
||||
}),
|
||||
);
|
||||
console.debug("Detected fontawesome classes in", Date.now() - time, "ms");
|
||||
}
|
||||
|
||||
return {
|
||||
regular,
|
||||
solid,
|
||||
brands,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect fontawesome icons used by the application and creates subset font files only containing the used icons.
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
export function fontawesomeSubset(): Plugin {
|
||||
return {
|
||||
name: "vite-plugin-fontawesome-subset",
|
||||
apply: "build",
|
||||
async buildStart() {
|
||||
const start = performance.now();
|
||||
console.log("\nCreating fontawesome subset...");
|
||||
|
||||
const fontawesomeClasses = getFontawesomeConfig();
|
||||
await createFontawesomeSubset(
|
||||
fontawesomeClasses,
|
||||
"src/webfonts-generated",
|
||||
{
|
||||
targetFormats: ["woff2"],
|
||||
},
|
||||
);
|
||||
|
||||
const end = performance.now();
|
||||
console.log(
|
||||
`Creating fontawesome subset took ${Math.round(end - start)} ms`,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
//detect if we run this as a main
|
||||
if (import.meta.url.endsWith(process.argv[1] as string)) {
|
||||
getFontawesomeConfig(true);
|
||||
}
|
||||
Reference in New Issue
Block a user