@fluixi/compiler 1.0.0-alpha.93 → 1.0.0-alpha.95

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.
Files changed (93) hide show
  1. package/LICENSE +1 -1
  2. package/dist/analyze/intrinsics.cjs +13 -1
  3. package/dist/analyze/intrinsics.d.cts +14 -0
  4. package/dist/analyze/intrinsics.d.ts +14 -0
  5. package/dist/analyze/intrinsics.d.ts.map +1 -1
  6. package/dist/analyze/intrinsics.js +17 -0
  7. package/dist/analyze/intrinsics.mjs +13 -1
  8. package/dist/analyze/static-graph.cjs +1 -1
  9. package/dist/analyze/static-graph.mjs +1 -1
  10. package/dist/{babel-RJHJQVEM.mjs → babel-V33QLJBF.mjs} +4 -4
  11. package/dist/{chunk-56QSOVVL.mjs → chunk-62F2LLBM.mjs} +1 -1
  12. package/dist/{chunk-R7PPFBCL.mjs → chunk-7GNSGUHK.mjs} +4 -83
  13. package/dist/{chunk-U7HCFLZ2.mjs → chunk-B64LVPTU.mjs} +356 -24
  14. package/dist/{chunk-OD53TMER.mjs → chunk-C3AK5PBV.mjs} +13 -1
  15. package/dist/{chunk-TJIH7TLX.mjs → chunk-GLFUPZ27.mjs} +1 -1
  16. package/dist/chunk-HUABRVWB.mjs +92 -0
  17. package/dist/chunk-XCWP7WJG.mjs +584 -0
  18. package/dist/codegen/backends/imperative.cjs +1 -1
  19. package/dist/codegen/backends/imperative.d.ts.map +1 -1
  20. package/dist/codegen/backends/imperative.js +2 -1
  21. package/dist/codegen/backends/imperative.mjs +1 -1
  22. package/dist/frontend/babel/build-ir-lit.cjs +352 -22
  23. package/dist/frontend/babel/build-ir-lit.mjs +352 -22
  24. package/dist/frontend/babel/build-ir.cjs +352 -22
  25. package/dist/frontend/babel/build-ir.mjs +352 -22
  26. package/dist/frontend/babel/index.cjs +353 -23
  27. package/dist/frontend/babel/index.mjs +353 -23
  28. package/dist/frontend/babel/lower-template.cjs +352 -22
  29. package/dist/frontend/babel/lower-template.mjs +352 -22
  30. package/dist/frontend/babel/plugin.cjs +352 -22
  31. package/dist/frontend/babel/plugin.mjs +352 -22
  32. package/dist/index.cjs +321 -22
  33. package/dist/index.mjs +333 -22
  34. package/dist/integrations.cjs +1240 -56
  35. package/dist/integrations.d.ts.map +1 -1
  36. package/dist/integrations.js +62 -9
  37. package/dist/integrations.mjs +257 -26
  38. package/dist/lower/compile-template.cjs +315 -21
  39. package/dist/lower/compile-template.d.cts +13 -0
  40. package/dist/lower/compile-template.d.ts +13 -0
  41. package/dist/lower/compile-template.d.ts.map +1 -1
  42. package/dist/lower/compile-template.js +3 -0
  43. package/dist/lower/compile-template.mjs +327 -21
  44. package/dist/lower/entities.cjs +288 -0
  45. package/dist/lower/entities.d.cts +1 -0
  46. package/dist/lower/entities.d.ts +2 -0
  47. package/dist/lower/entities.d.ts.map +1 -0
  48. package/dist/lower/entities.js +54 -0
  49. package/dist/lower/entities.mjs +267 -0
  50. package/dist/lower/jsx.cjs +104 -47
  51. package/dist/lower/jsx.d.cts +8 -0
  52. package/dist/lower/jsx.d.ts +8 -0
  53. package/dist/lower/jsx.d.ts.map +1 -1
  54. package/dist/lower/jsx.js +92 -2
  55. package/dist/lower/jsx.mjs +104 -47
  56. package/dist/lower/template.cjs +293 -20
  57. package/dist/lower/template.d.ts.map +1 -1
  58. package/dist/lower/template.js +35 -19
  59. package/dist/lower/template.mjs +293 -20
  60. package/dist/resolve/builtins.cjs +1 -1
  61. package/dist/resolve/builtins.mjs +1 -1
  62. package/dist/resolve/component-globals.cjs +6 -1
  63. package/dist/resolve/component-globals.js +2 -2
  64. package/dist/resolve/component-globals.mjs +6 -1
  65. package/dist/resolve/write-globals.cjs +6 -1
  66. package/dist/resolve/write-globals.mjs +6 -1
  67. package/dist/{server-fns-ZFRDIQ2X.mjs → server-fns-N3SDJXA7.mjs} +5 -4
  68. package/dist/{server-functions-CTDXX67X.mjs → server-functions-LVS66Q4R.mjs} +3 -3
  69. package/dist/transform/index.cjs +973 -33
  70. package/dist/transform/index.mjs +991 -47
  71. package/dist/transform/props.mjs +19 -14
  72. package/dist/transform/server-fns.mjs +19 -14
  73. package/dist/transform/styles.cjs +606 -0
  74. package/dist/transform/styles.d.cts +237 -0
  75. package/dist/transform/styles.d.ts +238 -0
  76. package/dist/transform/styles.d.ts.map +1 -0
  77. package/dist/transform/styles.js +743 -0
  78. package/dist/transform/styles.mjs +671 -0
  79. package/dist/transform/templates.cjs +972 -32
  80. package/dist/transform/templates.d.cts +13 -0
  81. package/dist/transform/templates.d.ts +13 -0
  82. package/dist/transform/templates.d.ts.map +1 -1
  83. package/dist/transform/templates.js +49 -12
  84. package/dist/transform/templates.mjs +990 -46
  85. package/dist/{transform-QWWQDCXE.mjs → transform-P4AETYV4.mjs} +49 -16
  86. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  87. package/dist/vite-styles.cjs +223 -0
  88. package/dist/vite-styles.d.cts +68 -0
  89. package/dist/vite-styles.d.ts +69 -0
  90. package/dist/vite-styles.d.ts.map +1 -0
  91. package/dist/vite-styles.js +241 -0
  92. package/dist/vite-styles.mjs +214 -0
  93. package/package.json +5 -4
