Skip to content

Commit

Permalink
feat: add support for lucide icons
Browse files Browse the repository at this point in the history
to reduce bundle size, font awesome and remixicon are no longer included as bulti-in icon packs
  • Loading branch information
aidenlx committed Mar 1, 2022
1 parent f6091f8 commit 1d3117a
Show file tree
Hide file tree
Showing 13 changed files with 844 additions and 106 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package-lock.json

# build
build
src/icons
assets

# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
.pnp.*
Expand Down
3 changes: 3 additions & 0 deletions esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import obPlugin from "@aidenlx/esbuild-plugin-obsidian";
import { build } from "esbuild";
import { lessLoader } from "esbuild-plugin-less";

import iconList from "./scripts/icon-list.js";

const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source visit the plugins github repository
Expand Down Expand Up @@ -32,6 +34,7 @@ try {
javascriptEnabled: true,
}),
obPlugin(),
iconList,
],
});
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "obsidian-icon-shortcodes",
"name": "Icon Shortcodes",
"version": "0.6.2",
"minAppVersion": "0.13.4",
"minAppVersion": "0.13.27",
"description": "Insert emoji and custom icons with shortcodes",
"author": "AidenLx",
"authorUrl": "https://github.com/aidenlx",
Expand Down
127 changes: 64 additions & 63 deletions scripts/generate-icon.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
import child_process from "child_process";
import glob from "fast-glob";
import { promises as fs } from "fs";
import { basename, join } from "path";
import { promisify } from "util";

const { writeFile, mkdir } = fs;
const { mkdir, copyFile, rm } = fs,
exec = promisify(child_process.exec);

