Skip to content

Commit

Permalink
Update core.ts
Browse files Browse the repository at this point in the history
- #2 - SVG Standard support ( #3 #4)
- #12 - Subfolder support
- #9 - Message processing: improvement and exploration
  • Loading branch information
JakiChen committed Nov 19, 2024
1 parent 6402dbb commit 8a27d2a
Showing 1 changed file with 94 additions and 35 deletions.
129 changes: 94 additions & 35 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import fs from "fs/promises";
import path from "path";
import type { SVGsOptions } from ".";
import { minify, format, md5 } from "./helpers";
import type { AstroIntegrationLogger } from "astro";
import { type SVGsOptions, Error1, name } from ".";
import { getSvgFiles, minify, format, md5 } from "./helpers";
import { logger } from "./utils/logger";

export const defaults: SVGsOptions = {
input: "src/svgs",
Expand All @@ -15,69 +15,128 @@ interface Sprite {
symbolIds: string[];
}

export async function compose(
{ input, compress }: SVGsOptions,
logger: AstroIntegrationLogger,
): Promise<Sprite> {
export async function compose({
input,
compress,
}: SVGsOptions): Promise<Sprite> {
let data = "",
hash = "",
symbolIds: string[] = [];

try {
// Make sure `input` is a valid array
let err: Error1;

const inputs = Array.isArray(input) ? input : [input];
const svgFiles: string[] = [];
const svgFiles: { inputDir: string; filePath: string }[] = [];

for (const input of inputs) {
// Check if `inputPath` is valid
if (!input || !(await fs.stat(input).catch(() => false))) {
logger.warn(`Invalid directory: ${input}`);
continue;
for (const inputDir of inputs) {
if (!inputDir || !(await fs.stat(inputDir).catch(() => false))) {
err = new Error1(`Invalid directory`);
err.hint = `Ensure \`${inputDir}\` exists and is accessible.`;
throw err;
}

const dirFiles = (await fs.readdir(input)).filter((file) =>
file.endsWith(".svg"),
);
svgFiles.push(...dirFiles.map((file) => path.join(input, file)));
const dirFiles = await getSvgFiles(inputDir);
svgFiles.push(...dirFiles.map((filePath) => ({ inputDir, filePath })));
}

const sprites = (
const defsSet = new Set<string>(); // For storing unique <defs> content
const symbolBodySet = new Set<string>(); // Set for unique SVG bodies based on content hash

const symbols = (
await Promise.all(
svgFiles.map(async (filePath) => {
svgFiles.map(async ({ inputDir, filePath }) => {
let body = await fs.readFile(filePath, "utf8");

const symbolId = path
.basename(filePath, ".svg")
.replace(/[^a-zA-Z0-9_-]/g, "_");
// Validate if file content is a valid SVG
if (!body.includes("<svg")) {
// logger.add("Invalid SVG file(s)", `- ${filePath}`);
return ""; // Skip invalid SVG content
}

// For typechecking
symbolIds.push(symbolId);
// Extract and store <defs> content if exists
const defsMatch = body.match(/<defs>([\s\S]*?)<\/defs>/);
if (defsMatch) {
defsSet.add(defsMatch[1]); // Add unique defs content
}

// Generate a hash of the body content to detect duplicates
const bodyHash = md5(body); // Generate a hash of the SVG body content

// If the same content already exists, skip adding it
if (symbolBodySet.has(bodyHash)) {
return ""; // Skip if the content is a duplicate
}

// Add the content hash to the set to prevent future duplicates
symbolBodySet.add(bodyHash);

// Match the viewBox value
// Generate symbolId, either file name or path-based if content differs
const fileName = path.basename(filePath, ".svg");
let symbolId: string;

if (bodyHash) {
symbolId = fileName;
} else {
const relativePath = path.relative(inputDir, filePath);
symbolId = relativePath
.replace(/\.svg$/, "")
.replace(/[^a-zA-Z0-9_-]/g, "_");
// .replace(/[\/\\]/g, ":")
// .replace(/[^a-zA-Z0-9_-]/g, ":");
}

// Check for viewBox, or derive from width/height if absent
const viewBoxMatch = body.match(/viewBox="([^"]+)"/);
const viewBox = viewBoxMatch ? `viewBox="${viewBoxMatch[1]}"` : "";
let viewBox = "";

if (viewBoxMatch) {
viewBox = `viewBox="${viewBoxMatch[1]}"`;
} else {
const widthMatch = body.match(/width="([^"]+)"/);
const heightMatch = body.match(/height="([^"]+)"/);
if (widthMatch && heightMatch) {
const width = parseFloat(widthMatch[1]);
const height = parseFloat(heightMatch[1]);
if (!isNaN(width) && !isNaN(height)) {
viewBox = `viewBox="0 0 ${width} ${height}"`;
}
}
}

// Remove the <svg> wrapper and clean up the content
// Clean SVG content
body = body
.replace(/<\?xml[^>]*\?>\s*/g, "")
.replace(/<svg[^>]*>/, "")
.replace(/<\/svg>\s*$/, "")
.replace(/\s*id="[^"]*"/g, "")
.replace(/<style[^>]*>[\s\S]*?<\/style>/g, "");
.replace(/<style[^>]*>[\s\S]*?<\/style>/g, "")
.replace(/<defs>[\s\S]*?<\/defs>/g, "") // Remove original <defs>
.replace(/<!--[\s\S]*?-->/g, "");

// Return the symbol with the correct viewBox and id
// Add the symbolId and body to the final list
symbolIds.push(symbolId);
return `<symbol id="${symbolId}" ${viewBox}>${body}</symbol>`;
}),
)
).join("\n");
)
.filter(Boolean)
.join("\n");

data = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs>${sprites}</defs></svg>`;
// Combine all unique defs content into a single <defs> block
let defs =
defsSet.size > 0 ? `<defs>${Array.from(defsSet).join("")}</defs>` : "";
defs = defs.replace(/<!--[\s\S]*?-->/g, "");

// Assemble the final SVG sprite data
data = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">${defs}${symbols}</svg>`;
data = compress !== "beautify" ? minify(data, compress!) : format(data);
hash = md5(data);
} catch (error) {
console.error(error);
} catch (err) {
if (err instanceof Error1) {
throw err;
}
}

// logger.print(name);
return { data, hash, symbolIds };
}

0 comments on commit 8a27d2a

Please sign in to comment.