@effing/satori 0.11.1 → 0.12.0

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.
@@ -1,26 +1,354 @@
1
1
  // src/vite/index.ts
2
+ import { posix } from "path";
2
3
  import { fileURLToPath } from "url";
4
+ var TARGET_MODULES = /* @__PURE__ */ new Set(["@effing/satori/pool", "@effing/satori"]);
5
+ var SCOPE_TYPES = /* @__PURE__ */ new Set([
6
+ "FunctionDeclaration",
7
+ "FunctionExpression",
8
+ "ArrowFunctionExpression",
9
+ "ClassExpression",
10
+ "BlockStatement",
11
+ "ForStatement",
12
+ "ForInStatement",
13
+ "ForOfStatement",
14
+ "SwitchStatement",
15
+ "CatchClause"
16
+ ]);
17
+ function isEstreeNode(value) {
18
+ return typeof value === "object" && value !== null && typeof value.type === "string" && typeof value.start === "number" && typeof value.end === "number";
19
+ }
20
+ function walk(node, enter, leave, parent = null) {
21
+ enter(node, parent);
22
+ for (const key of Object.keys(node)) {
23
+ if (key === "type" || key === "start" || key === "end") continue;
24
+ const val = node[key];
25
+ if (isEstreeNode(val)) {
26
+ walk(val, enter, leave, node);
27
+ } else if (Array.isArray(val)) {
28
+ for (const item of val) {
29
+ if (isEstreeNode(item)) {
30
+ walk(item, enter, leave, node);
31
+ }
32
+ }
33
+ }
34
+ }
35
+ leave?.(node, parent);
36
+ }
37
+ function findImportedLocalNames(ast) {
38
+ const names = /* @__PURE__ */ new Set();
39
+ const body = ast.body;
40
+ if (!Array.isArray(body)) return names;
41
+ for (const node of body) {
42
+ if (node.type !== "ImportDeclaration" || !isEstreeNode(node.source) || !TARGET_MODULES.has(node.source.value))
43
+ continue;
44
+ if (node.importKind === "type") continue;
45
+ const specifiers = node.specifiers;
46
+ if (!Array.isArray(specifiers)) continue;
47
+ for (const spec of specifiers) {
48
+ if (spec.type !== "ImportSpecifier") continue;
49
+ if (spec.importKind === "type") continue;
50
+ const imported = spec.imported;
51
+ const local = spec.local;
52
+ if (imported && imported.name === "createSatoriPool" && local)
53
+ names.add(local.name);
54
+ }
55
+ }
56
+ return names;
57
+ }
58
+ function collectPatternNames(pattern, tracked, into) {
59
+ switch (pattern.type) {
60
+ case "Identifier":
61
+ if (tracked.has(pattern.name)) into.add(pattern.name);
62
+ break;
63
+ case "ObjectPattern": {
64
+ const props = pattern.properties;
65
+ if (Array.isArray(props)) {
66
+ for (const prop of props) {
67
+ if (prop.type === "RestElement") {
68
+ const arg = prop.argument;
69
+ if (isEstreeNode(arg)) collectPatternNames(arg, tracked, into);
70
+ } else {
71
+ const target = prop.value ?? prop.key;
72
+ if (isEstreeNode(target))
73
+ collectPatternNames(target, tracked, into);
74
+ }
75
+ }
76
+ }
77
+ break;
78
+ }
79
+ case "ArrayPattern": {
80
+ const elems = pattern.elements;
81
+ if (Array.isArray(elems)) {
82
+ for (const elem of elems) {
83
+ if (isEstreeNode(elem)) collectPatternNames(elem, tracked, into);
84
+ }
85
+ }
86
+ break;
87
+ }
88
+ case "RestElement": {
89
+ const arg = pattern.argument;
90
+ if (isEstreeNode(arg)) collectPatternNames(arg, tracked, into);
91
+ break;
92
+ }
93
+ case "AssignmentPattern": {
94
+ const left = pattern.left;
95
+ if (isEstreeNode(left)) collectPatternNames(left, tracked, into);
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ function collectHoistedVarNames(body, tracked) {
101
+ const found = /* @__PURE__ */ new Set();
102
+ function scanStatements(stmts) {
103
+ for (const stmt of stmts) {
104
+ if (stmt.type === "VariableDeclaration" && stmt.kind === "var") {
105
+ const decls = stmt.declarations;
106
+ if (!Array.isArray(decls)) continue;
107
+ for (const d of decls) {
108
+ const id = d.id;
109
+ if (isEstreeNode(id)) collectPatternNames(id, tracked, found);
110
+ }
111
+ }
112
+ if (stmt.type === "BlockStatement" || stmt.type === "SwitchStatement") {
113
+ const inner = stmt.body ?? stmt.cases;
114
+ if (Array.isArray(inner)) scanStatements(inner);
115
+ }
116
+ if (stmt.type === "IfStatement") {
117
+ if (isEstreeNode(stmt.consequent)) scanStatements([stmt.consequent]);
118
+ if (isEstreeNode(stmt.alternate)) scanStatements([stmt.alternate]);
119
+ }
120
+ if (stmt.type === "ForStatement" || stmt.type === "ForInStatement" || stmt.type === "ForOfStatement" || stmt.type === "WhileStatement" || stmt.type === "DoWhileStatement") {
121
+ if (isEstreeNode(stmt.body)) scanStatements([stmt.body]);
122
+ if (isEstreeNode(stmt.init)) scanStatements([stmt.init]);
123
+ if (isEstreeNode(stmt.left)) scanStatements([stmt.left]);
124
+ }
125
+ if (stmt.type === "TryStatement") {
126
+ if (isEstreeNode(stmt.block)) scanStatements([stmt.block]);
127
+ if (isEstreeNode(stmt.handler)) {
128
+ const handlerBody = stmt.handler.body;
129
+ if (isEstreeNode(handlerBody)) scanStatements([handlerBody]);
130
+ }
131
+ if (isEstreeNode(stmt.finalizer)) scanStatements([stmt.finalizer]);
132
+ }
133
+ if (stmt.type === "LabeledStatement" && isEstreeNode(stmt.body)) {
134
+ scanStatements([stmt.body]);
135
+ }
136
+ if (stmt.type === "SwitchCase") {
137
+ const consequent = stmt.consequent;
138
+ if (Array.isArray(consequent)) scanStatements(consequent);
139
+ }
140
+ }
141
+ }
142
+ scanStatements(body);
143
+ return found;
144
+ }
145
+ function collectBlockScopedNames(body, tracked) {
146
+ const found = /* @__PURE__ */ new Set();
147
+ for (const stmt of body) {
148
+ if (stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration") {
149
+ const id = stmt.id;
150
+ if (id?.type === "Identifier" && tracked.has(id.name))
151
+ found.add(id.name);
152
+ }
153
+ if (stmt.type === "VariableDeclaration" && (stmt.kind === "let" || stmt.kind === "const")) {
154
+ const decls = stmt.declarations;
155
+ if (Array.isArray(decls)) {
156
+ for (const d of decls) {
157
+ const id = d.id;
158
+ if (isEstreeNode(id)) collectPatternNames(id, tracked, found);
159
+ }
160
+ }
161
+ }
162
+ }
163
+ return found;
164
+ }
165
+ function collectEdits(ast, localNames, warn) {
166
+ const edits = [];
167
+ const scopeStack = [
168
+ { names: /* @__PURE__ */ new Set(), isFunction: true }
169
+ ];
170
+ function isShadowed(name) {
171
+ for (let i = scopeStack.length - 1; i >= 1; i--) {
172
+ if (scopeStack[i].names.has(name)) return true;
173
+ }
174
+ return false;
175
+ }
176
+ function nearestFunctionScope() {
177
+ for (let i = scopeStack.length - 1; i >= 0; i--) {
178
+ if (scopeStack[i].isFunction) return scopeStack[i];
179
+ }
180
+ return scopeStack[0];
181
+ }
182
+ function getFunctionBody(node) {
183
+ const body = node.body;
184
+ if (isEstreeNode(body) && body.type === "BlockStatement") {
185
+ const stmts = body.body;
186
+ return Array.isArray(stmts) ? stmts : null;
187
+ }
188
+ return null;
189
+ }
190
+ walk(
191
+ ast,
192
+ (node, parent) => {
193
+ if (SCOPE_TYPES.has(node.type)) {
194
+ const isFunction = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
195
+ const scope = { names: /* @__PURE__ */ new Set(), isFunction };
196
+ scopeStack.push(scope);
197
+ if (isFunction) {
198
+ const body = getFunctionBody(node);
199
+ if (body) {
200
+ const hoisted = collectHoistedVarNames(body, localNames);
201
+ for (const n of hoisted) scope.names.add(n);
202
+ }
203
+ const params = node.params;
204
+ if (Array.isArray(params)) {
205
+ for (const p of params) {
206
+ collectPatternNames(p, localNames, scope.names);
207
+ }
208
+ }
209
+ if (node.type === "FunctionExpression") {
210
+ const id = node.id;
211
+ if (id?.type === "Identifier" && localNames.has(id.name))
212
+ scope.names.add(id.name);
213
+ }
214
+ }
215
+ if (node.type === "ClassExpression") {
216
+ const id = node.id;
217
+ if (id?.type === "Identifier" && localNames.has(id.name))
218
+ scope.names.add(id.name);
219
+ }
220
+ if (node.type === "BlockStatement") {
221
+ const body = node.body;
222
+ if (Array.isArray(body)) {
223
+ const scoped = collectBlockScopedNames(body, localNames);
224
+ for (const n of scoped) scope.names.add(n);
225
+ }
226
+ }
227
+ if (node.type === "SwitchStatement") {
228
+ const cases = node.cases;
229
+ if (Array.isArray(cases)) {
230
+ const stmts = [];
231
+ for (const c of cases) {
232
+ const consequent = c.consequent;
233
+ if (Array.isArray(consequent)) stmts.push(...consequent);
234
+ }
235
+ const scoped = collectBlockScopedNames(stmts, localNames);
236
+ for (const n of scoped) scope.names.add(n);
237
+ }
238
+ }
239
+ if (node.type === "CatchClause") {
240
+ const param = node.param;
241
+ if (isEstreeNode(param))
242
+ collectPatternNames(param, localNames, scope.names);
243
+ }
244
+ }
245
+ if (node.type === "VariableDeclarator" && parent) {
246
+ const id = node.id;
247
+ if (isEstreeNode(id)) {
248
+ const kind = parent.kind;
249
+ const target = kind === "var" ? nearestFunctionScope().names : kind === "let" || kind === "const" ? scopeStack[scopeStack.length - 1].names : null;
250
+ if (target) collectPatternNames(id, localNames, target);
251
+ }
252
+ }
253
+ if (node.type === "FunctionDeclaration") {
254
+ const id = node.id;
255
+ if (id?.type === "Identifier" && localNames.has(id.name)) {
256
+ const target = scopeStack.length >= 2 ? scopeStack[scopeStack.length - 2] : scopeStack[0];
257
+ target.names.add(id.name);
258
+ }
259
+ }
260
+ if (node.type === "ClassDeclaration") {
261
+ const id = node.id;
262
+ if (id?.type === "Identifier" && localNames.has(id.name)) {
263
+ scopeStack[scopeStack.length - 1].names.add(id.name);
264
+ }
265
+ }
266
+ if (node.type === "CallExpression") {
267
+ const callee = node.callee;
268
+ if (callee?.type === "Identifier" && localNames.has(callee.name) && !isShadowed(callee.name)) {
269
+ if (typeof callee.end !== "number" || typeof node.end !== "number")
270
+ return;
271
+ const args = node.arguments;
272
+ if (!Array.isArray(args) || args.length === 0) {
273
+ edits.push({
274
+ start: callee.end,
275
+ end: node.end,
276
+ replacement: '({ workerFile: "__SATORI_WORKER_FILE__" })'
277
+ });
278
+ } else if (args[0].type === "ObjectExpression") {
279
+ if (typeof args[0].start !== "number") return;
280
+ edits.push({
281
+ start: args[0].start + 1,
282
+ end: args[0].start + 1,
283
+ replacement: ' workerFile: "__SATORI_WORKER_FILE__",'
284
+ });
285
+ } else {
286
+ warn(
287
+ `satoriPoolPlugin: unexpected first argument type "${args[0].type}" in createSatoriPool() call`
288
+ );
289
+ }
290
+ }
291
+ }
292
+ },
293
+ (node) => {
294
+ if (SCOPE_TYPES.has(node.type)) {
295
+ scopeStack.pop();
296
+ }
297
+ }
298
+ );
299
+ return edits;
300
+ }
301
+ function applyEdits(code, edits) {
302
+ const sorted = [...edits].sort((a, b) => b.start - a.start);
303
+ let result = code;
304
+ for (const { start, end, replacement } of sorted) {
305
+ result = result.slice(0, start) + replacement + result.slice(end);
306
+ }
307
+ return result;
308
+ }
3
309
  function satoriPoolPlugin() {
4
310
  let resolvedConfig;
5
311
  return {
6
312
  name: "@effing/satori:worker",
7
313
  apply: "build",
314
+ config() {
315
+ return { ssr: { external: ["tinypool"] } };
316
+ },
8
317
  configResolved(config) {
9
318
  resolvedConfig = config;
10
319
  },
11
- transform(code, _id, options) {
12
- if (!options?.ssr) return;
13
- const pattern = /\bcreateSatoriPool\(\s*(\)|\{)/g;
14
- let matched = false;
15
- const result = code.replace(pattern, (_match, capture) => {
16
- matched = true;
17
- if (capture === ")") {
18
- return 'createSatoriPool({ workerFile: import.meta.dirname + "/satori-worker.js" })';
19
- }
20
- return 'createSatoriPool({ workerFile: import.meta.dirname + "/satori-worker.js", ';
21
- });
22
- if (!matched) return;
23
- return { code: result, map: null };
320
+ transform(code, id, options) {
321
+ if (!options?.ssr || !code.includes("createSatoriPool")) return;
322
+ let ast;
323
+ try {
324
+ ast = this.parse(code);
325
+ } catch {
326
+ if (!id.startsWith("\0")) {
327
+ this.warn(`satoriPoolPlugin: skipped AST transform for ${id}`);
328
+ }
329
+ return;
330
+ }
331
+ const localNames = findImportedLocalNames(ast);
332
+ if (localNames.size === 0) return;
333
+ const edits = collectEdits(
334
+ ast,
335
+ localNames,
336
+ (msg) => this.warn(`${msg} (${id})`)
337
+ );
338
+ if (edits.length === 0) return;
339
+ return { code: applyEdits(code, edits), map: null };
340
+ },
341
+ renderChunk(code, chunk) {
342
+ const placeholder = '"__SATORI_WORKER_FILE__"';
343
+ if (!code.includes(placeholder)) return;
344
+ const chunkDir = posix.dirname(chunk.fileName);
345
+ const relToRoot = chunkDir === "." ? "." : posix.relative(chunkDir, ".");
346
+ const workerRel = posix.join(relToRoot, "satori-worker.js");
347
+ const expr = `import.meta.dirname + ${JSON.stringify("/" + workerRel)}`;
348
+ return {
349
+ code: code.replaceAll(placeholder, expr),
350
+ map: null
351
+ };
24
352
  },
25
353
  async writeBundle(outputOptions) {
26
354
  if (!resolvedConfig.build.ssr) return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["import { fileURLToPath } from \"url\";\n\nimport type { Plugin, ResolvedConfig } from \"vite\";\n\n/**\n * Vite plugin that bundles the `@effing/satori` worker into the SSR output and\n * rewrites `createSatoriPool()` calls to point at it.\n *\n * **This plugin is required for production SSR builds.** Without it the worker\n * path resolved via `import.meta.url` breaks after Vite bundles the pool code,\n * because the URL points at the build output directory instead of `node_modules`.\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { satoriPoolPlugin } from \"@effing/satori/vite\";\n *\n * export default defineConfig({\n * plugins: [satoriPoolPlugin()],\n * });\n * ```\n */\nexport function satoriPoolPlugin(): Plugin {\n let resolvedConfig: ResolvedConfig;\n\n return {\n name: \"@effing/satori:worker\",\n apply: \"build\",\n\n configResolved(config) {\n resolvedConfig = config;\n },\n\n transform(code, _id, options) {\n if (!options?.ssr) return;\n\n const pattern = /\\bcreateSatoriPool\\(\\s*(\\)|\\{)/g;\n let matched = false;\n const result = code.replace(pattern, (_match, capture: string) => {\n matched = true;\n if (capture === \")\") {\n return 'createSatoriPool({ workerFile: import.meta.dirname + \"/satori-worker.js\" })';\n }\n // capture === \"{\"\n return 'createSatoriPool({ workerFile: import.meta.dirname + \"/satori-worker.js\", ';\n });\n\n if (!matched) return;\n return { code: result, map: null };\n },\n\n async writeBundle(outputOptions) {\n if (!resolvedConfig.build.ssr) return;\n\n const workerEntry = fileURLToPath(\n new URL(\"../worker/index.js\", import.meta.url),\n );\n const outDir = outputOptions.dir ?? resolvedConfig.build.outDir;\n\n const { build } = await import(\"vite\");\n await build({\n configFile: false,\n logLevel: \"silent\",\n build: {\n write: true,\n emptyOutDir: false,\n outDir,\n lib: {\n entry: workerEntry,\n formats: [\"es\"],\n fileName: () => \"satori-worker.js\",\n },\n rollupOptions: {\n external: [\"@resvg/resvg-js\"],\n },\n },\n });\n },\n };\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;AAsBvB,SAAS,mBAA2B;AACzC,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IAEP,eAAe,QAAQ;AACrB,uBAAiB;AAAA,IACnB;AAAA,IAEA,UAAU,MAAM,KAAK,SAAS;AAC5B,UAAI,CAAC,SAAS,IAAK;AAEnB,YAAM,UAAU;AAChB,UAAI,UAAU;AACd,YAAM,SAAS,KAAK,QAAQ,SAAS,CAAC,QAAQ,YAAoB;AAChE,kBAAU;AACV,YAAI,YAAY,KAAK;AACnB,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT,CAAC;AAED,UAAI,CAAC,QAAS;AACd,aAAO,EAAE,MAAM,QAAQ,KAAK,KAAK;AAAA,IACnC;AAAA,IAEA,MAAM,YAAY,eAAe;AAC/B,UAAI,CAAC,eAAe,MAAM,IAAK;AAE/B,YAAM,cAAc;AAAA,QAClB,IAAI,IAAI,sBAAsB,YAAY,GAAG;AAAA,MAC/C;AACA,YAAM,SAAS,cAAc,OAAO,eAAe,MAAM;AAEzD,YAAM,EAAE,MAAM,IAAI,MAAM,OAAO,MAAM;AACrC,YAAM,MAAM;AAAA,QACV,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,OAAO;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb;AAAA,UACA,KAAK;AAAA,YACH,OAAO;AAAA,YACP,SAAS,CAAC,IAAI;AAAA,YACd,UAAU,MAAM;AAAA,UAClB;AAAA,UACA,eAAe;AAAA,YACb,UAAU,CAAC,iBAAiB;AAAA,UAC9B;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["import { posix } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { Plugin, ResolvedConfig } from \"vite\";\n\ninterface EstreeNode {\n type: string;\n start: number;\n end: number;\n [key: string]: unknown;\n}\n\ninterface TextEdit {\n start: number;\n end: number;\n replacement: string;\n}\n\nconst TARGET_MODULES = new Set([\"@effing/satori/pool\", \"@effing/satori\"]);\n\nconst SCOPE_TYPES = new Set([\n \"FunctionDeclaration\",\n \"FunctionExpression\",\n \"ArrowFunctionExpression\",\n \"ClassExpression\",\n \"BlockStatement\",\n \"ForStatement\",\n \"ForInStatement\",\n \"ForOfStatement\",\n \"SwitchStatement\",\n \"CatchClause\",\n]);\n\nfunction isEstreeNode(value: unknown): value is EstreeNode {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as EstreeNode).type === \"string\" &&\n typeof (value as EstreeNode).start === \"number\" &&\n typeof (value as EstreeNode).end === \"number\"\n );\n}\n\nfunction walk(\n node: EstreeNode,\n enter: (node: EstreeNode, parent: EstreeNode | null) => void,\n leave?: (node: EstreeNode, parent: EstreeNode | null) => void,\n parent: EstreeNode | null = null,\n): void {\n enter(node, parent);\n for (const key of Object.keys(node)) {\n if (key === \"type\" || key === \"start\" || key === \"end\") continue;\n const val = node[key];\n if (isEstreeNode(val)) {\n walk(val, enter, leave, node);\n } else if (Array.isArray(val)) {\n for (const item of val) {\n if (isEstreeNode(item)) {\n walk(item, enter, leave, node);\n }\n }\n }\n }\n leave?.(node, parent);\n}\n\nfunction findImportedLocalNames(ast: EstreeNode): Set<string> {\n const names = new Set<string>();\n const body = ast.body as EstreeNode[] | undefined;\n if (!Array.isArray(body)) return names;\n\n for (const node of body) {\n if (\n node.type !== \"ImportDeclaration\" ||\n !isEstreeNode(node.source) ||\n !TARGET_MODULES.has(node.source.value as string)\n )\n continue;\n if ((node.importKind as string | undefined) === \"type\") continue;\n\n const specifiers = node.specifiers as EstreeNode[] | undefined;\n if (!Array.isArray(specifiers)) continue;\n\n for (const spec of specifiers) {\n if (spec.type !== \"ImportSpecifier\") continue;\n if ((spec.importKind as string | undefined) === \"type\") continue;\n const imported = spec.imported as EstreeNode | undefined;\n const local = spec.local as EstreeNode | undefined;\n if (imported && (imported.name as string) === \"createSatoriPool\" && local)\n names.add(local.name as string);\n }\n }\n return names;\n}\n\nfunction collectPatternNames(\n pattern: EstreeNode,\n tracked: Set<string>,\n into: Set<string>,\n): void {\n switch (pattern.type) {\n case \"Identifier\":\n if (tracked.has(pattern.name as string)) into.add(pattern.name as string);\n break;\n case \"ObjectPattern\": {\n const props = pattern.properties as EstreeNode[] | undefined;\n if (Array.isArray(props)) {\n for (const prop of props) {\n if (prop.type === \"RestElement\") {\n const arg = prop.argument as EstreeNode | undefined;\n if (isEstreeNode(arg)) collectPatternNames(arg, tracked, into);\n } else {\n // Property node — use value if present, fall back to key for\n // robustness across parser node shapes (shorthand properties)\n const target = (prop.value ?? prop.key) as EstreeNode | undefined;\n if (isEstreeNode(target))\n collectPatternNames(target, tracked, into);\n }\n }\n }\n break;\n }\n case \"ArrayPattern\": {\n const elems = pattern.elements as (EstreeNode | null)[] | undefined;\n if (Array.isArray(elems)) {\n for (const elem of elems) {\n if (isEstreeNode(elem)) collectPatternNames(elem, tracked, into);\n }\n }\n break;\n }\n case \"RestElement\": {\n const arg = pattern.argument as EstreeNode | undefined;\n if (isEstreeNode(arg)) collectPatternNames(arg, tracked, into);\n break;\n }\n case \"AssignmentPattern\": {\n const left = pattern.left as EstreeNode | undefined;\n if (isEstreeNode(left)) collectPatternNames(left, tracked, into);\n break;\n }\n }\n}\n\nfunction collectHoistedVarNames(\n body: EstreeNode[],\n tracked: Set<string>,\n): Set<string> {\n const found = new Set<string>();\n\n function scanStatements(stmts: EstreeNode[]): void {\n for (const stmt of stmts) {\n if (\n stmt.type === \"VariableDeclaration\" &&\n (stmt.kind as string) === \"var\"\n ) {\n const decls = stmt.declarations as EstreeNode[] | undefined;\n if (!Array.isArray(decls)) continue;\n for (const d of decls) {\n const id = d.id as EstreeNode | undefined;\n if (isEstreeNode(id)) collectPatternNames(id, tracked, found);\n }\n }\n // Recurse into blocks but NOT into nested functions\n if (stmt.type === \"BlockStatement\" || stmt.type === \"SwitchStatement\") {\n const inner = (stmt.body ?? stmt.cases) as EstreeNode[] | undefined;\n if (Array.isArray(inner)) scanStatements(inner);\n }\n if (stmt.type === \"IfStatement\") {\n if (isEstreeNode(stmt.consequent)) scanStatements([stmt.consequent]);\n if (isEstreeNode(stmt.alternate)) scanStatements([stmt.alternate]);\n }\n if (\n stmt.type === \"ForStatement\" ||\n stmt.type === \"ForInStatement\" ||\n stmt.type === \"ForOfStatement\" ||\n stmt.type === \"WhileStatement\" ||\n stmt.type === \"DoWhileStatement\"\n ) {\n if (isEstreeNode(stmt.body)) scanStatements([stmt.body]);\n if (isEstreeNode(stmt.init)) scanStatements([stmt.init]);\n if (isEstreeNode(stmt.left)) scanStatements([stmt.left]);\n }\n if (stmt.type === \"TryStatement\") {\n if (isEstreeNode(stmt.block)) scanStatements([stmt.block]);\n if (isEstreeNode(stmt.handler)) {\n const handlerBody = stmt.handler.body as EstreeNode | undefined;\n if (isEstreeNode(handlerBody)) scanStatements([handlerBody]);\n }\n if (isEstreeNode(stmt.finalizer)) scanStatements([stmt.finalizer]);\n }\n if (stmt.type === \"LabeledStatement\" && isEstreeNode(stmt.body)) {\n scanStatements([stmt.body]);\n }\n if (stmt.type === \"SwitchCase\") {\n const consequent = stmt.consequent as EstreeNode[] | undefined;\n if (Array.isArray(consequent)) scanStatements(consequent);\n }\n }\n }\n\n scanStatements(body);\n return found;\n}\n\nfunction collectBlockScopedNames(\n body: EstreeNode[],\n tracked: Set<string>,\n): Set<string> {\n const found = new Set<string>();\n for (const stmt of body) {\n if (\n stmt.type === \"FunctionDeclaration\" ||\n stmt.type === \"ClassDeclaration\"\n ) {\n const id = stmt.id as EstreeNode | undefined;\n if (id?.type === \"Identifier\" && tracked.has(id.name as string))\n found.add(id.name as string);\n }\n if (\n stmt.type === \"VariableDeclaration\" &&\n ((stmt.kind as string) === \"let\" || (stmt.kind as string) === \"const\")\n ) {\n const decls = stmt.declarations as EstreeNode[] | undefined;\n if (Array.isArray(decls)) {\n for (const d of decls) {\n const id = d.id as EstreeNode | undefined;\n if (isEstreeNode(id)) collectPatternNames(id, tracked, found);\n }\n }\n }\n }\n return found;\n}\n\nfunction collectEdits(\n ast: EstreeNode,\n localNames: Set<string>,\n warn: (msg: string) => void,\n): TextEdit[] {\n const edits: TextEdit[] = [];\n const scopeStack: { names: Set<string>; isFunction: boolean }[] = [\n { names: new Set(), isFunction: true },\n ];\n\n function isShadowed(name: string): boolean {\n for (let i = scopeStack.length - 1; i >= 1; i--) {\n if (scopeStack[i].names.has(name)) return true;\n }\n return false;\n }\n\n function nearestFunctionScope(): { names: Set<string>; isFunction: boolean } {\n for (let i = scopeStack.length - 1; i >= 0; i--) {\n if (scopeStack[i].isFunction) return scopeStack[i];\n }\n return scopeStack[0];\n }\n\n function getFunctionBody(node: EstreeNode): EstreeNode[] | null {\n const body = node.body as EstreeNode | EstreeNode[] | undefined;\n if (isEstreeNode(body) && body.type === \"BlockStatement\") {\n const stmts = body.body as EstreeNode[] | undefined;\n return Array.isArray(stmts) ? stmts : null;\n }\n return null;\n }\n\n walk(\n ast,\n (node, parent) => {\n // Push scope\n if (SCOPE_TYPES.has(node.type)) {\n const isFunction =\n node.type === \"FunctionDeclaration\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"ArrowFunctionExpression\";\n\n const scope = { names: new Set<string>(), isFunction };\n scopeStack.push(scope);\n\n // Prescan for hoisting\n if (isFunction) {\n const body = getFunctionBody(node);\n if (body) {\n const hoisted = collectHoistedVarNames(body, localNames);\n for (const n of hoisted) scope.names.add(n);\n }\n // Add function params\n const params = node.params as EstreeNode[] | undefined;\n if (Array.isArray(params)) {\n for (const p of params) {\n collectPatternNames(p, localNames, scope.names);\n }\n }\n // Named FunctionExpression id is only visible inside its own body\n if (node.type === \"FunctionExpression\") {\n const id = node.id as EstreeNode | undefined;\n if (id?.type === \"Identifier\" && localNames.has(id.name as string))\n scope.names.add(id.name as string);\n }\n }\n\n // Named ClassExpression id is only visible inside the class body\n if (node.type === \"ClassExpression\") {\n const id = node.id as EstreeNode | undefined;\n if (id?.type === \"Identifier\" && localNames.has(id.name as string))\n scope.names.add(id.name as string);\n }\n\n if (node.type === \"BlockStatement\") {\n const body = node.body as EstreeNode[] | undefined;\n if (Array.isArray(body)) {\n const scoped = collectBlockScopedNames(body, localNames);\n for (const n of scoped) scope.names.add(n);\n }\n }\n\n if (node.type === \"SwitchStatement\") {\n const cases = node.cases as EstreeNode[] | undefined;\n if (Array.isArray(cases)) {\n const stmts: EstreeNode[] = [];\n for (const c of cases) {\n const consequent = c.consequent as EstreeNode[] | undefined;\n if (Array.isArray(consequent)) stmts.push(...consequent);\n }\n const scoped = collectBlockScopedNames(stmts, localNames);\n for (const n of scoped) scope.names.add(n);\n }\n }\n\n if (node.type === \"CatchClause\") {\n const param = node.param as EstreeNode | undefined;\n if (isEstreeNode(param))\n collectPatternNames(param, localNames, scope.names);\n }\n }\n\n // Track variable declarations\n if (node.type === \"VariableDeclarator\" && parent) {\n const id = node.id as EstreeNode | undefined;\n if (isEstreeNode(id)) {\n const kind = (parent as EstreeNode).kind as string | undefined;\n const target =\n kind === \"var\"\n ? nearestFunctionScope().names\n : kind === \"let\" || kind === \"const\"\n ? scopeStack[scopeStack.length - 1].names\n : null;\n if (target) collectPatternNames(id, localNames, target);\n }\n }\n\n // Track function declarations (name goes to enclosing scope)\n if (node.type === \"FunctionDeclaration\") {\n const id = node.id as EstreeNode | undefined;\n if (id?.type === \"Identifier\" && localNames.has(id.name as string)) {\n const target =\n scopeStack.length >= 2\n ? scopeStack[scopeStack.length - 2]\n : scopeStack[0];\n target.names.add(id.name as string);\n }\n }\n\n // Track class declarations (block-scoped, name goes to current scope)\n if (node.type === \"ClassDeclaration\") {\n const id = node.id as EstreeNode | undefined;\n if (id?.type === \"Identifier\" && localNames.has(id.name as string)) {\n scopeStack[scopeStack.length - 1].names.add(id.name as string);\n }\n }\n\n // Detect call sites\n if (node.type === \"CallExpression\") {\n const callee = node.callee as EstreeNode | undefined;\n if (\n callee?.type === \"Identifier\" &&\n localNames.has(callee.name as string) &&\n !isShadowed(callee.name as string)\n ) {\n if (typeof callee.end !== \"number\" || typeof node.end !== \"number\")\n return;\n\n const args = node.arguments as EstreeNode[] | undefined;\n if (!Array.isArray(args) || args.length === 0) {\n // No args: replace entire arg list\n edits.push({\n start: callee.end,\n end: node.end,\n replacement: '({ workerFile: \"__SATORI_WORKER_FILE__\" })',\n });\n } else if (args[0].type === \"ObjectExpression\") {\n if (typeof args[0].start !== \"number\") return;\n edits.push({\n start: args[0].start + 1,\n end: args[0].start + 1,\n replacement: ' workerFile: \"__SATORI_WORKER_FILE__\",',\n });\n } else {\n warn(\n `satoriPoolPlugin: unexpected first argument type \"${args[0].type}\" in createSatoriPool() call`,\n );\n }\n }\n }\n },\n (node) => {\n if (SCOPE_TYPES.has(node.type)) {\n scopeStack.pop();\n }\n },\n );\n\n return edits;\n}\n\nfunction applyEdits(code: string, edits: TextEdit[]): string {\n const sorted = [...edits].sort((a, b) => b.start - a.start);\n let result = code;\n for (const { start, end, replacement } of sorted) {\n result = result.slice(0, start) + replacement + result.slice(end);\n }\n return result;\n}\n\n/**\n * Vite plugin that bundles the `@effing/satori` worker into the SSR output and\n * rewrites `createSatoriPool()` calls to point at it.\n *\n * **This plugin is required for production SSR builds.** Without it the worker\n * path resolved via `import.meta.url` breaks after Vite bundles the pool code,\n * because the URL points at the build output directory instead of `node_modules`.\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { satoriPoolPlugin } from \"@effing/satori/vite\";\n *\n * export default defineConfig({\n * plugins: [satoriPoolPlugin()],\n * });\n * ```\n */\nexport function satoriPoolPlugin(): Plugin {\n let resolvedConfig: ResolvedConfig;\n\n return {\n name: \"@effing/satori:worker\",\n apply: \"build\",\n\n config() {\n return { ssr: { external: [\"tinypool\"] } };\n },\n\n configResolved(config) {\n resolvedConfig = config;\n },\n\n transform(code, id, options) {\n if (!options?.ssr || !code.includes(\"createSatoriPool\")) return;\n\n let ast: EstreeNode;\n try {\n ast = this.parse(code) as unknown as EstreeNode;\n } catch {\n if (!id.startsWith(\"\\0\")) {\n this.warn(`satoriPoolPlugin: skipped AST transform for ${id}`);\n }\n return;\n }\n\n const localNames = findImportedLocalNames(ast);\n if (localNames.size === 0) return;\n\n const edits = collectEdits(ast, localNames, (msg) =>\n this.warn(`${msg} (${id})`),\n );\n if (edits.length === 0) return;\n return { code: applyEdits(code, edits), map: null };\n },\n\n renderChunk(code, chunk) {\n const placeholder = '\"__SATORI_WORKER_FILE__\"';\n if (!code.includes(placeholder)) return;\n\n const chunkDir = posix.dirname(chunk.fileName);\n const relToRoot = chunkDir === \".\" ? \".\" : posix.relative(chunkDir, \".\");\n const workerRel = posix.join(relToRoot, \"satori-worker.js\");\n const expr = `import.meta.dirname + ${JSON.stringify(\"/\" + workerRel)}`;\n\n return {\n code: code.replaceAll(placeholder, expr),\n map: null,\n };\n },\n\n async writeBundle(outputOptions) {\n if (!resolvedConfig.build.ssr) return;\n\n const workerEntry = fileURLToPath(\n new URL(\"../worker/index.js\", import.meta.url),\n );\n const outDir = outputOptions.dir ?? resolvedConfig.build.outDir;\n\n const { build } = await import(\"vite\");\n await build({\n configFile: false,\n logLevel: \"silent\",\n build: {\n write: true,\n emptyOutDir: false,\n outDir,\n lib: {\n entry: workerEntry,\n formats: [\"es\"],\n fileName: () => \"satori-worker.js\",\n },\n rollupOptions: {\n external: [\"@resvg/resvg-js\"],\n },\n },\n });\n },\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effing/satori",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Render JSX to PNG using Satori with emoji support",
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,7 +25,8 @@
25
25
  "dist"
26
26
  ],
27
27
  "dependencies": {
28
- "satori": ">=0.10.0 <1.0.0"
28
+ "satori": ">=0.10.0 <1.0.0",
29
+ "tinypool": "^2.0.0"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@types/react": "^19.0.0",
@@ -37,16 +38,12 @@
37
38
  "peerDependencies": {
38
39
  "@resvg/resvg-js": "^2.6.2",
39
40
  "react": "^18.0.0 || ^19.0.0",
40
- "tinypool": "^1.0.0",
41
41
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "react": {
45
45
  "optional": true
46
46
  },
47
- "tinypool": {
48
- "optional": true
49
- },
50
47
  "vite": {
51
48
  "optional": true
52
49
  }