const formatLines = (lines) => {
lines.unshift("/* eslint-disable simple-import-sort/exports */");
lines.push("");
return lines.join("\n");
const iconsDir = "assets";

const prepareFolder = async (dir) => {
try {
await rm(dir, { recursive: true });
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
try {
await mkdir(dir, { recursive: true });
} catch (error) {
if (error.code !== "EEXIST") throw error;
}
};
const iconsDir = "src/icons";
const zip = async (targetDir) =>
exec(`zip -jr ${targetDir}.zip ${targetDir}/*.svg`);

/**
* @param {string} faPath
* @returns {Promise<Promise<void>[]>}
*/
const importFontAwesome = async (faPath) => {
const bundleName = "fa";
const series = (
await glob([join(faPath, "*")], { onlyDirectories: true })
).map((path) => basename(path));

const files = series.map((s) => ({
const seriesPattern = series.map((s) => ({
series: s,
prefix: bundleName + s[0],
lines: [],
}));
for (const { series, lines, prefix } of files) {

return seriesPattern.map(async ({ series, prefix }) => {
let copyQueue = [];
const writeTo = join(iconsDir, `${bundleName}-${series}`);
await prepareFolder(writeTo);
for (const path of await glob([join(faPath, series, "**/*.svg")])) {
let varName = basename(path).slice(0, -4).replace(/-/g, "_"),
importPath = path.replace(/^node_modules\//, "");
lines.push(
`export ` +
`{ default as ${prefix}_${varName} }` +
` from "${importPath}";`,
);
let varName = basename(path).slice(0, -4).replace(/-/g, "_");
copyQueue.push(copyFile(path, join(writeTo, `${prefix}_${varName}.svg`)));
}
}

for (const { prefix, lines } of files) {
await writeFile(join(iconsDir, prefix + ".ts"), formatLines(lines));
}
return files.map(({ prefix }) => prefix);
await Promise.all(copyQueue);
return zip(writeTo);
});
};

/**
Expand All @@ -50,65 +59,57 @@ const importRemixicon = async (faPath) => {
const bundleName = "ri";
const series = ["fill", "line"];

const files = series.map((s) => ({
series: s,
prefix: bundleName + s[0],
suffix: "_" + s,
lines: [],
}));
for (const path of await glob([join(faPath, "**/*.svg")])) {
const seriesPattern = series.map((s) => ({
series: s,
prefix: bundleName + s[0],
suffix: "_" + s,
})),
files = await glob([join(faPath, "**/*.svg")]);

let folderQueueMap = {};
for (const path of files) {
let varName = basename(path).slice(0, -4).replace(/-/g, "_"),
importPath = path.replace(/^node_modules\//, "");

let matched = false;
for (const { prefix, suffix, lines } of files) {
const copy = async (folder, filename) => {
const writeTo = join(iconsDir, folder);
if (!folderQueueMap[folder]) {
await prepareFolder(writeTo);
folderQueueMap[folder] = [];
}
folderQueueMap[folder].push(
copyFile(path, join(writeTo, `${filename}.svg`)),
);
};
for (const { series, prefix, suffix } of seriesPattern) {
if (varName.endsWith(suffix)) {
matched = true;
lines.push(
`export ` +
`{ default as ${prefix}_${varName.slice(0, -suffix.length)} }` +
` from "${importPath}";`,
await copy(
`${bundleName}-${series}`,
`${prefix}_${varName.slice(0, -suffix.length)}`,
);
break;
}
}
if (!matched) {
let file;
if (
importPath.includes("Editor/") &&
(file = files.find((f) => f.series === "line"))
) {
const { prefix, lines } = file;
lines.push(
`export ` +
`{ default as ${prefix}_${varName} }` +
` from "${importPath}";`,
if (importPath.includes("Editor/")) {
const { series, prefix } = seriesPattern.find(
(f) => f.series === "line",
);
await copy(`${bundleName}-${series}`, `${prefix}_${varName}`);
} else
console.error(
"unexpected suffix in %s, skipping...",
"node_modules/" + importPath,
);
}
}

for (const { prefix, lines } of files) {
await writeFile(join(iconsDir, prefix + ".ts"), formatLines(lines));
for (const [folder, queue] of Object.entries(folderQueueMap)) {
Promise.all(queue).then(() => zip(join(iconsDir, folder)));
}
return files.map(({ prefix }) => prefix);
};

(async (writeTo) => {
try {
await mkdir(iconsDir);
} catch (err) {
if (err && err.code !== "EEXIST") throw err;
}

const all = await Promise.all([
importFontAwesome("node_modules/@fortawesome/fontawesome-free/svgs"),
importRemixicon("node_modules/remixicon/icons"),
]);
let lines = all.flat().map((name) => `export * as ${name} from "./${name}";`);
await writeFile(writeTo, formatLines(lines));
})("src/icons/index.ts");
(async () => {
importFontAwesome("node_modules/@fortawesome/fontawesome-free/svgs");
importRemixicon("node_modules/remixicon/icons");
})();
22 changes: 22 additions & 0 deletions scripts/icon-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { promises } from "fs";
const { readFile } = promises;

/**
* @type {import("esbuild").Plugin}
*/
const iconList = {
name: "obsidian-plugin",
setup: (build) => {
build.onLoad(
{ filter: /src\/icons\/[^\/]+?\.txt$/, namespace: "file" },
async ({ path }) => {
const lines = (await readFile(path, "utf8")).split(/\r?\n/);
return {
contents: JSON.stringify(lines),
loader: "json",
};
},
);
},
};
export default iconList;
45 changes: 36 additions & 9 deletions src/icon-packs/built-ins.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
import svg2uri from "mini-svg-data-uri";
import emojiByName from "node-emoji/lib/emoji.json";
import { setIcon } from "obsidian";

import * as iconsets from "../icons/index";
import { LucideIcon, ObsidianIcon } from "../icons";
import { BultiInIconData as BultiInIconDataType, IconInfo } from "./types";
import { ObjtoEntries } from "./utils";

export type SVGPacknames = keyof typeof iconsets;
const kabobToSnake = (name: string) => name.replace(/-/g, "_");

const LucidePackName = "luc",
ObsidianPackName = "obs";

export type SVGPacknames = typeof LucidePackName | typeof ObsidianPackName;

class BultiInIconData implements BultiInIconDataType {
public type = "bulti-in" as const;
constructor(public pack: string, public name: string, public data: string) {}
public name: string;
/** icon shortcode */
public id: string;
constructor(public pack: string, private obsidianId: string) {
this.name = kabobToSnake(obsidianId);
this.id = `${pack}_${this.name}`;
}
public get data() {
const el = createDiv();
setIcon(
el,
(this.pack === LucidePackName ? "lucide-" : "") + this.obsidianId,
);
["class", "height", "width"].forEach((k) =>
el.firstElementChild?.removeAttribute(k),
);
el.firstElementChild?.setAttribute("xmlns", "http://www.w3.org/2000/svg");
return el.innerHTML;
}
public get dataUri() {
return svg2uri(this.data);
}
Expand All @@ -24,12 +47,16 @@ const getBuiltIns = (): {
let packs = new Map<string, BultiInIconDataType>(),
ids = [] as IconInfo[],
packnames = [] as string[];
for (const [pack, icons] of ObjtoEntries(iconsets)) {

for (const [pack, icons] of [
[ObsidianPackName, ObsidianIcon],
[LucidePackName, LucideIcon],
] as const) {
packnames.push(pack);
for (const [id, svg] of ObjtoEntries(icons as Record<string, string>)) {
const name = id.substring(pack.length + 1);
packs.set(id, new BultiInIconData(pack, name, svg));
ids.push({ id, pack, name });
for (const obsidianId of icons) {
const icon = new BultiInIconData(pack, obsidianId);
packs.set(icon.id, icon);
ids.push(icon);
}
}
packnames.push(EMOJI_PACK_NAME);
Expand Down
2 changes: 1 addition & 1 deletion src/icon-packs/pack-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ export default class PackManager extends Events {
keys: ["name", "pack"],
includeScore: true,
// ignoreLocation: true,
// findAllMatches: true,
findAllMatches: true,
threshold: 0.5,
shouldSort: true,
includeMatches: true,
Expand Down
4 changes: 4 additions & 0 deletions src/icons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import LucideIcon from "./lucide-v0.17.2.txt";
import ObsidianIcon from "./obsidian-v0.13.27.txt";

export { LucideIcon, ObsidianIcon };
Loading

0 comments on commit 1d3117a

Please sign in to comment.