unplugin-react-fine-grained-signals 0.1.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.
@@ -0,0 +1,1341 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { createUnplugin } from "unplugin";
3
+ import { declare } from "@babel/helper-plugin-utils";
4
+ import { transformSync } from "@babel/core";
5
+ import * as t from "@babel/types";
6
+ //#region src/internal/transform.ts
7
+ const useSignalsComment = /(^|\s)@useSignals(\s|$)/;
8
+ const noUseSignalsComment = /(^|\s)@noUseSignals(\s|$)/;
9
+ const transformedMetadataKey = "reactFineGrainedSignalsTransformed";
10
+ const noMemoDirective = "use no memo";
11
+ const memoizationDirectives = /* @__PURE__ */ new Set([
12
+ "use memo",
13
+ "use forget",
14
+ "use no memo",
15
+ "use no forget"
16
+ ]);
17
+ /** Adds the opt-out unless the author already stated a memoization choice. */
18
+ function addNoMemoDirective(body) {
19
+ if (body.directives.some(({ value }) => memoizationDirectives.has(value.value))) return false;
20
+ body.directives.unshift(t.directive(t.directiveLiteral(noMemoDirective)));
21
+ return true;
22
+ }
23
+ function hasLeadingComment(path, pattern) {
24
+ return path.node.leadingComments?.some((comment) => pattern.test(comment.value)) ?? false;
25
+ }
26
+ function hasOwnedLeadingComment(path, pattern) {
27
+ let current = path;
28
+ while (current !== null && !current.isProgram()) {
29
+ if (hasLeadingComment(current, pattern)) return true;
30
+ if (current.isStatement()) {
31
+ const parent = current.parentPath;
32
+ return parent !== null && (parent.isExportNamedDeclaration() || parent.isExportDefaultDeclaration()) && hasLeadingComment(parent, pattern);
33
+ }
34
+ current = current.parentPath;
35
+ }
36
+ return false;
37
+ }
38
+ const reactPackageSource = "react";
39
+ function isReactSource(source, reactImportSource) {
40
+ return source === reactPackageSource || source === reactImportSource;
41
+ }
42
+ /**
43
+ * Resolves `name` back to the import specifier and source module it is bound
44
+ * to, or `undefined` if `name` isn't bound by a value-level import at all
45
+ * (not imported, a type-only specifier, or a type-only declaration).
46
+ *
47
+ * Every import-recognition predicate in this file -- React's `memo`/
48
+ * `forwardRef` wrappers, a default-or-namespace React import, a named or
49
+ * namespace `useSignals` import -- reduces to "does this resolved binding
50
+ * have the right specifier kind and source", so they all build on this one
51
+ * binding walk instead of each repeating
52
+ * `getBinding` -> specifier-kind -> `importKind` -> parent-declaration.
53
+ */
54
+ function resolveImportedBinding(path, name) {
55
+ const binding = path.scope.getBinding(name);
56
+ if (binding === void 0) return void 0;
57
+ const specifier = binding.path;
58
+ if (!specifier.isImportSpecifier() && !specifier.isImportDefaultSpecifier() && !specifier.isImportNamespaceSpecifier()) return;
59
+ if (specifier.isImportSpecifier() && specifier.node.importKind === "type") return void 0;
60
+ const declaration = specifier.parentPath;
61
+ if (!declaration.isImportDeclaration() || declaration.node.importKind === "type") return;
62
+ return {
63
+ specifier,
64
+ source: declaration.node.source.value
65
+ };
66
+ }
67
+ /** Is `name` bound by `import { memo } from "react"` (or `forwardRef`, possibly aliased)? */
68
+ function isReactNamedImport(path, name, importedName, reactImportSource) {
69
+ const resolved = resolveImportedBinding(path, name);
70
+ if (resolved === void 0 || !resolved.specifier.isImportSpecifier()) return false;
71
+ return t.isIdentifier(resolved.specifier.node.imported, { name: importedName }) && isReactSource(resolved.source, reactImportSource);
72
+ }
73
+ /** Is `name` bound by `import * as React from "react"` or `import React from "react"`? */
74
+ function isReactDefaultOrNamespaceImport(path, name, reactImportSource) {
75
+ const resolved = resolveImportedBinding(path, name);
76
+ return resolved !== void 0 && (resolved.specifier.isImportNamespaceSpecifier() || resolved.specifier.isImportDefaultSpecifier()) && isReactSource(resolved.source, reactImportSource);
77
+ }
78
+ function isKnownComponentWrapper(path, reactImportSource) {
79
+ const callee = path.get("callee");
80
+ if (callee.isIdentifier()) {
81
+ const name = callee.node.name;
82
+ return isReactNamedImport(path, name, "memo", reactImportSource) || isReactNamedImport(path, name, "forwardRef", reactImportSource);
83
+ }
84
+ if (!callee.isMemberExpression()) return false;
85
+ const propertyName = getReadPropertyName(callee.node);
86
+ if (propertyName !== "memo" && propertyName !== "forwardRef") return false;
87
+ const object = callee.get("object");
88
+ return object.isIdentifier() && isReactDefaultOrNamespaceImport(path, object.node.name, reactImportSource);
89
+ }
90
+ /**
91
+ * The static name of an object property's or class field's key. A computed key
92
+ * is only readable when it is a string literal (`{ ["Home"]: ... }`), exactly as
93
+ * `getReadPropertyName` reads `items["map"]`; anything else is decided at
94
+ * runtime and stays unnamed, which leaves the function untransformed.
95
+ */
96
+ function getPropertyKeyName(node) {
97
+ const key = unwrapTransparent(node.key);
98
+ if (node.computed) return t.isStringLiteral(key) ? key.value : void 0;
99
+ if (t.isIdentifier(key)) return key.name;
100
+ return t.isStringLiteral(key) ? key.value : void 0;
101
+ }
102
+ /**
103
+ * The component/hook name a function held in a *keyed slot* takes from its key:
104
+ *
105
+ * ```js
106
+ * Card.Header = () => <h1>{count.value}</h1>; // a compound component
107
+ * export const ns = { Home: () => <p>{count.value}</p> }; // an object namespace
108
+ * class Holder { Row = () => <li>{count.value}</li>; } // a class field
109
+ * ```
110
+ *
111
+ * All three are ordinary ways to hold a component that no *binding* names, so
112
+ * without this they resolved to no identity at all and `auto` mode passed over
113
+ * them in silence. The key is the name the rest of the module reaches them by
114
+ * (`<ns.Home />`, `<Card.Header />`), which is precisely what the PascalCase /
115
+ * `useX` conventions are asked to classify -- so a lowercase key (`{ render:
116
+ * ... }`, `obj.onClick = ...`) resolves to a lowercase identity and is filtered
117
+ * out by those same conventions, exactly as a lowercase binding is.
118
+ *
119
+ * Reading the key *before* the function's own `id` is the same precedence the
120
+ * enclosing binding already takes: `Card.onSelect = function Row() {}` is a
121
+ * handler named for debugging, not a component, and the slot it was put in says
122
+ * so more reliably than the label it carries.
123
+ *
124
+ * An identifier-target assignment (`Row = (props) => ...` after a separate
125
+ * `let Row`) is deliberately not named here: the declaration is the binding, and
126
+ * routing it through this would give a re-assignment the same authority as a
127
+ * declaration. It keeps resolving through `getBindingIdentityName`'s own
128
+ * branches exactly as before.
129
+ *
130
+ * An assignment target is required to root in a plain identifier
131
+ * (`getStaticMemberPath`), which is what excludes `this.Row = ...` in a class
132
+ * component's constructor. That shape is genuinely ambiguous here: it is just as
133
+ * often a per-item row renderer invoked through `this.props.items.map(this.Row)`
134
+ * as it is a component, and `this` is not a binding, so the reference walk that
135
+ * disqualifies every other keyed slot (`isKeyedRenderCallback`) cannot see those
136
+ * uses at all -- there would be no way to take the boundary back once given. So
137
+ * the ambiguous case resolves the way this file always resolves one: name
138
+ * nothing, transform nothing. Class components stay a manual `useSignals()` /
139
+ * `@useSignals` opt-in.
140
+ */
141
+ function getKeyedIdentityName(parent) {
142
+ if (parent.isAssignmentExpression()) {
143
+ const left = parent.node.left;
144
+ if (!t.isMemberExpression(left)) return void 0;
145
+ return getStaticMemberPath(left)?.keys.at(-1);
146
+ }
147
+ if (parent.isObjectProperty() || parent.isClassProperty()) return getPropertyKeyName(parent.node);
148
+ }
149
+ /**
150
+ * A member expression read as a root binding plus the chain of static keys
151
+ * taken from it: `a.b.Row` gives `a` / `["b", "Row"]`. Every link has to be a
152
+ * statically readable key and the innermost object a plain identifier, so a
153
+ * computed runtime key (`a[k].Row`) and a `this`-rooted path (`this.Row`) both
154
+ * yield nothing rather than a partial answer.
155
+ *
156
+ * The chain, rather than just the last key, is what lets `isKeyedRenderCallback`
157
+ * match a use site against the definition site: a one-level walk saw
158
+ * `const a = { b: { Row } }` handed to `items.map(a.b.Row)` as an unrelated
159
+ * reference and let the per-item callback keep a hook boundary of its own.
160
+ */
161
+ function getStaticMemberPath(node) {
162
+ const keys = [];
163
+ let current = node;
164
+ while (t.isMemberExpression(current)) {
165
+ const key = getReadPropertyName(current);
166
+ if (key === void 0) return void 0;
167
+ keys.unshift(key);
168
+ current = current.object;
169
+ }
170
+ return t.isIdentifier(current) ? {
171
+ root: current.name,
172
+ keys
173
+ } : void 0;
174
+ }
175
+ function getBindingIdentityName(path, reactImportSource, climbedParent = climbComponentWrappers(path, reactImportSource).parentPath) {
176
+ const parent = climbedParent;
177
+ if (parent !== null) {
178
+ if (parent.isVariableDeclarator() && t.isIdentifier(parent.node.id)) return parent.node.id.name;
179
+ const keyed = getKeyedIdentityName(parent);
180
+ if (keyed !== void 0) return keyed;
181
+ }
182
+ if ((path.isFunctionDeclaration() || path.isFunctionExpression()) && path.node.id !== null && path.node.id !== void 0) return path.node.id.name;
183
+ }
184
+ /** Does a name read as a React component -- PascalCase -- by convention? */
185
+ function isComponentName(name) {
186
+ return /^[A-Z]/.test(name);
187
+ }
188
+ /** Does a name read as a custom hook -- `useX` -- by convention? */
189
+ function isHookName(name) {
190
+ return /^use[A-Z]/.test(name);
191
+ }
192
+ /**
193
+ * Does `path` render JSX *itself*, rather than merely containing some?
194
+ *
195
+ * Nested functions are skipped, because their JSX is their own output and not
196
+ * this function's: `(Base) => (props) => <Base />` renders nothing at all, it
197
+ * returns a function that does. That distinction is the whole difference
198
+ * between a factory and a component here, so without it a factory of factories
199
+ * would be mistaken for the component at the bottom of the chain and have a
200
+ * hook injected into a function React never renders.
201
+ *
202
+ * The cost of the stricter reading is a component whose only JSX lives inside a
203
+ * callback (`(props) => items.map((i) => <li />)`) -- it renders JSX, but not
204
+ * itself. That direction is the safe one: it declines to derive an identity,
205
+ * leaving behavior exactly as it was, rather than injecting an invalid hook.
206
+ */
207
+ function rendersJsx(path) {
208
+ let found = false;
209
+ path.traverse({
210
+ Function(nested) {
211
+ nested.skip();
212
+ },
213
+ JSXElement(jsx) {
214
+ found = true;
215
+ jsx.stop();
216
+ },
217
+ JSXFragment(jsx) {
218
+ found = true;
219
+ jsx.stop();
220
+ }
221
+ });
222
+ return found;
223
+ }
224
+ /**
225
+ * Is `path` a higher-order component factory -- a function that *produces* a
226
+ * component rather than being one, and that React itself therefore never
227
+ * renders?
228
+ *
229
+ * The evidence is the parameter list. React calls a component with exactly one
230
+ * props object, and a `forwardRef` render function with `(props, ref)`; neither
231
+ * of those parameters is ever PascalCase, and a destructured one is a pattern
232
+ * rather than a plain identifier. A PascalCase *identifier* parameter is
233
+ * therefore a component being handed in, which only a factory receives. That is
234
+ * the same naming convention this file already reads to classify `Counter` as a
235
+ * component and `useCount` as a hook, applied one position further in -- not a
236
+ * new kind of guess.
237
+ *
238
+ * The second half -- rendering no JSX of its own (`rendersJsx`, which looks past
239
+ * nested functions) -- keeps a genuine component out no matter how its
240
+ * parameters are spelled: a function that renders is a component.
241
+ *
242
+ * Two consequences follow, and together they are the whole policy for a
243
+ * PascalCase factory such as
244
+ * `export const WithCount = (Base) => (props) => <Base count={count.value} />`:
245
+ *
246
+ * - the factory is never transformed itself (`decideTransform` withholds both
247
+ * the automatic and the annotated route from it), and
248
+ * - the function it returns inherits the factory's name (`getIdentityFactory`)
249
+ * exactly as a camelCase `withCount` factory's returned component already did.
250
+ *
251
+ * The first half is the load-bearing one. Without it the factory looked like an
252
+ * ordinary component from every angle -- a PascalCase binding, and (because the
253
+ * component it returns owned no identity, so `inspectFunction` never skipped it)
254
+ * the returned component's JSX and `.value` reads credited to the factory's own
255
+ * body. `auto` mode injected the boundary into the factory, `all` mode did so
256
+ * with no `.value` read needed at all, and since `WithCount(Foo)` is normally
257
+ * called at module scope the injected `useSignals()` ran with no React
258
+ * dispatcher and threw at import time -- the unrecoverable direction this file
259
+ * steers away from everywhere else.
260
+ */
261
+ function isHigherOrderComponentFactory(path) {
262
+ return path.node.params.some((param) => {
263
+ const name = getSimpleParameterName(param);
264
+ return name !== void 0 && isComponentName(name);
265
+ }) && !rendersJsx(path);
266
+ }
267
+ /**
268
+ * The name a parameter binds, seen through a default value. `(Base)` and
269
+ * `(Base = Fallback)` bind the very same `Base`, but a default makes the node an
270
+ * `AssignmentPattern` wrapping the identifier rather than an identifier itself
271
+ * -- so matching only `Identifier` let a defaulted parameter fall through and
272
+ * put the factory straight back in front of the import-time crash
273
+ * `isHigherOrderComponentFactory` exists to prevent.
274
+ *
275
+ * A destructured or rest parameter binds no single name and yields nothing,
276
+ * which is what keeps a component's `({ Icon })` prop out.
277
+ */
278
+ function getSimpleParameterName(param) {
279
+ if (t.isIdentifier(param)) return param.name;
280
+ if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) return param.left.name;
281
+ }
282
+ /**
283
+ * The higher-order-component factory whose identity `path` inherits, when
284
+ * `path` is the component that factory returns:
285
+ *
286
+ * ```js
287
+ * export const withCount = (Base) => (props) => <Base count={count.value} />;
288
+ * export function withCount(Base) { return (props) => <Base ... />; }
289
+ * ```
290
+ *
291
+ * The inner function is the real component in both, but it has no name of its
292
+ * own: its only identity comes from being what `withCount` returns. Left
293
+ * unresolved it is not a component to `auto` mode and not an annotatable
294
+ * function to `@useSignals`, so a signal write silently stops re-rendering it
295
+ * -- no error, just a stale UI.
296
+ *
297
+ * Three things have to hold, tested cheapest-first:
298
+ *
299
+ * - `path` sits in a return position of the enclosing function: an explicit
300
+ * `return`, or an arrow's concise body, which returns just as literally.
301
+ * Missing the second is what made the concise form fail even when annotated.
302
+ * - The enclosing function has a resolvable name that is not a hook name, and
303
+ * -- if it is a *component* name -- is a function `isHigherOrderComponentFactory`
304
+ * independently identifies as a factory. A function returned by a component or
305
+ * a hook is otherwise a render prop or a callback that runs inside that owner's
306
+ * render rather than an independently mounted component, and a hook boundary of
307
+ * its own would be an invalid hook call. Factories are camelCase by convention
308
+ * (`withCount`, `connect`, `observer`), so the plain name test reads as
309
+ * "returned from a plain factory"; the PascalCase escape hatch exists because
310
+ * `WithCount` is an equally common spelling of the very same thing, and there
311
+ * the convention alone was not merely unhelpful but actively harmful (see
312
+ * `isHigherOrderComponentFactory`).
313
+ *
314
+ * A `useX` name gets no such escape hatch, deliberately. React calls a hook
315
+ * from inside a render that is already in progress, so anything the hook hands
316
+ * back is a closure the caller may invoke during that same render -- no
317
+ * structural evidence in this file can rule that out, and the safe reading of
318
+ * an ambiguous case is to leave the returned function unresolved rather than
319
+ * give it a boundary that would be an invalid hook call.
320
+ * - `path` renders JSX itself (`rendersJsx`). A factory handing back a
321
+ * comparator, a reducer or any other plain closure returns no component and
322
+ * must not be swept up -- and neither must the middle link of a factory
323
+ * chain, whose JSX is all inside the function *it* returns.
324
+ *
325
+ * Only the immediately enclosing function is consulted, so the inherited name
326
+ * is always a real module binding. A factory that returns a factory
327
+ * (`(a) => (b) => (props) => <p />`) deliberately leaves the innermost
328
+ * function unresolved rather than inventing a name for a middle function that
329
+ * has none -- an `@useSignals` annotation there still warns
330
+ * (`warnUnnamedUseSignalsAnnotation`) instead of silently doing nothing.
331
+ */
332
+ function getIdentityFactory(path, reactImportSource, climbedParent) {
333
+ if (climbedParent === null) return void 0;
334
+ let enclosing;
335
+ if (climbedParent.isArrowFunctionExpression()) enclosing = climbedParent;
336
+ else if (climbedParent.isReturnStatement()) enclosing = climbedParent.getFunctionParent();
337
+ else return;
338
+ if (enclosing === null) return void 0;
339
+ const name = getBindingIdentityName(enclosing, reactImportSource);
340
+ if (name === void 0 || isHookName(name)) return void 0;
341
+ if (!rendersJsx(path)) return void 0;
342
+ if (isComponentName(name) && !isHigherOrderComponentFactory(enclosing)) return void 0;
343
+ return {
344
+ path: enclosing,
345
+ name
346
+ };
347
+ }
348
+ function resolveComponentIdentity(path, reactImportSource, climbedParent = climbComponentWrappers(path, reactImportSource).parentPath) {
349
+ const own = getBindingIdentityName(path, reactImportSource, climbedParent);
350
+ if (own !== void 0) return {
351
+ kind: "binding",
352
+ name: own
353
+ };
354
+ const factory = getIdentityFactory(path, reactImportSource, climbedParent);
355
+ if (factory === void 0) return void 0;
356
+ return {
357
+ kind: "hoc-return",
358
+ name: `${factory.name}()`,
359
+ factory: factory.path
360
+ };
361
+ }
362
+ function isComponentIdentity(identity) {
363
+ if (identity === void 0) return false;
364
+ return identity.kind === "hoc-return" || isComponentName(identity.name);
365
+ }
366
+ function isHookIdentity(identity) {
367
+ if (identity === void 0) return false;
368
+ return identity.kind === "binding" && isHookName(identity.name);
369
+ }
370
+ /**
371
+ * A PascalCase identity derived from the module's own file name.
372
+ *
373
+ * `export default (props) => <p>{count.value}</p>` is a perfectly ordinary way
374
+ * to write a component and the only component position in a module that carries
375
+ * no name at all: there is no binding, no key, and no function `id` for
376
+ * `getBindingIdentityName` to read, so the export resolved to no identity and
377
+ * `auto` and `all` alike passed straight over it in silence -- while the named
378
+ * `export default function App()` one line away was transformed.
379
+ *
380
+ * The file name is the name such a module actually has: `Counter.tsx` is
381
+ * imported as `Counter`, which is exactly the identity the rest of the codebase
382
+ * refers to it by. Only its *shape* is ever used -- the derived string
383
+ * classifies the export through `isComponentName`/`isHookName` and is never
384
+ * emitted into the output -- so sanitizing punctuation away (`my-widget.tsx` ->
385
+ * `MyWidget`) costs nothing and cannot collide with anything.
386
+ *
387
+ * A name that does not come out PascalCase (`123.tsx`, a module with no usable
388
+ * stem) yields nothing rather than something invented, and the caller reports
389
+ * that rather than silently skipping the export.
390
+ */
391
+ function deriveModuleIdentityName(filename) {
392
+ if (filename === void 0) return void 0;
393
+ const base = filename.replace(/[?#].*$/, "").replace(/\\/g, "/");
394
+ const name = base.slice(base.lastIndexOf("/") + 1).replace(/\.[^.]*$/, "").split(/[^A-Za-z0-9]+/).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
395
+ return isComponentName(name) ? name : void 0;
396
+ }
397
+ /**
398
+ * The derived identity for an anonymous `export default`, and nothing else.
399
+ * Reached only after `resolveComponentIdentity` has found no real name, so a
400
+ * named default export (`export default function App()`, `export default memo(
401
+ * function App() {})`) keeps the name it wrote for itself.
402
+ */
403
+ function getDefaultExportIdentity(climbedParent, filename) {
404
+ if (climbedParent === null || !climbedParent.isExportDefaultDeclaration()) return void 0;
405
+ const name = deriveModuleIdentityName(filename);
406
+ return name === void 0 ? void 0 : {
407
+ kind: "binding",
408
+ name
409
+ };
410
+ }
411
+ function isVerifiedUseSignalsSource(source, importSource) {
412
+ return source === importSource || source === `${importSource}/runtime`;
413
+ }
414
+ function isNamedUseSignalsImport(functionPath, name, importSource) {
415
+ const resolved = resolveImportedBinding(functionPath, name);
416
+ if (resolved === void 0 || !resolved.specifier.isImportSpecifier()) return false;
417
+ return t.isIdentifier(resolved.specifier.node.imported, { name: "useSignals" }) && (importSource === void 0 || isVerifiedUseSignalsSource(resolved.source, importSource));
418
+ }
419
+ function isNamespaceUseSignalsImport(functionPath, name, importSource) {
420
+ const resolved = resolveImportedBinding(functionPath, name);
421
+ return resolved !== void 0 && resolved.specifier.isImportNamespaceSpecifier() && (importSource === void 0 || isVerifiedUseSignalsSource(resolved.source, importSource));
422
+ }
423
+ function isUseSignalsCallee(functionPath, callee, importSource, allowBarrel = true) {
424
+ if (callee.isIdentifier()) return isNamedUseSignalsImport(functionPath, callee.node.name, allowBarrel ? void 0 : importSource);
425
+ if (!callee.isMemberExpression()) return false;
426
+ const object = callee.get("object");
427
+ if (!object.isIdentifier()) return false;
428
+ return getReadPropertyName(callee.node) === "useSignals" && isNamespaceUseSignalsImport(functionPath, object.node.name, allowBarrel ? void 0 : importSource);
429
+ }
430
+ const renderCallbackMethods = /* @__PURE__ */ new Set([
431
+ "map",
432
+ "flatMap",
433
+ "forEach"
434
+ ]);
435
+ function isTransparentWrapper(node) {
436
+ return t.isTSNonNullExpression(node) || t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node) || t.isTSTypeAssertion(node) || t.isTSInstantiationExpression(node);
437
+ }
438
+ /** The value-level node behind any chain of transparent TypeScript wrappers. */
439
+ function unwrapTransparent(node) {
440
+ let current = node;
441
+ while (isTransparentWrapper(current)) current = current.expression;
442
+ return current;
443
+ }
444
+ /**
445
+ * The path of the syntactic slot `path` really occupies. A reference wrapped in
446
+ * `Row!` or `Row as Fn` sits one or more nodes below the argument list it is
447
+ * actually part of, so climbing past those wrappers is what makes the enclosing
448
+ * call -- and the node that genuinely sits in its argument list -- visible.
449
+ */
450
+ function climbTransparentWrappers(path) {
451
+ let current = path;
452
+ for (let parent = current.parentPath; parent !== null && isTransparentWrapper(parent.node) && parent.node.expression === current.node; parent = current.parentPath) current = parent;
453
+ return current;
454
+ }
455
+ /**
456
+ * The mirror image of `climbTransparentWrappers`: the path of the value a
457
+ * chain of transparent wrappers is wrapped *around*. A declarator's initializer
458
+ * is such a chain in `const Row = ((item) => <li />) as Fn`, so a check that
459
+ * asks whether a binding was initialized with a function has to descend past
460
+ * the wrappers to find it.
461
+ */
462
+ function unwrapTransparentPath(path) {
463
+ let current = path;
464
+ while (isTransparentWrapper(current.node)) current = current.get("expression");
465
+ return current;
466
+ }
467
+ /**
468
+ * The syntactic slot a function really occupies, past every wrapper that leaves
469
+ * both its value and its identity alone: transparent TypeScript wrappers and
470
+ * React's own `memo()` / `forwardRef()` calls. The two interleave freely --
471
+ * `memo(((props) => <p />) as Fn)` puts the wrapper inside the call and
472
+ * `(memo((props) => <p />)) as Fn` puts it outside -- so one loop alternates
473
+ * between them rather than two separate passes, which would each stop at the
474
+ * first wrapper of the other kind.
475
+ *
476
+ * Only argument 0 -- the component itself -- may climb. `memo`'s second
477
+ * argument is an `areEqual` comparator that React calls during reconciliation,
478
+ * outside any component's render: letting it inherit the wrapper's binding name
479
+ * would classify `memo(Row, (a, b) => a.id === b.id)`'s comparator as the
480
+ * component `MemoRow`, inject a full hook boundary into it, and throw
481
+ * "Invalid hook call" on the first re-render. `forwardRef` takes only one
482
+ * argument, so the same rule costs it nothing.
483
+ */
484
+ function climbComponentWrappers(path, reactImportSource) {
485
+ let current = climbTransparentWrappers(path);
486
+ for (let parent = current.parentPath; parent !== null && parent.isCallExpression() && parent.node.arguments[0] === current.node && isKnownComponentWrapper(parent, reactImportSource); parent = current.parentPath) current = climbTransparentWrappers(parent);
487
+ return current;
488
+ }
489
+ /** The callee and arguments of a plain or optional-chained call. */
490
+ function getCallParts(node) {
491
+ if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) return {
492
+ callee: node.callee,
493
+ arguments: node.arguments
494
+ };
495
+ }
496
+ /**
497
+ * Is `callee` a member access naming one of the known iteration methods
498
+ * (`items.map`, `items?.flatMap`, `items["forEach"]`)? The object's runtime
499
+ * type cannot be known statically, so this stays a heuristic on the method
500
+ * name -- but a targeted one. `memo`/`forwardRef` can never match it: a bare
501
+ * `memo(Row)` has an identifier callee, and `React.memo(Row)` names a property
502
+ * that is not in the set, so React's wrappers keep their wrapped component
503
+ * eligible for the transform without needing a special case here.
504
+ */
505
+ function isRenderCallbackCallee(callee) {
506
+ const node = unwrapTransparent(callee);
507
+ if (!t.isMemberExpression(node) && !t.isOptionalMemberExpression(node)) return false;
508
+ const name = getReadPropertyName(node);
509
+ return name !== void 0 && renderCallbackMethods.has(name);
510
+ }
511
+ /**
512
+ * The single notion of "this reference means the function runs synchronously,
513
+ * repeatedly, as part of the caller's render": `callback` is *the* callback
514
+ * argument of a call to a known array iteration method. Both the render-callback
515
+ * exclusion and the read propagation in `inspectFunction` ask this same
516
+ * question, so they cannot drift apart.
517
+ *
518
+ * `map`, `flatMap` and `forEach` all have the signature `(callbackFn, thisArg?)`,
519
+ * so only argument 0 is ever invoked. Accepting any position would read the
520
+ * `thisArg` of `items.map(String, Row)` as a render callback and strip `Row` --
521
+ * a component this call never invokes at all -- of its own subscription, leaving
522
+ * it stale on every later signal write.
523
+ *
524
+ * `callback` is matched against the node that literally sits in the argument
525
+ * list, wrappers included, so a caller holding `Row!` compares equal while a
526
+ * caller holding the inner `Row` does not; use `climbTransparentWrappers` to
527
+ * reach the former from the latter.
528
+ */
529
+ function isRenderCallbackInvocation(call, callback) {
530
+ const parts = getCallParts(call);
531
+ if (parts === void 0 || !isRenderCallbackCallee(parts.callee)) return false;
532
+ return parts.arguments[0] === callback;
533
+ }
534
+ /**
535
+ * The name other statements can reach this function's own binding by. The
536
+ * declarator is reached from the climbed position rather than the immediate
537
+ * parent, because a wrapper on the initializer -- `const Row = ((item) =>
538
+ * <li />) as Fn` -- stands between the function and the binding it names.
539
+ *
540
+ * This is `getBindingIdentityName`'s twin, deliberately not shared with it:
541
+ * that function asks what a function *is* (its public component/hook
542
+ * identity, where the enclosing binding always outranks the function's own
543
+ * name and a `memo()`/`forwardRef()` wrapper is climbed through to reach it);
544
+ * this one asks what other code in the module can *call it by* (a function
545
+ * declaration's own name is authoritative the instant it exists, and only a
546
+ * transparent TypeScript wrapper -- never a component wrapper -- sits between
547
+ * a reference and the binding, since `items.map(memo(Row))` does not hand
548
+ * `map` the plain `Row` reference). Each direction was its own past
549
+ * regression, so keep them separate rather than reconciling the priority or
550
+ * the climb depth.
551
+ */
552
+ function getOwnBindingName(path) {
553
+ if (path.isFunctionDeclaration() && path.node.id !== null && path.node.id !== void 0) return path.node.id.name;
554
+ const parent = climbTransparentWrappers(path).parentPath;
555
+ if (parent !== null && parent.isVariableDeclarator() && t.isIdentifier(parent.node.id)) return parent.node.id.name;
556
+ }
557
+ /**
558
+ * The full path a function in a keyed slot is reached by, from a root binding
559
+ * down: `const ns = { Row: ... }` gives `ns` / `["Row"]`, `Card.Row = ...` gives
560
+ * `Card` / `["Row"]`, and `const a = { b: { Row: ... } }` gives `a` /
561
+ * `["b", "Row"]`.
562
+ *
563
+ * Nesting has to be followed all the way out rather than one level, because the
564
+ * path is matched against use sites: stopping early left `items.map(a.b.Row)`
565
+ * unrecognized, so the per-item callback kept a boundary that React would run
566
+ * once per item inside a single render.
567
+ *
568
+ * A class field is deliberately absent -- it is reached through an instance
569
+ * (`new Holder().Row`), which no binding walk can follow.
570
+ */
571
+ function getKeyedAccessPath(parent) {
572
+ if (parent.isAssignmentExpression()) {
573
+ const left = parent.node.left;
574
+ if (!t.isMemberExpression(left)) return void 0;
575
+ const path = getStaticMemberPath(left);
576
+ return path === void 0 ? void 0 : {
577
+ origin: parent,
578
+ ...path
579
+ };
580
+ }
581
+ if (!parent.isObjectProperty()) return void 0;
582
+ const key = getPropertyKeyName(parent.node);
583
+ if (key === void 0) return void 0;
584
+ const keys = [key];
585
+ for (let container = parent.parentPath; container.isObjectExpression();) {
586
+ const holder = container.parentPath;
587
+ if (holder === null) return void 0;
588
+ if (holder.isVariableDeclarator() && t.isIdentifier(holder.node.id)) return {
589
+ origin: parent,
590
+ root: holder.node.id.name,
591
+ keys
592
+ };
593
+ if (holder.isAssignmentExpression() && t.isMemberExpression(holder.node.left)) {
594
+ const path = getStaticMemberPath(holder.node.left);
595
+ return path === void 0 ? void 0 : {
596
+ origin: parent,
597
+ root: path.root,
598
+ keys: [...path.keys, ...keys]
599
+ };
600
+ }
601
+ if (!holder.isObjectProperty() || holder.node.value !== container.node) return void 0;
602
+ const outerKey = getPropertyKeyName(holder.node);
603
+ if (outerKey === void 0) return void 0;
604
+ keys.unshift(outerKey);
605
+ container = holder.parentPath;
606
+ }
607
+ }
608
+ /**
609
+ * The keyed-slot twin of the by-reference check below: is a function held at
610
+ * `ns.Row` / `Card.Row` handed to an iteration method anywhere in the module
611
+ * (`items.map(ns.Row)`)?
612
+ *
613
+ * A keyed component has no binding of its own, so `getOwnBindingName` can say
614
+ * nothing about it and the ordinary reference walk never runs. Naming such a
615
+ * slot (`getKeyedIdentityName`) is what made it eligible for a boundary in the
616
+ * first place, so this is the exclusion that has to come with it: without it,
617
+ * `const handlers = { Row: (item) => <li>{item.value}</li> }` passed to `map`
618
+ * would be the one keyed shape that gets a hook injected into a per-item
619
+ * callback.
620
+ *
621
+ * The walk goes through the *root* binding rather than the function's, then
622
+ * follows the same chain of keys back down from each reference, so only a use
623
+ * that reads this exact slot counts. A JSX use (`<ns.Row />`) is a
624
+ * `JSXMemberExpression`, not a member expression, so it never matches and a
625
+ * genuine compound component keeps its boundary.
626
+ */
627
+ function isKeyedRenderCallback(parent) {
628
+ const access = getKeyedAccessPath(parent);
629
+ if (access === void 0) return false;
630
+ const binding = access.origin.scope.getBinding(access.root);
631
+ if (binding === void 0) return false;
632
+ return binding.referencePaths.some((rootPath) => {
633
+ let current = rootPath;
634
+ for (const key of access.keys) {
635
+ const member = current.parentPath;
636
+ if (member === null || !member.isMemberExpression() || member.node.object !== current.node || getReadPropertyName(member.node) !== key) return false;
637
+ current = member;
638
+ }
639
+ const slot = climbTransparentWrappers(current);
640
+ return slot.parentPath !== null && isRenderCallbackInvocation(slot.parentPath.node, slot.node);
641
+ });
642
+ }
643
+ function isRenderCallback(path, visited = /* @__PURE__ */ new Set()) {
644
+ if (visited.has(path.node)) return false;
645
+ visited.add(path.node);
646
+ const argument = climbTransparentWrappers(path);
647
+ const enclosing = argument.parentPath;
648
+ if (enclosing !== null && isRenderCallbackInvocation(enclosing.node, argument.node)) return true;
649
+ if (enclosing !== null && isKeyedRenderCallback(enclosing)) return true;
650
+ const name = getOwnBindingName(path);
651
+ if (name === void 0 || enclosing === null) return false;
652
+ const binding = enclosing.scope.getBinding(name);
653
+ if (binding === void 0) return false;
654
+ if (binding.path.node !== path.node && binding.path.node !== enclosing.node) return false;
655
+ return binding.referencePaths.some((reference) => {
656
+ const slot = climbTransparentWrappers(reference);
657
+ if (slot.parentPath !== null && isRenderCallbackInvocation(slot.parentPath.node, slot.node)) return true;
658
+ return isCalledFromRenderCallback(slot, visited);
659
+ });
660
+ }
661
+ /**
662
+ * Is `slot` the callee of a call that happens inside a render callback --
663
+ * `items.map((item) => Row(item))` rather than `items.map(Row)`?
664
+ *
665
+ * Both forms run `Row` once per item inside the owner's single render pass, so
666
+ * both must keep `Row` off a boundary of its own: `Row` called as a plain
667
+ * function is never mounted as a fiber, so three hooks injected into it would
668
+ * run in a loop and crash with "Rendered more hooks than during the previous
669
+ * render" the moment the array length changes. The by-reference form is caught
670
+ * by `isRenderCallbackInvocation`; this is the direct-call twin of it.
671
+ */
672
+ function isCalledFromRenderCallback(slot, visited) {
673
+ const call = slot.parentPath;
674
+ if (call === null) return false;
675
+ const parts = getCallParts(call.node);
676
+ if (parts === void 0 || parts.callee !== slot.node) return false;
677
+ const owner = call.getFunctionParent();
678
+ return owner !== null && isRenderCallback(owner, visited);
679
+ }
680
+ /** The function `name`, referenced from `origin`'s scope, is bound to. */
681
+ function resolveReferencedFunction(origin, name) {
682
+ const binding = origin.scope.getBinding(name);
683
+ if (binding === void 0) return void 0;
684
+ const bindingPath = binding.path;
685
+ if (bindingPath.isFunctionDeclaration()) return bindingPath;
686
+ if (bindingPath.isVariableDeclarator()) {
687
+ const init = bindingPath.get("init");
688
+ if (init.isExpression()) {
689
+ const value = unwrapTransparentPath(init);
690
+ if (value.isArrowFunctionExpression() || value.isFunctionExpression()) return value;
691
+ }
692
+ }
693
+ }
694
+ /**
695
+ * Is `path` a component defined inline as a call argument --
696
+ * `observer(function App() { return <p>{count.value}</p>; })`?
697
+ *
698
+ * The by-reference form is already eligible and documented as such: `const App
699
+ * = ...; observer(App)` is named by its binding, and a reference passed to any
700
+ * call that is not a known iteration method is "ordinary component
701
+ * registration", where React instantiates the result as its own fiber with its
702
+ * own hooks. The inline form is the same registration written in one
703
+ * expression, and left out it was the one shape a third-party HOC could take
704
+ * that went untransformed *and* unreported.
705
+ *
706
+ * Admitting it needs a discriminator with no ambiguity left in it, so all four
707
+ * of these hold:
708
+ *
709
+ * - a function *expression carrying its own PascalCase name*. An arrow or an
710
+ * anonymous function expression has no identity for a boundary to attach to
711
+ * anyway, and the written-out name is the author's own statement that this is
712
+ * a component -- the same statement `const App = ...` makes.
713
+ * - it renders JSX itself. A named callback that renders nothing is a
714
+ * comparator, a reducer, or a test body.
715
+ * - it sits in an *argument* slot, never the callee, so the IIFE
716
+ * `(function App() { ... })()` -- which runs once at module scope, outside any
717
+ * render, exactly where a hook call throws -- is excluded.
718
+ * - it is not the callback of a deferred hook (`useMemo(function Rows() {...},
719
+ * [])`), which runs inside the caller's render and must carry no hook of its
720
+ * own. The render-callback form (`items.map(function Row() {...})`) is
721
+ * excluded before this is ever reached, by `isRenderCallback`.
722
+ */
723
+ function isInlineNamedComponentArgument(path, call, reactImportSource) {
724
+ if (!path.isFunctionExpression()) return false;
725
+ const id = path.node.id;
726
+ if (id === null || id === void 0 || !isComponentName(id.name)) return false;
727
+ const slot = climbComponentWrappers(path, reactImportSource);
728
+ if (!call.node.arguments.some((argument) => argument === slot.node)) return false;
729
+ if (isDeferredCallbackArgument(path)) return false;
730
+ return rendersJsx(path);
731
+ }
732
+ function isAutomaticTransformCandidate(path, reactImportSource, climbedParent) {
733
+ if (isRenderCallback(path)) return false;
734
+ if (path.isFunctionDeclaration()) return true;
735
+ const parent = climbedParent === void 0 ? climbComponentWrappers(path, reactImportSource).parentPath : climbedParent;
736
+ return parent !== null && (parent.isVariableDeclarator() || parent.isReturnStatement() || parent.isArrowFunctionExpression() || parent.isExportDefaultDeclaration() || parent.isObjectProperty() || parent.isClassProperty() || parent.isAssignmentExpression() && t.isMemberExpression(parent.node.left) && getStaticMemberPath(parent.node.left) !== void 0 || parent.isCallExpression() && isInlineNamedComponentArgument(path, parent, reactImportSource));
737
+ }
738
+ function isNestedTrackingBoundary(path, reactImportSource) {
739
+ const climbedParent = climbComponentWrappers(path, reactImportSource).parentPath;
740
+ if (!isAutomaticTransformCandidate(path, reactImportSource, climbedParent)) return false;
741
+ const identity = resolveComponentIdentity(path, reactImportSource, climbedParent);
742
+ return isComponentIdentity(identity) || isHookIdentity(identity);
743
+ }
744
+ const deferredCallbackHooks = /* @__PURE__ */ new Set([
745
+ "useEffect",
746
+ "useLayoutEffect",
747
+ "useInsertionEffect",
748
+ "useCallback",
749
+ "useMemo"
750
+ ]);
751
+ /**
752
+ * The property name a member expression reads, for the forms this file
753
+ * tracks. A wrapper can sit around the key alone -- `items["map" as
754
+ * const](Row)` is a plain member expression whose property is the wrapped
755
+ * node -- so a computed property still needs unwrapping to reach the name.
756
+ */
757
+ function getReadPropertyName(node) {
758
+ if (!node.computed) return t.isIdentifier(node.property) ? node.property.name : void 0;
759
+ const property = unwrapTransparent(node.property);
760
+ return t.isStringLiteral(property) ? property.value : void 0;
761
+ }
762
+ const nonSignalValueReceivers = /* @__PURE__ */ new Set([
763
+ "target",
764
+ "currentTarget",
765
+ "current"
766
+ ]);
767
+ function isNonSignalValueReceiver(object) {
768
+ const receiver = unwrapTransparent(object);
769
+ if (!t.isMemberExpression(receiver) && !t.isOptionalMemberExpression(receiver)) return false;
770
+ const name = getReadPropertyName(receiver);
771
+ return name !== void 0 && nonSignalValueReceivers.has(name);
772
+ }
773
+ /**
774
+ * Is `path` the value of a JSX event-handler attribute -- `onClick={() => ...}`,
775
+ * `onChange={(e) => ...}`?
776
+ *
777
+ * The `on[A-Z]` naming convention is what separates the two kinds of function
778
+ * a JSX attribute can carry. A handler runs from the event loop, long after the
779
+ * render that created it, so `e.target.value` inside one is not a render read.
780
+ * A render prop (`renderItem={(item) => ...}`) is invoked by the receiver
781
+ * during the very same render and stays deliberately included -- it is the
782
+ * documented workaround for the render-prop limitation, and excluding it would
783
+ * leave a component that only reads through one with no subscription at all.
784
+ */
785
+ function isJsxEventHandlerValue(path) {
786
+ const container = climbTransparentWrappers(path).parentPath;
787
+ if (container === null || !container.isJSXExpressionContainer()) return false;
788
+ const attribute = container.parentPath;
789
+ if (attribute === null || !attribute.isJSXAttribute()) return false;
790
+ const name = attribute.node.name;
791
+ return t.isJSXIdentifier(name) && /^on[A-Z]/.test(name.name);
792
+ }
793
+ /** Is `path` the callback argument of `useEffect`/`useMemo`/one of their kin? */
794
+ function isDeferredCallbackArgument(path) {
795
+ const slot = climbTransparentWrappers(path);
796
+ const call = slot.parentPath;
797
+ if (call === null) return false;
798
+ const parts = getCallParts(call.node);
799
+ if (parts === void 0 || parts.arguments[0] !== slot.node) return false;
800
+ const callee = unwrapTransparent(parts.callee);
801
+ if (t.isIdentifier(callee)) return deferredCallbackHooks.has(callee.name);
802
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) return false;
803
+ const property = getReadPropertyName(callee);
804
+ return property !== void 0 && deferredCallbackHooks.has(property);
805
+ }
806
+ /**
807
+ * Does a read at `path` sit inside a nested function that `functionPath`'s
808
+ * render never runs -- an event handler, a deferred hook callback, or an async
809
+ * body? Only the functions between the read and `functionPath` are examined:
810
+ * the enclosing component is already known to be synchronous by the time any of
811
+ * this matters, and anything further out owns its own inspection.
812
+ */
813
+ function isDeferredReadContext(path, functionPath) {
814
+ for (let current = path.getFunctionParent(); current !== null && current.node !== functionPath.node; current = current.getFunctionParent()) {
815
+ if (current.node.async) return true;
816
+ if (isJsxEventHandlerValue(current)) return true;
817
+ if (isDeferredCallbackArgument(current)) return true;
818
+ }
819
+ return false;
820
+ }
821
+ function inspectFunction(functionPath, importSource, reactImportSource, visited = /* @__PURE__ */ new Set([functionPath.node])) {
822
+ const inspection = {
823
+ containsJSX: false,
824
+ readsValue: false,
825
+ hasUseSignalsCall: false
826
+ };
827
+ const foldReferencedRenderCallbacks = (call) => {
828
+ const argument = call.node.arguments[0];
829
+ if (argument === void 0) return;
830
+ const reference = unwrapTransparent(argument);
831
+ if (!t.isIdentifier(reference)) return;
832
+ if (!isRenderCallbackInvocation(call.node, argument)) return;
833
+ const target = resolveReferencedFunction(call, reference.name);
834
+ if (target === void 0 || target.isDescendant(functionPath)) return;
835
+ if (visited.has(target.node)) return;
836
+ visited.add(target.node);
837
+ const nested = inspectFunction(target, importSource, reactImportSource, visited);
838
+ if (nested.containsJSX) inspection.containsJSX = true;
839
+ if (nested.readsValue) inspection.readsValue = true;
840
+ };
841
+ const foldDirectlyCalledRenderCallbacks = (call) => {
842
+ const callee = unwrapTransparent(call.node.callee);
843
+ if (!t.isIdentifier(callee) || !isComponentName(callee.name) && !isHookName(callee.name)) return;
844
+ const owner = call.getFunctionParent();
845
+ if (owner === null || owner.node === functionPath.node || !isRenderCallback(owner)) return;
846
+ const target = resolveReferencedFunction(call, callee.name);
847
+ if (target === void 0 || target.isDescendant(functionPath)) return;
848
+ if (visited.has(target.node)) return;
849
+ visited.add(target.node);
850
+ const nested = inspectFunction(target, importSource, reactImportSource, visited);
851
+ if (nested.containsJSX) inspection.containsJSX = true;
852
+ if (nested.readsValue) inspection.readsValue = true;
853
+ };
854
+ const recordValueRead = (node, path) => {
855
+ if (getReadPropertyName(node) !== "value") return;
856
+ if (isNonSignalValueReceiver(node.object)) return;
857
+ if (isDeferredReadContext(path, functionPath)) return;
858
+ inspection.readsValue = true;
859
+ };
860
+ functionPath.traverse({
861
+ Function(path) {
862
+ if (isNestedTrackingBoundary(path, reactImportSource)) path.skip();
863
+ },
864
+ JSXElement(_path) {
865
+ inspection.containsJSX = true;
866
+ },
867
+ JSXFragment(_path) {
868
+ inspection.containsJSX = true;
869
+ },
870
+ MemberExpression(path) {
871
+ recordValueRead(path.node, path);
872
+ },
873
+ OptionalMemberExpression(path) {
874
+ recordValueRead(path.node, path);
875
+ },
876
+ CallExpression(path) {
877
+ foldReferencedRenderCallbacks(path);
878
+ foldDirectlyCalledRenderCallbacks(path);
879
+ if (path.getFunctionParent()?.node !== functionPath.node) return;
880
+ if (isUseSignalsCallee(functionPath, path.get("callee"), importSource)) inspection.hasUseSignalsCall = true;
881
+ },
882
+ OptionalCallExpression(path) {
883
+ foldReferencedRenderCallbacks(path);
884
+ foldDirectlyCalledRenderCallbacks(path);
885
+ }
886
+ });
887
+ return inspection;
888
+ }
889
+ function findRuntimeImports(programPath, runtimeSource) {
890
+ const imports = [];
891
+ for (const statement of programPath.get("body")) {
892
+ if (!statement.isImportDeclaration() || statement.node.importKind === "type" || statement.node.source.value !== runtimeSource) continue;
893
+ for (const specifier of statement.get("specifiers")) if (specifier.isImportSpecifier() && specifier.node.importKind !== "type" && t.isIdentifier(specifier.node.imported, { name: "useSignals" })) imports.push({
894
+ identifier: t.cloneNode(specifier.node.local),
895
+ bindingPath: specifier
896
+ });
897
+ }
898
+ return imports;
899
+ }
900
+ function addRuntimeImport(programPath, runtimeSource, functionPath) {
901
+ const local = functionPath.scope.generateUidIdentifier("useSignals");
902
+ const declaration = t.importDeclaration([t.importSpecifier(t.cloneNode(local), t.identifier("useSignals"))], t.stringLiteral(runtimeSource));
903
+ const imports = programPath.get("body").filter((path) => path.isImportDeclaration());
904
+ const inserted = (imports.length === 0 ? programPath.unshiftContainer("body", declaration) : imports.at(-1).insertAfter(declaration))[0];
905
+ if (inserted === void 0 || !inserted.isImportDeclaration()) throw new Error("Failed to insert the useSignals import");
906
+ programPath.scope.registerDeclaration(inserted);
907
+ const specifier = inserted.get("specifiers")[0];
908
+ if (specifier === void 0 || !specifier.isImportSpecifier()) throw new Error("Failed to register the useSignals import");
909
+ return {
910
+ identifier: local,
911
+ bindingPath: specifier
912
+ };
913
+ }
914
+ /**
915
+ * Is `statements`' first entry a bare zero-argument call whose callee resolves
916
+ * to `useSignals`, with `allowBarrel` controlling whether a barrel/re-export
917
+ * chain counts (see `isUseSignalsCallee`)? Shared by `isExplicitUseSignals`
918
+ * (`allowBarrel: false`, the verified boundary) and
919
+ * `isUnverifiableBarrelUseSignals` (`allowBarrel: true`, to detect the exact
920
+ * call the former rejects).
921
+ */
922
+ function isFirstStatementUseSignalsCall(functionPath, statements, importSource, allowBarrel) {
923
+ const first = statements[0];
924
+ if (first === void 0 || !first.isExpressionStatement()) return false;
925
+ const expression = first.get("expression");
926
+ if (!expression.isCallExpression() || expression.node.arguments.length !== 0) return false;
927
+ return isUseSignalsCallee(functionPath, expression.get("callee"), importSource, allowBarrel);
928
+ }
929
+ function isExplicitUseSignals(functionPath, statements, importSource) {
930
+ return isFirstStatementUseSignalsCall(functionPath, statements, importSource, false);
931
+ }
932
+ /**
933
+ * The named import specifier the absorbed explicit `useSignals()` call resolves
934
+ * to, when the call is a bare identifier bound by one. `applyManaged` replaces
935
+ * that call with its own store declaration, which can leave the import behind
936
+ * with nothing referencing it -- so `Program.exit` needs the specifier to check
937
+ * and drop. A namespace import is deliberately not returned: the namespace
938
+ * object may be used for other exports of the same module.
939
+ */
940
+ function getAbsorbedUseSignalsImport(functionPath, statements) {
941
+ const first = statements[0];
942
+ if (first === void 0 || !first.isExpressionStatement()) return void 0;
943
+ const expression = first.get("expression");
944
+ if (!expression.isCallExpression()) return void 0;
945
+ const callee = expression.get("callee");
946
+ if (!callee.isIdentifier()) return void 0;
947
+ const resolved = resolveImportedBinding(functionPath, callee.node.name);
948
+ if (resolved === void 0 || !resolved.specifier.isImportSpecifier()) return void 0;
949
+ return resolved.specifier;
950
+ }
951
+ function isUnverifiableBarrelUseSignals(functionPath, statements, importSource, explicit) {
952
+ if (explicit) return false;
953
+ return isFirstStatementUseSignalsCall(functionPath, statements, importSource, true);
954
+ }
955
+ function warnUnverifiableBarrelUseSignals(path, importSource) {
956
+ const warning = path.buildCodeFrameError(`This useSignals() call cannot be verified as "${importSource}"'s own export: it resolves only through a barrel/re-export module, and a single-file transform cannot follow that chain to confirm the target. The component stays on the bare, best-effort useSignals() boundary. Import useSignals directly from "${importSource}" or "${importSource}/runtime" instead to get the verified boundary.`);
957
+ console.warn(warning.message);
958
+ }
959
+ function warnUnnamedUseSignalsAnnotation(path) {
960
+ const warning = path.buildCodeFrameError("This @useSignals annotation is ignored: the transform could not resolve a component or hook identity for the annotated function, and the boundary is attached to that identity. Give it a PascalCase (component) or useX (hook) name -- a binding, a named function declaration, or an object/class property key -- to opt it in.");
961
+ console.warn(warning.message);
962
+ }
963
+ function warnUnnamedDefaultExport(path) {
964
+ const warning = path.buildCodeFrameError("This anonymous default export is left untransformed: its component identity is derived from the module's file name, and this module's name does not yield a PascalCase identifier. Name the function (`export default function App() { ... }`), or assign it to a PascalCase binding and export that binding, to opt it in.");
965
+ console.warn(warning.message);
966
+ }
967
+ function shouldAutomaticallyTransform(mode, inspection, identity) {
968
+ if (isHookIdentity(identity)) return mode !== "manual" && inspection.readsValue;
969
+ if (!isComponentIdentity(identity) || !inspection.containsJSX) return false;
970
+ return mode === "all" || mode === "auto" && inspection.readsValue;
971
+ }
972
+ /** Marks the current file as having produced at least one real change. */
973
+ function markTransformed(state) {
974
+ state.file.metadata[transformedMetadataKey] = true;
975
+ }
976
+ /**
977
+ * Resolves what, if anything, `path` needs done to it: the opt-out check,
978
+ * explicit/annotated/automatic eligibility (plus the barrel-useSignals
979
+ * warning that eligibility check surfaces along the way), the
980
+ * async/generator guard, and -- once a function is confirmed eligible for
981
+ * real codegen -- acquiring the runtime `useSignals` import it will call.
982
+ * Pure decision-making: no AST mutation happens here, so every early return is
983
+ * just a `return`, not a `return` guarding mutations already made.
984
+ */
985
+ function decideTransform(path, state, options, reactImportSource) {
986
+ const climbedParent = climbComponentWrappers(path, reactImportSource).parentPath;
987
+ const identity = resolveComponentIdentity(path, reactImportSource, climbedParent) ?? getDefaultExportIdentity(climbedParent, state.filename);
988
+ const factory = identity?.kind === "hoc-return" ? identity.factory : void 0;
989
+ const ownsLeadingComment = (pattern) => hasOwnedLeadingComment(path, pattern) || factory !== void 0 && hasOwnedLeadingComment(factory, pattern);
990
+ if (ownsLeadingComment(noUseSignalsComment)) return { kind: "skip" };
991
+ const body = path.get("body");
992
+ const statements = body.isBlockStatement() ? body.get("body") : [];
993
+ const explicit = isExplicitUseSignals(path, statements, options.importSource);
994
+ if (isUnverifiableBarrelUseSignals(path, statements, options.importSource, explicit)) warnUnverifiableBarrelUseSignals(path, options.importSource);
995
+ const inspection = inspectFunction(path, options.importSource, reactImportSource);
996
+ const annotation = ownsLeadingComment(useSignalsComment);
997
+ const isFactory = isHigherOrderComponentFactory(path) && !isHookIdentity(identity);
998
+ const annotated = annotation && !isFactory && (isComponentIdentity(identity) || isHookIdentity(identity));
999
+ const candidate = isAutomaticTransformCandidate(path, reactImportSource, climbedParent);
1000
+ if (annotation && !annotated && rendersJsx(path)) {
1001
+ const enclosing = path.getFunctionParent();
1002
+ if (enclosing === null || !hasOwnedLeadingComment(enclosing, useSignalsComment)) warnUnnamedUseSignalsAnnotation(path);
1003
+ }
1004
+ if (options.mode !== "manual" && identity === void 0 && climbedParent !== null && climbedParent.isExportDefaultDeclaration() && rendersJsx(path)) warnUnnamedDefaultExport(path);
1005
+ const automatic = !isFactory && candidate && shouldAutomaticallyTransform(options.mode, inspection, identity);
1006
+ if (!explicit && !annotated && !automatic) return { kind: "skip" };
1007
+ if (!explicit && inspection.hasUseSignalsCall) return { kind: "skip" };
1008
+ if (options.transform === "inject" && explicit) return {
1009
+ kind: "directive-only",
1010
+ body
1011
+ };
1012
+ if (path.node.async || path.node.generator) {
1013
+ if (!explicit && !annotated) return { kind: "skip" };
1014
+ throw path.buildCodeFrameError("useSignals transform only supports synchronous, non-generator functions");
1015
+ }
1016
+ const managedRuntimeSource = `${options.importSource}/runtime`;
1017
+ const importSource = options.transform === "managed" ? managedRuntimeSource : options.importSource;
1018
+ const imports = options.transform === "managed" ? state.managedRuntimeImports : state.directImports;
1019
+ let runtimeImport = imports.find(({ identifier, bindingPath }) => path.scope.getBinding(identifier.name)?.path === bindingPath);
1020
+ if (runtimeImport === void 0) {
1021
+ runtimeImport = addRuntimeImport(state.programPath, importSource, path);
1022
+ imports.push(runtimeImport);
1023
+ }
1024
+ return {
1025
+ kind: options.transform,
1026
+ body,
1027
+ statements,
1028
+ explicit,
1029
+ runtimeImport,
1030
+ absorbedImport: explicit ? getAbsorbedUseSignalsImport(path, statements) : void 0
1031
+ };
1032
+ }
1033
+ /** Codegen: inject a bare `useSignals()` call at the top of the function body. */
1034
+ function applyInject(path, { body, runtimeImport }, options, state) {
1035
+ const call = t.expressionStatement(t.callExpression(t.cloneNode(runtimeImport.identifier), []));
1036
+ if (body.isBlockStatement()) body.unshiftContainer("body", call);
1037
+ else body.replaceWith(t.blockStatement([call, t.returnStatement(body.node)]));
1038
+ const injectedBody = path.node.body;
1039
+ if (options.reactCompiler === "auto" && t.isBlockStatement(injectedBody)) addNoMemoDirective(injectedBody);
1040
+ path.scope.crawl();
1041
+ markTransformed(state);
1042
+ }
1043
+ /** Codegen: wrap the function body in the managed try/finally render-tracking scope. */
1044
+ function applyManaged(path, { body, statements, explicit, runtimeImport, absorbedImport }, options, state) {
1045
+ const store = path.scope.generateUidIdentifier("signals");
1046
+ const declaration = t.variableDeclaration("const", [t.variableDeclarator(t.cloneNode(store), t.callExpression(t.cloneNode(runtimeImport.identifier), []))]);
1047
+ const first = statements[0];
1048
+ if (explicit && first !== void 0) t.inheritsComments(declaration, first.node);
1049
+ const originalStatements = body.isBlockStatement() ? statements.slice(explicit ? 1 : 0).map((statement) => statement.node) : [t.returnStatement(body.node)];
1050
+ const transformedBody = t.blockStatement([declaration, t.tryStatement(t.blockStatement(originalStatements), null, t.blockStatement([t.expressionStatement(t.callExpression(t.memberExpression(t.cloneNode(store), t.identifier("f")), []))]))]);
1051
+ if (body.isBlockStatement()) transformedBody.directives = body.node.directives;
1052
+ if (options.reactCompiler === "auto") addNoMemoDirective(transformedBody);
1053
+ body.replaceWith(transformedBody);
1054
+ path.scope.crawl();
1055
+ if (absorbedImport !== void 0) state.absorbedImports.push(absorbedImport);
1056
+ markTransformed(state);
1057
+ }
1058
+ /**
1059
+ * Drops an `import { useSignals } from "<importSource>"` the managed transform
1060
+ * absorbed and nothing else refers to any more, along with the whole
1061
+ * declaration once its last specifier goes. A reused `/runtime` import survives
1062
+ * this untouched: the emitted store declaration still calls it, so the binding
1063
+ * is still referenced.
1064
+ */
1065
+ function removeAbsorbedImports(programPath, state) {
1066
+ if (state.absorbedImports.length === 0) return;
1067
+ programPath.scope.crawl();
1068
+ for (const specifier of state.absorbedImports) {
1069
+ if (specifier.removed) continue;
1070
+ const binding = programPath.scope.getBinding(specifier.node.local.name);
1071
+ if (binding === void 0 || binding.path.node !== specifier.node || binding.referenced) continue;
1072
+ const declaration = specifier.parentPath;
1073
+ specifier.remove();
1074
+ if (declaration.isImportDeclaration() && declaration.node.specifiers.length === 0) declaration.remove();
1075
+ }
1076
+ state.absorbedImports = [];
1077
+ }
1078
+ const babelTransform = declare((api, options) => {
1079
+ api.assertVersion(8);
1080
+ const managedRuntimeSource = `${options.importSource}/runtime`;
1081
+ const reactImportSource = options.reactImportSource;
1082
+ return {
1083
+ name: "unplugin-react-fine-grained-signals",
1084
+ visitor: {
1085
+ Program: {
1086
+ enter(path, state) {
1087
+ state.programPath = path;
1088
+ state.managedRuntimeImports = findRuntimeImports(path, managedRuntimeSource);
1089
+ state.directImports = findRuntimeImports(path, options.importSource);
1090
+ state.absorbedImports = [];
1091
+ state.file.metadata[transformedMetadataKey] = false;
1092
+ },
1093
+ exit(path, state) {
1094
+ removeAbsorbedImports(path, state);
1095
+ }
1096
+ },
1097
+ Function(path, state) {
1098
+ const decision = decideTransform(path, state, options, reactImportSource);
1099
+ switch (decision.kind) {
1100
+ case "skip": return;
1101
+ case "directive-only":
1102
+ if (options.reactCompiler === "auto" && decision.body.isBlockStatement() && addNoMemoDirective(decision.body.node)) markTransformed(state);
1103
+ return;
1104
+ case "inject":
1105
+ applyInject(path, decision, options, state);
1106
+ return;
1107
+ case "managed":
1108
+ applyManaged(path, decision, options, state);
1109
+ return;
1110
+ }
1111
+ }
1112
+ }
1113
+ };
1114
+ });
1115
+ /**
1116
+ * A raw-text screen run before the file is parsed at all. Parsing, walking,
1117
+ * printing and generating a source map is the whole cost of this transform, and
1118
+ * it is discarded outright for every file none of the three opt-in routes can
1119
+ * possibly apply to:
1120
+ *
1121
+ * - explicit and annotated opt-in both need the text `useSignals` (an aliased
1122
+ * import, a namespace call and the `@useSignals` comment all still contain
1123
+ * it);
1124
+ * - `auto` needs a `.value` read, so it needs the text `value`;
1125
+ * - `all` additionally wraps JSX components, so it needs a `<` -- but its
1126
+ * custom-hook rule still needs a `.value` read.
1127
+ *
1128
+ * Each test is a plain substring scan and each one is deliberately wider than
1129
+ * what the AST check accepts, so this can only ever bail out on files the walk
1130
+ * would have rejected anyway.
1131
+ */
1132
+ function mightTransform(code, mode) {
1133
+ if (code.includes("useSignals")) return true;
1134
+ if (mode === "manual") return false;
1135
+ if (code.includes("value")) return true;
1136
+ return mode === "all" && code.includes("<");
1137
+ }
1138
+ /** Runs the private Babel transform for the universal bundler adapter. */
1139
+ function transformReactFineGrainedSignals(code, id, options) {
1140
+ if (!mightTransform(code, options.mode)) return null;
1141
+ const cleanId = id.replace(/[?#].*$/, "");
1142
+ const isTypeScript = /\.[cm]?tsx?$/i.test(cleanId);
1143
+ const supportsJsx = /\.[cm]?(?:jsx?|tsx)$/i.test(cleanId);
1144
+ const parserPlugins = [];
1145
+ if (supportsJsx) parserPlugins.push("jsx");
1146
+ if (isTypeScript) parserPlugins.push("typescript");
1147
+ parserPlugins.push("decorators-legacy", "decoratorAutoAccessors");
1148
+ let result;
1149
+ try {
1150
+ result = transformSync(code, {
1151
+ babelrc: false,
1152
+ configFile: false,
1153
+ filename: id,
1154
+ sourceFileName: cleanId,
1155
+ parserOpts: { plugins: parserPlugins },
1156
+ plugins: [[babelTransform, options]],
1157
+ sourceMaps: true
1158
+ });
1159
+ } catch (error) {
1160
+ const detail = error instanceof Error ? error.message : String(error);
1161
+ throw new Error(`unplugin-react-fine-grained-signals could not parse ${cleanId} (Babel parser plugins: ${parserPlugins.join(", ") || "none"}): ${detail}`, { cause: error });
1162
+ }
1163
+ if (result === null || typeof result.code !== "string" || result.metadata?.[transformedMetadataKey] !== true) return null;
1164
+ return {
1165
+ code: result.code,
1166
+ map: result.map
1167
+ };
1168
+ }
1169
+ //#endregion
1170
+ //#region src/unplugin.ts
1171
+ const SCRIPT_MODULE = /\.m?[jt]sx?$/;
1172
+ const pluginName = "unplugin-react-fine-grained-signals";
1173
+ /**
1174
+ * `id` with the `?query`/`#fragment` a bundler appends taken off, which is
1175
+ * exactly what the transform strips before it picks its parser plugins.
1176
+ *
1177
+ * Deciding on the raw id instead diverges from that decision in both
1178
+ * directions. Vite's SFC sub-request `App.vue?vue&type=script&lang.tsx` ends in
1179
+ * `.tsx` only because its *query* does, so it would be claimed and then reach
1180
+ * the parser as `App.vue` -- with neither the `jsx` nor the `typescript` plugin
1181
+ * enabled -- and fail as though the author had written broken syntax.
1182
+ * `App.tsx#frag` ends in neither, so it would be skipped although the transform
1183
+ * handles it perfectly well. Cleaning here keeps the claim and the parse
1184
+ * looking at the same name.
1185
+ */
1186
+ function cleanModuleId(id) {
1187
+ return id.replace(/[?#].*$/, "");
1188
+ }
1189
+ function canTransform(id, options) {
1190
+ if (id.startsWith("\0")) return false;
1191
+ if (id.includes("/node_modules/") || id.includes("\\node_modules\\")) return false;
1192
+ if (!SCRIPT_MODULE.test(cleanModuleId(id))) return false;
1193
+ if (options.exclude?.(id)) return false;
1194
+ return options.include?.(id) ?? true;
1195
+ }
1196
+ /**
1197
+ * `.js`/`.mjs` deliberately resolve to `"jsx"`. JavaScript commonly carries JSX
1198
+ * without a `.jsx` suffix -- the transform's own parser configuration says so,
1199
+ * and its output for such a file still contains that JSX -- but unplugin's
1200
+ * esbuild adapter labels every file it hands back with `guessLoader`, which
1201
+ * maps `.js` to `"js"` and so overrides a project's own
1202
+ * `loader: { ".js": "jsx" }` setting. Because that adapter returns
1203
+ * `{ contents, loader }` for every file passing `transformInclude`, even the
1204
+ * ones this transform declines to touch, a single wrong answer here breaks the
1205
+ * whole build rather than just one module.
1206
+ */
1207
+ const ESBUILD_LOADERS = {
1208
+ ".js": "jsx",
1209
+ ".mjs": "jsx",
1210
+ ".jsx": "jsx",
1211
+ ".ts": "ts",
1212
+ ".mts": "ts",
1213
+ ".tsx": "tsx"
1214
+ };
1215
+ /**
1216
+ * The loader to label `id`'s contents with, deferring to the build's own
1217
+ * `loader` map first so a project that configured an extension keeps its
1218
+ * choice. Exported for the adapter tests, which exercise the decision without
1219
+ * needing esbuild itself.
1220
+ */
1221
+ function resolveEsbuildLoader(id, configuredLoaders) {
1222
+ const extension = /\.[^./\\]*$/.exec(cleanModuleId(id))?.[0].toLowerCase() ?? "";
1223
+ return configuredLoaders?.[extension] ?? ESBUILD_LOADERS[extension] ?? "js";
1224
+ }
1225
+ let transformLoaderPath;
1226
+ /** The absolute path of this package's own webpack/rspack loader, if reachable. */
1227
+ function resolveTransformLoaderPath() {
1228
+ if (transformLoaderPath === void 0) try {
1229
+ transformLoaderPath = fileURLToPath(new URL("./loader.js", import.meta.url));
1230
+ } catch {
1231
+ transformLoaderPath = null;
1232
+ }
1233
+ return transformLoaderPath;
1234
+ }
1235
+ const patchedMarker = "__reactFineGrainedSignalsLoaderPatched";
1236
+ let loaderPatchReported = false;
1237
+ /**
1238
+ * Says, once per process, that the rewrite below found nothing to rewrite.
1239
+ *
1240
+ * That rewrite is deliberately shape-matched, so an unplugin release which
1241
+ * restructures its transform rule keeps its own behaviour rather than breaking
1242
+ * the build -- but its own behaviour is the dropped source map this package's
1243
+ * loader exists to avoid, and falling back to it in silence is precisely the
1244
+ * regression nobody would notice. One line is enough to find it; repeating it
1245
+ * per module or per rebuild would only teach people to ignore it.
1246
+ */
1247
+ function reportLoaderPatchSkipped(reason) {
1248
+ if (loaderPatchReported) return;
1249
+ loaderPatchReported = true;
1250
+ console.warn(`[${pluginName}] Source map fix not applied: ${reason}. Falling back to unplugin's own transform loader, whose output maps point at pre-transform line numbers.`);
1251
+ }
1252
+ /** Is this the bare `{ enforce, use(data) }` rule unplugin adds for `transform`? */
1253
+ function isUnpluginTransformRule(rule) {
1254
+ if (rule === null || typeof rule !== "object") return false;
1255
+ if (typeof rule.use !== "function") return false;
1256
+ return Object.keys(rule).every((key) => key === "use" || key === "enforce");
1257
+ }
1258
+ /**
1259
+ * Repoints unplugin's webpack/rspack transform rule at this package's own
1260
+ * loader, which is the same loader with the source map passed through
1261
+ * correctly -- see `src/loader.ts` for why unplugin's copy drops it.
1262
+ *
1263
+ * The rule is rewritten rather than replaced: unplugin's `use(data)` still
1264
+ * decides *whether* this file is claimed (it applies `transformInclude` and
1265
+ * carries the plugin object along in `options`), and only the loader entries it
1266
+ * stamped with this plugin's `ident` have their module path swapped. Anything
1267
+ * that does not match that shape is left exactly as it was, so a future
1268
+ * unplugin release that restructures this simply gets its own behaviour back
1269
+ * instead of a broken build.
1270
+ */
1271
+ function useOwnTransformLoader(compiler) {
1272
+ const loaderPath = resolveTransformLoaderPath();
1273
+ if (loaderPath === null) {
1274
+ reportLoaderPatchSkipped("this package's own loader could not be located on disk");
1275
+ return;
1276
+ }
1277
+ const rules = compiler?.options?.module?.rules;
1278
+ if (!Array.isArray(rules)) {
1279
+ reportLoaderPatchSkipped("the compiler exposes no `module.rules` array");
1280
+ return;
1281
+ }
1282
+ let matched = false;
1283
+ for (const rule of rules) {
1284
+ if (!isUnpluginTransformRule(rule)) continue;
1285
+ matched = true;
1286
+ const original = rule.use;
1287
+ if (original[patchedMarker] === true) continue;
1288
+ const patched = (data) => {
1289
+ const entries = original(data);
1290
+ if (!Array.isArray(entries)) return entries;
1291
+ return entries.map((entry) => entry !== null && typeof entry === "object" && entry.ident === "unplugin-react-fine-grained-signals" ? {
1292
+ ...entry,
1293
+ loader: loaderPath
1294
+ } : entry);
1295
+ };
1296
+ patched[patchedMarker] = true;
1297
+ rule.use = patched;
1298
+ }
1299
+ if (!matched) reportLoaderPatchSkipped("unplugin's transform rule was not found in `module.rules`");
1300
+ }
1301
+ const reactFineGrainedSignals = createUnplugin((options = {}) => {
1302
+ let configuredLoaders;
1303
+ return {
1304
+ name: pluginName,
1305
+ enforce: "pre",
1306
+ transformInclude(id) {
1307
+ return canTransform(id, options);
1308
+ },
1309
+ transform(code, id) {
1310
+ return transformReactFineGrainedSignals(code, id, {
1311
+ importSource: options.importSource ?? "react-fine-grained-signals",
1312
+ reactImportSource: options.reactImportSource ?? "react",
1313
+ mode: options.mode ?? "auto",
1314
+ transform: options.transform ?? "managed",
1315
+ reactCompiler: options.reactCompiler ?? "auto"
1316
+ });
1317
+ },
1318
+ esbuild: {
1319
+ config(buildOptions) {
1320
+ configuredLoaders = buildOptions.loader;
1321
+ },
1322
+ loader(_code, id) {
1323
+ return resolveEsbuildLoader(id, configuredLoaders);
1324
+ }
1325
+ },
1326
+ webpack: useOwnTransformLoader,
1327
+ rspack: useOwnTransformLoader
1328
+ };
1329
+ });
1330
+ /**
1331
+ * The one piece every per-bundler entry file (`vite.ts`, `webpack.ts`,
1332
+ * `rollup.ts`, `esbuild.ts`, `rspack.ts`) repeated: casting `reactFineGrainedSignals`'s
1333
+ * `bundler` property to a callable factory of its own bundler-specific plugin
1334
+ * type. Each entry file supplies only the two things that actually differ
1335
+ * between bundlers -- which property to read and what shape it returns.
1336
+ */
1337
+ function createBundlerPlugin(bundler) {
1338
+ return reactFineGrainedSignals[bundler];
1339
+ }
1340
+ //#endregion
1341
+ export { resolveEsbuildLoader as a, reactFineGrainedSignals as i, createBundlerPlugin as n, pluginName as r, canTransform as t };