-
Notifications
You must be signed in to change notification settings - Fork 0
/
find_svg.mjs
144 lines (125 loc) · 4.53 KB
/
find_svg.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { createHash } from "node:crypto";
import { createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { basename, sep as pathSep } from "node:path";
import { latestEcmaVersion, parse } from "espree";
import { Syntax, traverse } from "estraverse";
import { create } from "xmlbuilder2";
import { GetRecursiveFilesToParse } from "./dump_javascript_paths.mjs";
const svgOutputPath = "./svgs";
const pngOutputPath = "./pngs";
if (existsSync(svgOutputPath)) {
rmSync(svgOutputPath, { recursive: true });
}
if (existsSync(pngOutputPath)) {
rmSync(pngOutputPath, { recursive: true });
}
const base64PngPattern = /data:image\/png;base64,([A-Za-z0-9+\/=]+)/g;
for await (const file of GetRecursiveFilesToParse()) {
try {
console.log("::group::Parsing", file);
const code = await readFile(file, "utf8");
const file_basename = basename(file, ".js");
if (file.endsWith(".js")) {
console.log("Looking for svgs");
const sourceType = file.includes("/ssr/") ? "module" : "script";
const ast = parse(code, { ecmaVersion: latestEcmaVersion, loc: true, sourceType: sourceType });
let last_function_seen = null;
// output folder / resource folder / file name
const outputFolder = `${svgOutputPath}/${file.replace(process.cwd(), "").split(pathSep)[1]}/${file_basename}`;
if (!existsSync(outputFolder)) mkdirSync(outputFolder, { recursive: true });
traverse(ast, {
enter: function (node) {
if (node.type === Syntax.FunctionDeclaration) {
last_function_seen = node;
}
// TODO ssr doesn't have its svg elems under createElement
if (node.type === Syntax.CallExpression && node.callee?.property?.name === "createElement" && node.arguments?.[0]?.value === "svg") {
// as i understand it we don't want to go deeper if it's an svg (bc there can be svg in svg but we're only interested in the one most "outside")
this.skip();
const svg = createSvgBody(node).end({ prettyPrint: true });
const hash = createHash("sha1").update(svg).digest("hex").substring(0, 16);
console.debug(`Hash ${hash} from ${file} line ${node.loc.start.line} col ${node.loc.start.column}`);
OutputToFile(`${outputFolder}/${last_function_seen?.id.name ?? "null"}_${hash}.svg`, `${svg}\n`);
}
},
});
}
console.log("Looking for pngs");
// output folder / resource folder / file name
const outputFolder = `${pngOutputPath}/${file.replace(process.cwd(), "").split(pathSep)[1]}/${file_basename}`;
if (!existsSync(outputFolder)) mkdirSync(outputFolder, { recursive: true });
const result = code.matchAll(base64PngPattern);
for (const match of result) {
const png = Buffer.from(match[1], "base64");
const hash = createHash("sha1").update(png).digest("hex").substring(0, 16);
console.debug(`Hash ${hash} from ${file}`);
OutputToFile(`${outputFolder}/${hash}.png`, png);
}
} catch (e) {
console.error(`::error::Unable to parse "${file}":`, e);
} finally {
console.log("::endgroup::");
}
}
// TODO handle ssr svg format
function createSvgBody(node, xml = create()) {
if (!node) {
return xml;
}
try {
const elem = xml.ele(node.arguments[0].value);
node.arguments[1].properties?.forEach((prop) => {
if (prop.type === "SpreadElement" || prop.key.name === "className") return;
elem.att(fixSVGKeyName(prop.key), prop.value.value);
});
node.arguments.slice(2)?.forEach((prop) => {
if (prop.type === "CallExpression") {
createSvgBody(prop, elem);
}
});
} catch (e) {
console.warn("::warning::probably some vars that i can do nothing about", e);
}
return xml;
}
// fix svg key names that aren't the same in html vs xml
function fixSVGKeyName(key) {
// if a key has a dash in it, it's under value not name
const keyName = key.name || key.value;
switch (keyName) {
case "clipRule":
return "clip-rule";
case "clipPath":
return "clip-path";
case "fillRule":
return "fill-rule";
case "fillOpacity":
return "fill-opacity";
case "strokeLinecap":
return "stroke-linecap";
case "strokeWidth":
return "stroke-width";
case "strokeLinejoin":
return "stroke-linejoin";
case "strokeMiterlimit":
return "stroke-miterlimit";
case "strokeDasharray":
return "stroke-dasharray";
case "strokeDashoffset":
return "stroke-dashoffset";
default:
return keyName;
}
}
function OutputToFile(fileName, content) {
return new Promise((resolve) => {
const stream = createWriteStream(fileName, {
flags: "w",
encoding: "utf8",
});
stream.once("close", resolve);
stream.write(content);
stream.end();
});
}