@riebeckite/plugin-svg-optimizer 0.0.40 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +150 -129
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -26,90 +26,14 @@ function svgOptimizer(options = {}) {
|
|
|
26
26
|
svgo: SVGO_VERSION
|
|
27
27
|
}),
|
|
28
28
|
options,
|
|
29
|
-
transformAssets: async (
|
|
29
|
+
transformAssets: async (context) => {
|
|
30
30
|
stats = emptyStats();
|
|
31
31
|
if (resolved.keepOriginal) return [];
|
|
32
32
|
const outputs = [];
|
|
33
|
-
for (const asset of assets) {
|
|
33
|
+
for (const asset of context.assets) {
|
|
34
34
|
if (extensionOf(asset.path) !== "svg") continue;
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
stats.skipped.push({ path: asset.path, reason: "oversized" });
|
|
38
|
-
logger.warn(`Skipped oversized SVG asset: ${asset.path}`);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const input = await readAsset(asset.path);
|
|
42
|
-
if (!input || input.byteLength > DEFAULT_MAX_INPUT_BYTES) {
|
|
43
|
-
stats.skipped.push({ path: asset.path, reason: "unreadable" });
|
|
44
|
-
logger.warn(
|
|
45
|
-
`Skipped oversized or unreadable SVG asset: ${asset.path}`
|
|
46
|
-
);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
const cacheKey = assetCacheKey(asset.path, input);
|
|
50
|
-
const cached = await cache.get(cacheKey);
|
|
51
|
-
if (isCachedSvgTransform(cached)) {
|
|
52
|
-
stats.cacheHits += 1;
|
|
53
|
-
tracer.event("svg-transform.cache-hit", { path: asset.path });
|
|
54
|
-
if (cached.content !== null) {
|
|
55
|
-
const output = outputFor(asset.path, cached.content);
|
|
56
|
-
stats.optimized += 1;
|
|
57
|
-
stats.savedBytes += input.byteLength - output.content.byteLength;
|
|
58
|
-
outputs.push(output);
|
|
59
|
-
} else {
|
|
60
|
-
stats.noReduction += 1;
|
|
61
|
-
}
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
stats.cacheMisses += 1;
|
|
65
|
-
const source = decode(input);
|
|
66
|
-
if (source === null) {
|
|
67
|
-
stats.skipped.push({ path: asset.path, reason: "invalid UTF-8" });
|
|
68
|
-
logger.warn(
|
|
69
|
-
`Skipped SVG asset that is not valid UTF-8: ${asset.path}`
|
|
70
|
-
);
|
|
71
|
-
await cache.set(cacheKey, { content: null });
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
const unsafe = unsafeReason(source);
|
|
75
|
-
if (unsafe) {
|
|
76
|
-
stats.skipped.push({ path: asset.path, reason: unsafe });
|
|
77
|
-
logger.warn(
|
|
78
|
-
`Skipped SVG asset containing ${unsafe}: ${asset.path}. Optimization is not sanitization.`
|
|
79
|
-
);
|
|
80
|
-
await cache.set(cacheKey, { content: null });
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
let optimized;
|
|
84
|
-
try {
|
|
85
|
-
optimized = optimize(source, config).data;
|
|
86
|
-
} catch {
|
|
87
|
-
stats.skipped.push({ path: asset.path, reason: "optimize failed" });
|
|
88
|
-
logger.warn(
|
|
89
|
-
`Skipped SVG asset that could not be optimized: ${asset.path}`
|
|
90
|
-
);
|
|
91
|
-
await cache.set(cacheKey, { content: null });
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const content = Buffer.from(optimized, "utf8");
|
|
95
|
-
if (content.byteLength >= input.byteLength) {
|
|
96
|
-
stats.noReduction += 1;
|
|
97
|
-
tracer.event("svg-transform.no-reduction", { path: asset.path });
|
|
98
|
-
await cache.set(cacheKey, { content: null });
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
await cache.set(cacheKey, {
|
|
102
|
-
content: content.toString("base64")
|
|
103
|
-
});
|
|
104
|
-
tracer.event("svg-transform.run", { path: asset.path });
|
|
105
|
-
stats.optimized += 1;
|
|
106
|
-
stats.savedBytes += input.byteLength - content.byteLength;
|
|
107
|
-
outputs.push({
|
|
108
|
-
sourcePath: asset.path,
|
|
109
|
-
path: asset.path,
|
|
110
|
-
content,
|
|
111
|
-
mimeType: MIME_TYPE
|
|
112
|
-
});
|
|
35
|
+
const output = await optimizeSvgAsset(asset, context, config, stats);
|
|
36
|
+
if (output) outputs.push(output);
|
|
113
37
|
}
|
|
114
38
|
return outputs;
|
|
115
39
|
},
|
|
@@ -124,60 +48,157 @@ function svgOptimizer(options = {}) {
|
|
|
124
48
|
ja: "最適化結果、キャッシュ利用、スキップしたアセットを表示します。"
|
|
125
49
|
},
|
|
126
50
|
order: 23,
|
|
127
|
-
collect: () => resolved
|
|
128
|
-
kind: "key-value",
|
|
129
|
-
items: [
|
|
130
|
-
{
|
|
131
|
-
key: { en: "Status", ja: "状態" },
|
|
132
|
-
value: "disabled (keepOriginal)"
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
} : {
|
|
136
|
-
kind: "key-value",
|
|
137
|
-
items: [
|
|
138
|
-
{ key: { en: "Inputs", ja: "入力" }, value: stats.inputs },
|
|
139
|
-
{
|
|
140
|
-
key: { en: "Optimized", ja: "最適化" },
|
|
141
|
-
value: stats.optimized
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
key: { en: "No reduction", ja: "削減なし" },
|
|
145
|
-
value: stats.noReduction
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
key: { en: "Cache hits", ja: "キャッシュヒット" },
|
|
149
|
-
value: stats.cacheHits
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
key: { en: "Cache misses", ja: "キャッシュミス" },
|
|
153
|
-
value: stats.cacheMisses
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
key: { en: "Saved", ja: "削減量" },
|
|
157
|
-
value: formatBytes(stats.savedBytes)
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
key: { en: "Skipped", ja: "スキップ" },
|
|
161
|
-
value: stats.skipped.length
|
|
162
|
-
},
|
|
163
|
-
...stats.skipped.length > 0 ? [
|
|
164
|
-
{
|
|
165
|
-
key: {
|
|
166
|
-
en: "Skipped assets",
|
|
167
|
-
ja: "スキップしたアセット"
|
|
168
|
-
},
|
|
169
|
-
value: stats.skipped.slice(0, 20).map(
|
|
170
|
-
(skipped) => `${skipped.path} (${skipped.reason})`
|
|
171
|
-
).join("; ")
|
|
172
|
-
}
|
|
173
|
-
] : []
|
|
174
|
-
]
|
|
175
|
-
}
|
|
51
|
+
collect: () => collectInspectorItems(resolved, stats)
|
|
176
52
|
}
|
|
177
53
|
]
|
|
178
54
|
}
|
|
179
55
|
});
|
|
180
56
|
}
|
|
57
|
+
async function optimizeSvgAsset(asset, context, config, stats) {
|
|
58
|
+
stats.inputs += 1;
|
|
59
|
+
const input = await readSvgInput(asset, context, stats);
|
|
60
|
+
if (!input) return void 0;
|
|
61
|
+
const cacheKey = assetCacheKey(asset.path, input);
|
|
62
|
+
const cached = await context.cache.get(cacheKey);
|
|
63
|
+
if (isCachedSvgTransform(cached))
|
|
64
|
+
return applyCachedSvg(cached, asset, input, context, stats);
|
|
65
|
+
stats.cacheMisses += 1;
|
|
66
|
+
const source = decode(input);
|
|
67
|
+
if (source === null)
|
|
68
|
+
return skipSvgAsset(
|
|
69
|
+
stats,
|
|
70
|
+
context,
|
|
71
|
+
cacheKey,
|
|
72
|
+
asset.path,
|
|
73
|
+
"invalid UTF-8",
|
|
74
|
+
`Skipped SVG asset that is not valid UTF-8: ${asset.path}`
|
|
75
|
+
);
|
|
76
|
+
const unsafe = unsafeReason(source);
|
|
77
|
+
if (unsafe)
|
|
78
|
+
return skipSvgAsset(
|
|
79
|
+
stats,
|
|
80
|
+
context,
|
|
81
|
+
cacheKey,
|
|
82
|
+
asset.path,
|
|
83
|
+
unsafe,
|
|
84
|
+
`Skipped SVG asset containing ${unsafe}: ${asset.path}. Optimization is not sanitization.`
|
|
85
|
+
);
|
|
86
|
+
let optimized;
|
|
87
|
+
try {
|
|
88
|
+
optimized = optimize(source, config).data;
|
|
89
|
+
} catch {
|
|
90
|
+
return skipSvgAsset(
|
|
91
|
+
stats,
|
|
92
|
+
context,
|
|
93
|
+
cacheKey,
|
|
94
|
+
asset.path,
|
|
95
|
+
"optimize failed",
|
|
96
|
+
`Skipped SVG asset that could not be optimized: ${asset.path}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const content = Buffer.from(optimized, "utf8");
|
|
100
|
+
if (content.byteLength >= input.byteLength) {
|
|
101
|
+
stats.noReduction += 1;
|
|
102
|
+
context.tracer.event("svg-transform.no-reduction", { path: asset.path });
|
|
103
|
+
await context.cache.set(cacheKey, { content: null });
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
await context.cache.set(cacheKey, { content: content.toString("base64") });
|
|
107
|
+
context.tracer.event("svg-transform.run", { path: asset.path });
|
|
108
|
+
stats.optimized += 1;
|
|
109
|
+
stats.savedBytes += input.byteLength - content.byteLength;
|
|
110
|
+
return {
|
|
111
|
+
sourcePath: asset.path,
|
|
112
|
+
path: asset.path,
|
|
113
|
+
content,
|
|
114
|
+
mimeType: MIME_TYPE
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async function readSvgInput(asset, context, stats) {
|
|
118
|
+
if (asset.size !== void 0 && asset.size > DEFAULT_MAX_INPUT_BYTES) {
|
|
119
|
+
stats.skipped.push({ path: asset.path, reason: "oversized" });
|
|
120
|
+
context.logger.warn(`Skipped oversized SVG asset: ${asset.path}`);
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
const input = await context.readAsset(asset.path);
|
|
124
|
+
if (!input || input.byteLength > DEFAULT_MAX_INPUT_BYTES) {
|
|
125
|
+
stats.skipped.push({ path: asset.path, reason: "unreadable" });
|
|
126
|
+
context.logger.warn(
|
|
127
|
+
`Skipped oversized or unreadable SVG asset: ${asset.path}`
|
|
128
|
+
);
|
|
129
|
+
return void 0;
|
|
130
|
+
}
|
|
131
|
+
return input;
|
|
132
|
+
}
|
|
133
|
+
function applyCachedSvg(cached, asset, input, context, stats) {
|
|
134
|
+
stats.cacheHits += 1;
|
|
135
|
+
context.tracer.event("svg-transform.cache-hit", { path: asset.path });
|
|
136
|
+
if (cached.content === null) {
|
|
137
|
+
stats.noReduction += 1;
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
const output = outputFor(asset.path, cached.content);
|
|
141
|
+
stats.optimized += 1;
|
|
142
|
+
stats.savedBytes += input.byteLength - output.content.byteLength;
|
|
143
|
+
return output;
|
|
144
|
+
}
|
|
145
|
+
async function skipSvgAsset(stats, context, cacheKey, path, reason, message) {
|
|
146
|
+
stats.skipped.push({ path, reason });
|
|
147
|
+
context.logger.warn(message);
|
|
148
|
+
await context.cache.set(cacheKey, { content: null });
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
function collectInspectorItems(resolved, stats) {
|
|
152
|
+
if (resolved.keepOriginal)
|
|
153
|
+
return {
|
|
154
|
+
kind: "key-value",
|
|
155
|
+
items: [
|
|
156
|
+
{
|
|
157
|
+
key: { en: "Status", ja: "状態" },
|
|
158
|
+
value: "disabled (keepOriginal)"
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
};
|
|
162
|
+
return {
|
|
163
|
+
kind: "key-value",
|
|
164
|
+
items: [
|
|
165
|
+
{ key: { en: "Inputs", ja: "入力" }, value: stats.inputs },
|
|
166
|
+
{
|
|
167
|
+
key: { en: "Optimized", ja: "最適化" },
|
|
168
|
+
value: stats.optimized
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
key: { en: "No reduction", ja: "削減なし" },
|
|
172
|
+
value: stats.noReduction
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
key: { en: "Cache hits", ja: "キャッシュヒット" },
|
|
176
|
+
value: stats.cacheHits
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
key: { en: "Cache misses", ja: "キャッシュミス" },
|
|
180
|
+
value: stats.cacheMisses
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: { en: "Saved", ja: "削減量" },
|
|
184
|
+
value: formatBytes(stats.savedBytes)
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: { en: "Skipped", ja: "スキップ" },
|
|
188
|
+
value: stats.skipped.length
|
|
189
|
+
},
|
|
190
|
+
...stats.skipped.length > 0 ? [
|
|
191
|
+
{
|
|
192
|
+
key: {
|
|
193
|
+
en: "Skipped assets",
|
|
194
|
+
ja: "スキップしたアセット"
|
|
195
|
+
},
|
|
196
|
+
value: stats.skipped.slice(0, 20).map((skipped) => `${skipped.path} (${skipped.reason})`).join("; ")
|
|
197
|
+
}
|
|
198
|
+
] : []
|
|
199
|
+
]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
181
202
|
function svgoConfig(options) {
|
|
182
203
|
const overrides = {
|
|
183
204
|
cleanupIds: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riebeckite/plugin-svg-optimizer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.42",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"svgo": "^4.1.0",
|
|
18
|
+
"@riebeckite/core": "0.0.42"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@riebeckite/plugin-image-optimizer": "0.0.40",
|
|
22
|
-
"@riebeckite/plugin-obsidian-markdown": "0.0.40",
|
|
23
21
|
"sharp": "^0.35.5",
|
|
24
|
-
"tsx": "^4.22.4"
|
|
22
|
+
"tsx": "^4.22.4",
|
|
23
|
+
"@riebeckite/plugin-image-optimizer": "0.0.42",
|
|
24
|
+
"@riebeckite/plugin-obsidian-markdown": "0.0.42"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|