@barefootjs/mojolicious 0.3.0 → 0.5.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.
@@ -87,6 +87,7 @@ class MojoAdapter extends BaseAdapter {
87
87
  name = "mojolicious";
88
88
  extension = ".html.ep";
89
89
  templatesPerComponent = true;
90
+ importMapInjection = "html-snippet";
90
91
  templatePrimitives = MOJO_PRIMITIVE_EMIT_MAP;
91
92
  componentName = "";
92
93
  options;
@@ -919,8 +920,11 @@ function perlIdentifierFromMarkerId(markerId) {
919
920
  return markerId.replace(/[^a-zA-Z0-9]/g, (ch) => ch === "_" ? "__" : `_x${ch.charCodeAt(0).toString(16)}`);
920
921
  }
921
922
  function renderSortMethod(recv, c) {
922
- const keyEntry = c.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${c.key.field}'`;
923
- return `bf->sort(${recv}, { ${keyEntry}, compare_type => '${c.type}', direction => '${c.direction}' })`;
923
+ const keyHashes = c.keys.map((k) => {
924
+ const keyEntry = k.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${k.key.field}'`;
925
+ return `{ ${keyEntry}, compare_type => '${k.type}', direction => '${k.direction}' }`;
926
+ });
927
+ return `bf->sort(${recv}, { keys => [${keyHashes.join(", ")}] })`;
924
928
  }
925
929
 
926
930
  class MojoFilterEmitter {
@@ -24,6 +24,7 @@ export declare class MojoAdapter extends BaseAdapter implements IRNodeEmitter<Mo
24
24
  name: string;
25
25
  extension: string;
26
26
  templatesPerComponent: boolean;
27
+ importMapInjection: 'html-snippet';
27
28
  /**
28
29
  * Identifier-path callees the Mojo runtime can render in template
29
30
  * scope. The relocate pass consults this map to mark matching
@@ -1 +1 @@
1
- {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
package/dist/build.js CHANGED
@@ -87,6 +87,7 @@ class MojoAdapter extends BaseAdapter {
87
87
  name = "mojolicious";
88
88
  extension = ".html.ep";
89
89
  templatesPerComponent = true;
90
+ importMapInjection = "html-snippet";
90
91
  templatePrimitives = MOJO_PRIMITIVE_EMIT_MAP;
91
92
  componentName = "";
92
93
  options;
@@ -919,8 +920,11 @@ function perlIdentifierFromMarkerId(markerId) {
919
920
  return markerId.replace(/[^a-zA-Z0-9]/g, (ch) => ch === "_" ? "__" : `_x${ch.charCodeAt(0).toString(16)}`);
920
921
  }
921
922
  function renderSortMethod(recv, c) {
922
- const keyEntry = c.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${c.key.field}'`;
923
- return `bf->sort(${recv}, { ${keyEntry}, compare_type => '${c.type}', direction => '${c.direction}' })`;
923
+ const keyHashes = c.keys.map((k) => {
924
+ const keyEntry = k.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${k.key.field}'`;
925
+ return `{ ${keyEntry}, compare_type => '${k.type}', direction => '${k.direction}' }`;
926
+ });
927
+ return `bf->sort(${recv}, { keys => [${keyHashes.join(", ")}] })`;
924
928
  }
925
929
 
926
930
  class MojoFilterEmitter {
package/dist/index.js CHANGED
@@ -87,6 +87,7 @@ class MojoAdapter extends BaseAdapter {
87
87
  name = "mojolicious";
88
88
  extension = ".html.ep";
89
89
  templatesPerComponent = true;
90
+ importMapInjection = "html-snippet";
90
91
  templatePrimitives = MOJO_PRIMITIVE_EMIT_MAP;
91
92
  componentName = "";
92
93
  options;
@@ -919,8 +920,11 @@ function perlIdentifierFromMarkerId(markerId) {
919
920
  return markerId.replace(/[^a-zA-Z0-9]/g, (ch) => ch === "_" ? "__" : `_x${ch.charCodeAt(0).toString(16)}`);
920
921
  }
921
922
  function renderSortMethod(recv, c) {
922
- const keyEntry = c.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${c.key.field}'`;
923
- return `bf->sort(${recv}, { ${keyEntry}, compare_type => '${c.type}', direction => '${c.direction}' })`;
923
+ const keyHashes = c.keys.map((k) => {
924
+ const keyEntry = k.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${k.key.field}'`;
925
+ return `{ ${keyEntry}, compare_type => '${k.type}', direction => '${k.direction}' }`;
926
+ });
927
+ return `bf->sort(${recv}, { keys => [${keyHashes.join(", ")}] })`;
924
928
  }
925
929
 
926
930
  class MojoFilterEmitter {
package/lib/BarefootJS.pm CHANGED
@@ -530,7 +530,8 @@ sub trim ($self, $recv) {
530
530
  # lowering (#1448 Tier B). Non-mutating — JS's mutate-vs-new
531
531
  # distinction is moot in SSR template context.
532
532
  #
533
- # Opts hash-ref (compiler emits exactly these four keys):
533
+ # Opts hash-ref. The compiler emits a `keys` list of per-key hashes
534
+ # in priority order; each hash carries:
534
535
  #
535
536
  # key_kind => 'self' | 'field'
536
537
  # key => '' when key_kind eq 'self'; field name verbatim
@@ -539,7 +540,7 @@ sub trim ($self, $recv) {
539
540
  # applied. Perl hash lookups are case-sensitive so
540
541
  # the key here must match the actual hash key the
541
542
  # user populated.
542
- # compare_type => 'numeric' | 'string'
543
+ # compare_type => 'numeric' | 'string' | 'auto'
543
544
  # direction => 'asc' | 'desc'
544
545
  #
545
546
  # Accepted comparator catalogue (gated upstream at parse time —
@@ -548,47 +549,73 @@ sub trim ($self, $recv) {
548
549
  # (a,b) => a.f - b.f → field, numeric
549
550
  # (a,b) => a - b → self, numeric
550
551
  # (a,b) => a[.f].localeCompare(b[.f]) → field|self, string
552
+ # (a,b) => a.f > b.f ? 1 : -1 → field|self, auto
553
+ # any of the above ||-chained → multi-key tie-breaks
551
554
  # (and reversed-operand variants for `desc`).
552
555
  #
553
- # A future `nulls => 'first' | 'last'` knob can land alongside
554
- # without churnthe opts hash is the right place to grow.
556
+ # `auto` (relational-ternary lowering) compares numerically when both
557
+ # keys `looks_like_number`, else lexically Go's `bf_sort` applies the
558
+ # same rule so the two template adapters stay byte-equal.
559
+ #
560
+ # A future `nulls => 'first' | 'last'` knob can land per key without
561
+ # churn — the opts hash is the right place to grow.
555
562
 
556
563
  sub sort ($self, $recv, $opts = {}) {
557
564
  return [] unless ref($recv) eq 'ARRAY';
558
- my $key_kind = $opts->{key_kind} // 'self';
559
- my $key = $opts->{key} // '';
560
- my $compare_type = $opts->{compare_type} // 'numeric';
561
- my $direction = $opts->{direction} // 'asc';
562
-
563
- # Schwartzian transform: project each item to its sort key once,
564
- # then sort by key, then drop the keys. Cheaper than re-resolving
565
- # the field accessor inside every comparison for non-trivial arrays.
565
+
566
+ # Normalise the per-key specs (priority order, length >= 1).
567
+ my @spec = map {
568
+ {
569
+ key_kind => $_->{key_kind} // 'self',
570
+ key => $_->{key} // '',
571
+ compare_type => $_->{compare_type} // 'numeric',
572
+ direction => $_->{direction} // 'asc',
573
+ }
574
+ } @{ $opts->{keys} // [] };
575
+ return [ @$recv ] unless @spec;
576
+
577
+ # Schwartzian transform: project each item to all its sort keys
578
+ # once, then compare projected keys. Cheaper than re-resolving the
579
+ # field accessors inside every comparison for non-trivial arrays.
566
580
  my @keyed = map {
567
581
  my $item = $_;
568
- my $k = $key_kind eq 'field' && ref($item) eq 'HASH' ? $item->{$key} : $item;
569
- [$k, $item]
582
+ my @ks = map {
583
+ $_->{key_kind} eq 'field' && ref($item) eq 'HASH' ? $item->{ $_->{key} } : $item;
584
+ } @spec;
585
+ [ \@ks, $item ];
570
586
  } @$recv;
571
587
 
572
- my $cmp;
573
- if ($compare_type eq 'string') {
574
- $cmp = $direction eq 'desc'
575
- ? sub { ($b->[0] // '') cmp ($a->[0] // '') }
576
- : sub { ($a->[0] // '') cmp ($b->[0] // '') };
577
- } else {
578
- # Numeric: undef projects to 0 so the sort is total without
579
- # warnings on missing fields. Documented divergence from JS
580
- # (which would coerce undef → NaN and produce indeterminate
581
- # ordering); matching Go's `toFloat64(nil) == 0` keeps the
582
- # adapter outputs symmetric.
583
- $cmp = $direction eq 'desc'
584
- ? sub { ($b->[0] // 0) <=> ($a->[0] // 0) }
585
- : sub { ($a->[0] // 0) <=> ($b->[0] // 0) };
586
- }
588
+ my $cmp = sub {
589
+ for my $i (0 .. $#spec) {
590
+ my $sp = $spec[$i];
591
+ my $c = _compare_sort_key($a->[0][$i], $b->[0][$i], $sp->{compare_type});
592
+ next if $c == 0; # tie on this key try the next
593
+ return $sp->{direction} eq 'desc' ? -$c : $c;
594
+ }
595
+ return 0;
596
+ };
587
597
 
588
598
  my @sorted = sort $cmp @keyed;
589
599
  return [ map { $_->[1] } @sorted ];
590
600
  }
591
601
 
602
+ # Compare two projected keys, ascending orientation (-1 / 0 / 1); the
603
+ # caller negates for 'desc'. 'auto' compares numerically when both
604
+ # keys look like numbers, else lexically (matches Go's `bf_sort`).
605
+ # undef coalesces to '' / 0 so the order stays total without warnings.
606
+ sub _compare_sort_key ($av, $bv, $compare_type) {
607
+ if ($compare_type eq 'string') {
608
+ return ($av // '') cmp ($bv // '');
609
+ }
610
+ if ($compare_type eq 'auto') {
611
+ if (looks_like_number($av // '') && looks_like_number($bv // '')) {
612
+ return ($av // 0) <=> ($bv // 0);
613
+ }
614
+ return ($av // '') cmp ($bv // '');
615
+ }
616
+ return ($av // 0) <=> ($bv // 0); # numeric
617
+ }
618
+
592
619
  # ---------------------------------------------------------------------------
593
620
  # JSX intrinsic-element spread (#1407)
594
621
  # ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/mojolicious",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Mojolicious EP template adapter for BarefootJS - generates .html.ep files from IR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,14 +52,14 @@
52
52
  "directory": "packages/adapter-mojolicious"
53
53
  },
54
54
  "dependencies": {
55
- "@barefootjs/shared": "0.3.0"
55
+ "@barefootjs/shared": "0.5.0"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@barefootjs/jsx": ">=0.2.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@barefootjs/adapter-tests": "0.1.0",
62
- "@barefootjs/jsx": "0.3.0",
62
+ "@barefootjs/jsx": "0.5.0",
63
63
  "typescript": "^5.0.0"
64
64
  }
65
65
  }
@@ -894,6 +894,8 @@ import { fixture as arraySortFieldAscFixture } from '../../../adapter-tests/fixt
894
894
  import { fixture as arraySortFieldDescFixture } from '../../../adapter-tests/fixtures/methods/array-sort-field-desc'
895
895
  import { fixture as arraySortPrimitiveFixture } from '../../../adapter-tests/fixtures/methods/array-sort-primitive'
896
896
  import { fixture as arraySortLocaleFixture } from '../../../adapter-tests/fixtures/methods/array-sort-locale'
897
+ import { fixture as arraySortMultiKeyFixture } from '../../../adapter-tests/fixtures/methods/array-sort-multikey'
898
+ import { fixture as arraySortTernaryFixture } from '../../../adapter-tests/fixtures/methods/array-sort-ternary'
897
899
  import { fixture as arrayToSortedFixture } from '../../../adapter-tests/fixtures/methods/array-toSorted'
898
900
  // #1448 Tier B — .entries / .keys / .values iteration shapes.
899
901
  import { fixture as arrayEntriesFixture } from '../../../adapter-tests/fixtures/methods/array-entries'
@@ -918,12 +920,17 @@ describe('MojoAdapter - #1448 Tier A/B fixture-driven lowering pins', () => {
918
920
  { fixture: stringTrimFixture, expect: 'bf->trim($value)' },
919
921
  // #1448 Tier B — sort / toSorted. The loop-chained field cases
920
922
  // hoist into a `my $bf_iter_lN = bf->sort(...)` local; the
921
- // standalone primitive cases inline the call.
922
- { fixture: arraySortFieldAscFixture, expect: `bf->sort($items, { key_kind => 'field', key => 'price', compare_type => 'numeric', direction => 'asc' })` },
923
- { fixture: arraySortFieldDescFixture, expect: `bf->sort($items, { key_kind => 'field', key => 'price', compare_type => 'numeric', direction => 'desc' })` },
924
- { fixture: arraySortPrimitiveFixture, expect: `bf->sort($nums, { key_kind => 'self', compare_type => 'numeric', direction => 'asc' })` },
925
- { fixture: arraySortLocaleFixture, expect: `bf->sort($names, { key_kind => 'self', compare_type => 'string', direction => 'asc' })` },
926
- { fixture: arrayToSortedFixture, expect: `bf->sort($nums, { key_kind => 'self', compare_type => 'numeric', direction => 'asc' })` },
923
+ // standalone primitive cases inline the call. Each comparison key
924
+ // is one hash under `keys` (a single-key comparator one element).
925
+ { fixture: arraySortFieldAscFixture, expect: `bf->sort($items, { keys => [{ key_kind => 'field', key => 'price', compare_type => 'numeric', direction => 'asc' }] })` },
926
+ { fixture: arraySortFieldDescFixture, expect: `bf->sort($items, { keys => [{ key_kind => 'field', key => 'price', compare_type => 'numeric', direction => 'desc' }] })` },
927
+ { fixture: arraySortPrimitiveFixture, expect: `bf->sort($nums, { keys => [{ key_kind => 'self', compare_type => 'numeric', direction => 'asc' }] })` },
928
+ { fixture: arraySortLocaleFixture, expect: `bf->sort($names, { keys => [{ key_kind => 'self', compare_type => 'string', direction => 'asc' }] })` },
929
+ // Multi-key (`||`-chain): one hash per comparison key, in order.
930
+ { fixture: arraySortMultiKeyFixture, expect: `bf->sort($items, { keys => [{ key_kind => 'field', key => 'price', compare_type => 'numeric', direction => 'asc' }, { key_kind => 'field', key => 'name', compare_type => 'string', direction => 'asc' }] })` },
931
+ // Relational-ternary comparator lowers to a single `auto` key.
932
+ { fixture: arraySortTernaryFixture, expect: `bf->sort($items, { keys => [{ key_kind => 'field', key => 'rank', compare_type => 'auto', direction => 'asc' }] })` },
933
+ { fixture: arrayToSortedFixture, expect: `bf->sort($nums, { keys => [{ key_kind => 'self', compare_type => 'numeric', direction => 'asc' }] })` },
927
934
  // #1448 Tier B — iteration shapes. These are loop-level patterns.
928
935
  // .entries() → for loop with both $i index var and $v value var
929
936
  { fixture: arrayEntriesFixture, expect: '% my $v = $items->[$i];' },
@@ -135,6 +135,9 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
135
135
  name = 'mojolicious'
136
136
  extension = '.html.ep'
137
137
  templatesPerComponent = true
138
+ // Template-string target with no component layer: `bf build` emits a static
139
+ // `barefoot-importmap.html` to `%= include` into the page <head> (#1644).
140
+ importMapInjection = 'html-snippet' as const
138
141
 
139
142
  /**
140
143
  * Identifier-path callees the Mojo runtime can render in template
@@ -1603,10 +1606,11 @@ function renderArrayMethod(
1603
1606
  * plus the loop-hoist path in `renderLoop` — same emit shape across
1604
1607
  * all three so a regression in any one path surfaces consistently.
1605
1608
  *
1606
- * The Perl helper accepts a hash-ref opts bag (room for a future
1607
- * `nulls` knob without arity churn), and returns a fresh ARRAY ref
1608
- * so downstream composition (`@{bf->sort(...)}` in `join(...)`, etc.)
1609
- * stays straightforward.
1609
+ * The Perl helper accepts a hash-ref opts bag whose `keys` entry is
1610
+ * an ordered list of per-key hashes (room for a future `nulls` knob
1611
+ * without arity churn), and returns a fresh ARRAY ref so downstream
1612
+ * composition (`@{bf->sort(...)}` in `join(...)`, etc.) stays
1613
+ * straightforward.
1610
1614
  */
1611
1615
  /**
1612
1616
  * Encode an `IRLoop.markerId` into a Perl-identifier-safe suffix
@@ -1626,11 +1630,18 @@ function perlIdentifierFromMarkerId(markerId: string): string {
1626
1630
  }
1627
1631
 
1628
1632
  function renderSortMethod(recv: string, c: SortComparator): string {
1629
- const keyEntry =
1630
- c.key.kind === 'self'
1631
- ? `key_kind => 'self'`
1632
- : `key_kind => 'field', key => '${c.key.field}'`
1633
- return `bf->sort(${recv}, { ${keyEntry}, compare_type => '${c.type}', direction => '${c.direction}' })`
1633
+ // One hash per comparison key, in priority order, under `keys`. A
1634
+ // simple comparator yields a one-element list; a `||`-chained
1635
+ // multi-key comparator yields one per operand. `bf->sort` walks them
1636
+ // in order, falling through to the next on a tie.
1637
+ const keyHashes = c.keys.map((k) => {
1638
+ const keyEntry =
1639
+ k.key.kind === 'self'
1640
+ ? `key_kind => 'self'`
1641
+ : `key_kind => 'field', key => '${k.key.field}'`
1642
+ return `{ ${keyEntry}, compare_type => '${k.type}', direction => '${k.direction}' }`
1643
+ })
1644
+ return `bf->sort(${recv}, { keys => [${keyHashes.join(', ')}] })`
1634
1645
  }
1635
1646
 
1636
1647
  /**
@@ -305,7 +305,7 @@ function buildChildRenderers(
305
305
  const lines: string[] = []
306
306
  lines.push(`# Register child component renderers`)
307
307
 
308
- for (const [componentName] of childTemplates) {
308
+ for (const [componentName, { ir: childIR }] of childTemplates) {
309
309
  const snakeName = toSnakeCase(componentName)
310
310
  const childTemplatePath = resolve(tempDir, `${snakeName}.html.ep`)
311
311
 
@@ -326,6 +326,22 @@ function buildChildRenderers(
326
326
 
327
327
  lines.push(` $bf->register_child_renderer('${snakeName}', sub {`)
328
328
  lines.push(` my ($child_props) = @_;`)
329
+ // (#1652) A child that destructures a rest-spread bag
330
+ // (`function NativeSelect({ children, ...props })`) emits a
331
+ // template referencing `$<restPropsName>`
332
+ // (`<%== bf->spread_attrs($props) %>`). The parent's render_child
333
+ // call only forwards the props it explicitly passed (here just
334
+ // `children`), so the rest bag never reaches the child stash and
335
+ // Perl strict mode aborts with `Global symbol "$props" requires
336
+ // explicit package name`. Seed it with an empty hashref when the
337
+ // caller didn't supply one — mirroring the top-level harness path
338
+ // (`buildPerlProps`) and the production runtime's
339
+ // `_derive_stash_from_defaults` `isRestProps` branch, which plumbs
340
+ // the equivalent of Go's `Spread_0`/`Extras` Input field.
341
+ if (childIR.metadata.restPropsName) {
342
+ const rest = childIR.metadata.restPropsName
343
+ lines.push(` $child_props->{${rest}} = {} unless defined $child_props->{${rest}};`)
344
+ }
329
345
  lines.push(` ${slotIdsPerl}`)
330
346
  lines.push(` my $child_bf = BarefootJS->new($c, {});`)
331
347
  lines.push(` $child_bf->_scope_id("test_$sid");`)