package/dist/lower/jsx.js CHANGED
@@ -4,6 +4,7 @@ import { shapeOf } from '../analyze/expr-shape.js';
4
4
  import { analyzeStatic } from '../optimize/analyze-static.js';
5
5
  import { cleanTemplateText } from './text.js';
6
6
  import { paren } from '../analyze/paren.js';
7
+ import { markHole } from '../transform/source-locations.js';
7
8
  import { imperativeBackend, inlineTemplates } from '../codegen/backends/imperative.js';
8
9
  export const SVG_TAGS = new Set([
9
10
  'svg', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline',
@@ -132,9 +133,18 @@ function attrExprCode(attr, ctx) {
132
133
  }
133
134
  return null;
134
135
  }
135
- function buildProps(attrs, ctx) {
136
+ /**
137
+ * `element`: the tag is a DOM element. Only there are class:/style: and a style object taken
138
+ * apart; a component gets its props as written.
139
+ */
140
+ function buildProps(attrs, ctx, element = false) {
136
141
  const props = [];
137
142
  const usePairs = []; // use:name directives collapse into one `use` prop
143
+ // class:x and style:x collapse into one classList / style object, as in templates; each
144
+ // value that moves is an accessor in it, so the runtime updates that one key.
145
+ const classEntries = [];
146
+ const styleEntries = [];
147
+ let styleAt;
138
148
  for (const a of attrs) {
139
149
  if (a.type === 'JSXSpreadAttribute') {
140
150
  props.push({ name: '', kind: 'spread', expr: ctx.code(a.argument) });
@@ -155,6 +165,33 @@ function buildProps(attrs, ctx) {
155
165
  usePairs.push(opts != null ? `[${dir}, () => ${paren(opts, 'arrowBody')}]` : `[${dir}]`);
156
166
  continue;
157
167
  }
168
+ if (element && (ns === 'class' || ns === 'style')) {
169
+ const key = JSON.stringify(a.name.name.name);
170
+ const v = a.value;
171
+ let value;
172
+ if (v == null)
173
+ value = 'true';
174
+ else if (isString(v))
175
+ value = JSON.stringify(literalOf(v));
176
+ else if (v.type === 'JSXExpressionContainer' && v.expression?.type !== 'JSXEmptyExpression') {
177
+ value = keyValue(v.expression, ctx);
178
+ if (ns === 'style')
179
+ styleAt ??= posOf(v.expression, ctx);
180
+ }
181
+ else
182
+ continue;
183
+ (ns === 'class' ? classEntries : styleEntries).push(`${key}: ${value}`);
184
+ continue;
185
+ }
186
+ // style={{ … }}: one entry per property, so `--gap` moving does not rewrite `margin`.
187
+ if (element && a.name.type === 'JSXIdentifier' && a.name.name === 'style' && a.value?.type === 'JSXExpressionContainer') {
188
+ const entries = objectEntries(a.value.expression, ctx);
189
+ if (entries) {
190
+ styleEntries.unshift(...entries);
191
+ styleAt = posOf(a.value.expression, ctx);
192
+ continue;
193
+ }
194
+ }
158
195
  // oncapture:name -> native on:name with { capture: true }
159
196
  if (ns === 'oncapture') {
160
197
  const evt = a.name.name.name.toLowerCase();
@@ -164,10 +201,51 @@ function buildProps(attrs, ctx) {
164
201
  }
165
202
  props.push(buildAttr(a, ctx));
166
203
  }
204
+ if (classEntries.length > 0)
205
+ props.push({ name: 'classList', kind: 'attr', expr: `{ ${classEntries.join(', ')} }`, reactive: false });
206
+ if (styleEntries.length > 0) {
207
+ const entries = `{ ${styleEntries.join(', ')} }`;
208
+ // A style the object form could not take apart (a string, a variable) goes first, and
209
+ // the keys after it: the runtime applies an array in order.
210
+ const base = props.findIndex((p) => p.name === 'style' && p.kind === 'attr');
211
+ if (base === -1)
212
+ props.push({ name: 'style', kind: 'attr', expr: entries, reactive: false, ...(styleAt ? { at: styleAt } : {}) });
213
+ else {
214
+ const prop = props[base];
215
+ const value = prop.literal !== undefined ? JSON.stringify(prop.literal) : prop.reactive ? `() => ${paren(prop.expr, 'arrowBody')}` : prop.expr;
216
+ props[base] = { name: 'style', kind: 'attr', expr: `[${value}, ${entries}]`, reactive: false, ...(prop.at ? { at: prop.at } : {}) };
217
+ }
218
+ }
167
219
  if (usePairs.length > 0)
168
220
  props.push({ name: 'use', kind: 'attr', expr: `[${usePairs.join(', ')}]` });
169
221
  return props;
170
222
  }
223
+ /** A value inside a classList / style object: an accessor when it moves. */
224
+ function keyValue(expr, ctx) {
225
+ const code = ctx.code(expr);
226
+ if (!isReactive(expr))
227
+ return code;
228
+ const accessor = `() => ${paren(code, 'arrowBody')}`;
229
+ const at = posOf(expr, ctx);
230
+ return at ? markHole(accessor, at.line, at.column) : accessor;
231
+ }
232
+ /** `{ a: x, '--b': y }` as object entries; null for anything with a spread or a method. */
233
+ function objectEntries(expr, ctx) {
234
+ if (expr?.type !== 'ObjectExpression')
235
+ return null;
236
+ const entries = [];
237
+ for (const p of expr.properties) {
238
+ if (p.type !== 'ObjectProperty')
239
+ return null;
240
+ const key = p.computed
241
+ ? `[${ctx.code(p.key)}]`
242
+ : p.key.type === 'Identifier'
243
+ ? JSON.stringify(p.key.name)
244
+ : JSON.stringify(String(p.key.value));
245
+ entries.push(`${key}: ${keyValue(p.value, ctx)}`);
246
+ }
247
+ return entries;
248
+ }
171
249
  function buildChildren(children, ctx) {
172
250
  const out = [];
173
251
  for (let i = 0; i < children.length; i++) {
@@ -203,6 +281,9 @@ function buildChildren(children, ctx) {
203
281
  }
204
282
  }
205
283
  else if (child.type === 'JSXElement' || child.type === 'JSXFragment') {
284
+ // A `<style>` the compiler took out leaves no child behind, not even an empty insert.
285
+ if (ctx.dropStyles?.has(child.start))
286
+ continue;
206
287
  out.push(buildNode(child, ctx));
207
288
  }
208
289
  else if (child.type === 'JSXSpreadChild') {
@@ -295,7 +376,13 @@ function buildElement(node, ctx, handled) {
295
376
  const at = ctx.sourceFile && node.loc?.start
296
377
  ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column }
297
378
  : undefined;
298
- return { kind: 'element', tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...(at ? { at } : {}) };
379
+ const props = buildProps(attributes, ctx, true);
380
+ // A scoped style's mark: a static attribute, so it is part of the template itself and
381
+ // every render carries it, on the client, from the server and through hydration.
382
+ const scope = ctx.scopes?.find((s) => node.start >= s.start && node.end <= s.end);
383
+ if (scope)
384
+ props.unshift({ name: 'data-fx-s', kind: 'attr', literal: scope.value });
385
+ return { kind: 'element', tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...(at ? { at } : {}) };
299
386
  }
300
387
  function buildNode(node, ctx) {
301
388
  if (node.type === 'JSXFragment') {
@@ -303,6 +390,9 @@ function buildNode(node, ctx) {
303
390
  }
304
391
  if (directive(node, 'if'))
305
392
  return buildIf(node, null, ctx);
393
+ // Its CSS went to the host; nothing of the element is rendered.
394
+ if (ctx.dropStyles?.has(node.start))
395
+ return { kind: 'fragment', children: [] };
306
396
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
307
397
  if (!component)
308
398
  return buildElement(node, ctx, []);
@@ -305,6 +305,50 @@ function paren(code, position) {
305
305
  return topLevelComma(src) === false ? code : `(${code})`;
306
306
  }
307
307
 
308
+ // src/analyze/intrinsics.ts
309
+ var REACTIVE_MODULE = "@fluixi/reactive/signal";
310
+ var INTRINSICS = {
311
+ $signal: { export: "signal", module: REACTIVE_MODULE, returns: "signal-handle" },
312
+ $memo: { export: "memo", module: REACTIVE_MODULE, returns: "memo-handle" },
313
+ $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
314
+ $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
315
+ $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
316
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
317
+ // No short runtime form for these yet, so the intrinsic stands for the `create*`
318
+ // name. Still worth having: no import to write, and when a shorter form is
319
+ // designed, the change is this line rather than a compiler pass.
320
+ $selector: { export: "createSelector", module: REACTIVE_MODULE, returns: "memo-accessor" },
321
+ $deferred: { export: "createDeferred", module: REACTIVE_MODULE, returns: "memo-accessor" },
322
+ // Not value constructors, but the compiler has to know them: they change what a read
323
+ // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
324
+ // dependency analysis must not guess at.
325
+ $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
326
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
327
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
328
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
329
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
330
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
331
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
332
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
333
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
334
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
335
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
336
+ // handle as far as reads go: the same accessor with `set` on it.
337
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
338
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
339
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
340
+ // An environment variable, public or server-only; the browser build refuses a secret.
341
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
342
+ };
343
+ var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
344
+
345
+ // src/transform/source-locations.ts
346
+ var SOURCE_MARKER = "__fx_source";
347
+ var HOLE_MARKER = "__fx_hole";
348
+ var markHole = (accessor, line, column, name) => `(globalThis.${HOLE_MARKER} ?? ((f) => f))(${accessor},${line},${column}${name === void 0 ? "" : `,${JSON.stringify(name)}`})`;
349
+ var PROPS_MARKER = "__fx_props";
350
+ var markProps = (object, at) => `(globalThis.${PROPS_MARKER} ?? ((p) => p))(${object},${JSON.stringify(at)})`;
351
+
308
352
  // src/codegen/serialize-static.ts
309
353
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
310
354
  "area",
@@ -509,50 +553,6 @@ var RUNTIME_CONTRACT_V2 = {
509
553
  symbols: [...RUNTIME_CONTRACT_V1.symbols, "template", "cloneTemplate", "walk"]
510
554
  };
511
555
 
512
- // src/analyze/intrinsics.ts
513
- var REACTIVE_MODULE = "@fluixi/reactive/signal";
514
- var INTRINSICS = {
515
- $signal: { export: "signal", module: REACTIVE_MODULE, returns: "signal-handle" },
516
- $memo: { export: "memo", module: REACTIVE_MODULE, returns: "memo-handle" },
517
- $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
518
- $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
519
- $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
520
- $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
521
- // No short runtime form for these yet, so the intrinsic stands for the `create*`
522
- // name. Still worth having: no import to write, and when a shorter form is
523
- // designed, the change is this line rather than a compiler pass.
524
- $selector: { export: "createSelector", module: REACTIVE_MODULE, returns: "memo-accessor" },
525
- $deferred: { export: "createDeferred", module: REACTIVE_MODULE, returns: "memo-accessor" },
526
- // Not value constructors, but the compiler has to know them: they change what a read
527
- // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
528
- // dependency analysis must not guess at.
529
- $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
530
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
531
- // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
532
- // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
533
- $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
534
- $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
535
- $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
536
- $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
537
- $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
538
- $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
539
- // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
540
- // handle as far as reads go: the same accessor with `set` on it.
541
- $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
542
- $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
543
- $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
544
- // An environment variable, public or server-only; the browser build refuses a secret.
545
- $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
546
- };
547
- var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
548
-
549
- // src/transform/source-locations.ts
550
- var SOURCE_MARKER = "__fx_source";
551
- var HOLE_MARKER = "__fx_hole";
552
- var markHole = (accessor, line, column, name) => `(globalThis.${HOLE_MARKER} ?? ((f) => f))(${accessor},${line},${column}${name === void 0 ? "" : `,${JSON.stringify(name)}`})`;
553
- var PROPS_MARKER = "__fx_props";
554
- var markProps = (object, at) => `(globalThis.${PROPS_MARKER} ?? ((p) => p))(${object},${JSON.stringify(at)})`;
555
-
556
556
  // src/codegen/backends/imperative.ts
557
557
  var CONTROL_COMPONENT = {
558
558
  show: "Show",
@@ -614,7 +614,7 @@ function emitComponent(name, props, children, used, templates, at) {
614
614
  }
615
615
  let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
616
616
  if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
617
- const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
617
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => (${body})`);
618
618
  const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
619
619
  obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
620
620
  }
@@ -858,9 +858,12 @@ function attrExprCode(attr, ctx) {
858
858
  }
859
859
  return null;
860
860
  }
861
- function buildProps(attrs, ctx) {
861
+ function buildProps(attrs, ctx, element = false) {
862
862
  const props = [];
863
863
  const usePairs = [];
864
+ const classEntries = [];
865
+ const styleEntries = [];
866
+ let styleAt;
864
867
  for (const a of attrs) {
865
868
  if (a.type === "JSXSpreadAttribute") {
866
869
  props.push({ name: "", kind: "spread", expr: ctx.code(a.argument) });
@@ -876,6 +879,27 @@ function buildProps(attrs, ctx) {
876
879
  usePairs.push(opts != null ? `[${dir}, () => ${paren(opts, "arrowBody")}]` : `[${dir}]`);
877
880
  continue;
878
881
  }
882
+ if (element && (ns === "class" || ns === "style")) {
883
+ const key = JSON.stringify(a.name.name.name);
884
+ const v = a.value;
885
+ let value;
886
+ if (v == null) value = "true";
887
+ else if (isString(v)) value = JSON.stringify(literalOf(v));
888
+ else if (v.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression") {
889
+ value = keyValue(v.expression, ctx);
890
+ if (ns === "style") styleAt ??= posOf(v.expression, ctx);
891
+ } else continue;
892
+ (ns === "class" ? classEntries : styleEntries).push(`${key}: ${value}`);
893
+ continue;
894
+ }
895
+ if (element && a.name.type === "JSXIdentifier" && a.name.name === "style" && a.value?.type === "JSXExpressionContainer") {
896
+ const entries = objectEntries(a.value.expression, ctx);
897
+ if (entries) {
898
+ styleEntries.unshift(...entries);
899
+ styleAt = posOf(a.value.expression, ctx);
900
+ continue;
901
+ }
902
+ }
879
903
  if (ns === "oncapture") {
880
904
  const evt = a.name.name.name.toLowerCase();
881
905
  const h = attrExprCode(a, ctx) ?? "undefined";
@@ -884,9 +908,37 @@ function buildProps(attrs, ctx) {
884
908
  }
885
909
  props.push(buildAttr(a, ctx));
886
910
  }
911
+ if (classEntries.length > 0) props.push({ name: "classList", kind: "attr", expr: `{ ${classEntries.join(", ")} }`, reactive: false });
912
+ if (styleEntries.length > 0) {
913
+ const entries = `{ ${styleEntries.join(", ")} }`;
914
+ const base = props.findIndex((p) => p.name === "style" && p.kind === "attr");
915
+ if (base === -1) props.push({ name: "style", kind: "attr", expr: entries, reactive: false, ...styleAt ? { at: styleAt } : {} });
916
+ else {
917
+ const prop = props[base];
918
+ const value = prop.literal !== void 0 ? JSON.stringify(prop.literal) : prop.reactive ? `() => ${paren(prop.expr, "arrowBody")}` : prop.expr;
919
+ props[base] = { name: "style", kind: "attr", expr: `[${value}, ${entries}]`, reactive: false, ...prop.at ? { at: prop.at } : {} };
920
+ }
921
+ }
887
922
  if (usePairs.length > 0) props.push({ name: "use", kind: "attr", expr: `[${usePairs.join(", ")}]` });
888
923
  return props;
889
924
  }
925
+ function keyValue(expr, ctx) {
926
+ const code = ctx.code(expr);
927
+ if (!isReactive(expr)) return code;
928
+ const accessor = `() => ${paren(code, "arrowBody")}`;
929
+ const at = posOf(expr, ctx);
930
+ return at ? markHole(accessor, at.line, at.column) : accessor;
931
+ }
932
+ function objectEntries(expr, ctx) {
933
+ if (expr?.type !== "ObjectExpression") return null;
934
+ const entries = [];
935
+ for (const p of expr.properties) {
936
+ if (p.type !== "ObjectProperty") return null;
937
+ const key = p.computed ? `[${ctx.code(p.key)}]` : p.key.type === "Identifier" ? JSON.stringify(p.key.name) : JSON.stringify(String(p.key.value));
938
+ entries.push(`${key}: ${keyValue(p.value, ctx)}`);
939
+ }
940
+ return entries;
941
+ }
890
942
  function buildChildren(children, ctx) {
891
943
  const out = [];
892
944
  for (let i = 0; i < children.length; i++) {
@@ -914,6 +966,7 @@ function buildChildren(children, ctx) {
914
966
  });
915
967
  }
916
968
  } else if (child.type === "JSXElement" || child.type === "JSXFragment") {
969
+ if (ctx.dropStyles?.has(child.start)) continue;
917
970
  out.push(buildNode(child, ctx));
918
971
  } else if (child.type === "JSXSpreadChild") {
919
972
  out.push({ kind: "expr", code: ctx.code(child.expression), reactive: false });
@@ -983,13 +1036,17 @@ function buildElement(node, ctx, handled) {
983
1036
  const children = each ? [each] : buildChildren(node.children, ctx);
984
1037
  if (tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
985
1038
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
986
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1039
+ const props = buildProps(attributes, ctx, true);
1040
+ const scope = ctx.scopes?.find((s) => node.start >= s.start && node.end <= s.end);
1041
+ if (scope) props.unshift({ name: "data-fx-s", kind: "attr", literal: scope.value });
1042
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
987
1043
  }
988
1044
  function buildNode(node, ctx) {
989
1045
  if (node.type === "JSXFragment") {
990
1046
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
991
1047
  }
992
1048
  if (directive(node, "if")) return buildIf(node, null, ctx);
1049
+ if (ctx.dropStyles?.has(node.start)) return { kind: "fragment", children: [] };
993
1050
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
994
1051
  if (!component) return buildElement(node, ctx, []);
995
1052
  const props = buildProps(node.openingElement.attributes, ctx);