ef.js

Declarative DOM helper experiment

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
# JSX sample

This page demonstrates simplicity and composability of ef.js using sample JSX implementation.
Thanks to simple design choices, you can create your own JSX library which directly returns a DOM element.

Sample application is a simple counter (increase decrease number) using component and Signal reactivity.

## Limitation

Due to design constraint of both JSX and ef.js, several pitfalls exist.

Those are workaround-able, but resulting implementation would be opinionated and different to other "normal" JSX implementations (such as React).
For this reason, ef.js does not provide first-party JSX support.
You need to implement your own.

### No way to create namespaced elements

SVG and MathML elements requires `document.createElementNS` with namespace URI for creating each elements.
However, JSX does not have a concept of namespace URI: everything is HTML.
JSX does have XML namespace (`<foo:bar baz:qux="" />`), but this is different from namespace URI, which has important role in DOM.
Therefore, in order to support those non-HTML elements, you need to add a hack on top of JSX or check the tag name (requires full list of SVG/MathML tag names = increased JS filesize).
If you care about correctness, the latter is not an option because both HTML and SVG have overlapping tag names (e.g. `<a>`, `<title>`, `<style>`).

```jsx
// For example...

// By abusing XML namespace, probably compatibility issue on some tool
const nsLikeHack = (
  <svg:svg>
    <svg:path d="M0,0 L1,0 L0,1 Z" />
    <svg:circle cx="3" cy="3" r="2" />
  </svg:svg>
);

// Simple and straightforward, but unintuitive (totally not HTML)
const attrHack = (
  <svg svg>
    <rect svg width="10" height="10" />
  </svg>
);

// Explicit and straightforward, but verbose
// More DOM/HTML-ish than the above `svg` boolean attribute, though.
const svgNS = "http://www.w3.org/2000/svg";
const verboseAttr = (
  <svg ns={svgNS}>
    <path ns={svgNS} d="M1,0 L0,1" />
  </svg>
);
```

Preact avoids this problem by turning `isSvg` on when it encounter `<svg>` (`SVGSVGElement`) so descendant elements are treated as SVG elements (probably same for MathML elements).
This approach is not appliable to ef.js because Element creation is performed on inverse order (child first).

### No distinction between attributes and properties

By design, JSX does not distinguish attributes and properties.
Hence, a hack such as "set as attribute if the element does not have this property" is required.

You can avoid that by adapting to identifier modifier approach like Vue.js, but it would spoil JSX's merit due to code would be less HTML-ish (and SVG-ish, MathML-ish).

```jsx
// As an explanation, better avoid this
// prefixed with "$" ... attr, everything else ... props
// Maybe works better with TypeScript thanks to template string literal type?
const clearProps = <div $tabindex="0" tabIndex={0} />;

// Wrap with marker object (detect it using Symbol or Class)
// Typing complexity and possible runtime performance penalty?
const markValues = <div tabindex={attr("0")} tabIndex={0} />;
```