@reticlehq/babel-plugin 2.13.0 → 2.14.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,37 @@
1
+ /**
2
+ * The lowercase JSX tags that are actually DOM elements.
3
+ *
4
+ * "Lowercase means host element" was the rule, and it is true only for React DOM. React is a
5
+ * reconciler interface, not a renderer: react-three-fiber, react-pdf, ink and friends define their
6
+ * own lowercase intrinsics, and those host instances are not nodes and do not take attributes.
7
+ *
8
+ * R3F is the case that bit. Its `applyProps` treats any prop containing a dash as a PIERCED
9
+ * PROPERTY PATH, so `data-reticle-source` is walked as `data` -> `reticle` -> `source`; there is no
10
+ * `data` object on a three.js instance, and it throws from inside the commit phase. Unhandled there,
11
+ * the whole React tree unmounts to a white screen — and because it needs an element UPDATE rather
12
+ * than a mount, it fires long after the app looked fine.
13
+ *
14
+ * So the check is an ALLOWLIST, not a heuristic. The set of custom-reconciler intrinsics is
15
+ * unbounded and cannot be enumerated; the set of HTML and SVG tags can. Getting it wrong in the
16
+ * conservative direction costs one source pointer on an exotic element. Getting it wrong the other
17
+ * way costs the user their entire app.
18
+ *
19
+ * WHAT THIS DOES NOT COVER, stated rather than implied: a handful of three.js class names collide
20
+ * with real DOM tags — `line` is both `<line>` in SVG and `THREE.Line`, and `audio` is both. A tag
21
+ * name alone cannot separate them, and a lexical "is it under a <Canvas>" walk only sees the file
22
+ * it is transforming, so a scene component in its own file would still slip through. The complete
23
+ * answer for a three.js app is `reticle({ sourceMapping: false })` — which `reticle init` writes on
24
+ * its own when it finds one of these renderers in the manifest (see `Detection.customReconciler`).
25
+ *
26
+ * This paragraph described that as already true for a release in which it was not: nothing read the
27
+ * manifest, and the crash reached new installs anyway. The allowlist below is a mitigation, not the
28
+ * remedy; the manifest check is the remedy.
29
+ */
30
+ /**
31
+ * Whether `tag` names a DOM element that will accept an arbitrary `data-*` attribute.
32
+ *
33
+ * Custom elements are deliberately included: a tag containing a dash is a valid custom element per
34
+ * the HTML spec, it IS a DOM node, and `data-*` on it is ordinary. That rule cannot collide with a
35
+ * custom reconciler's intrinsics, which are bare identifiers (`mesh`, `group`, `box`).
36
+ */
37
+ export declare function isDomTag(tag: string): boolean;
@@ -0,0 +1,274 @@
1
+ "use strict";
2
+ /**
3
+ * The lowercase JSX tags that are actually DOM elements.
4
+ *
5
+ * "Lowercase means host element" was the rule, and it is true only for React DOM. React is a
6
+ * reconciler interface, not a renderer: react-three-fiber, react-pdf, ink and friends define their
7
+ * own lowercase intrinsics, and those host instances are not nodes and do not take attributes.
8
+ *
9
+ * R3F is the case that bit. Its `applyProps` treats any prop containing a dash as a PIERCED
10
+ * PROPERTY PATH, so `data-reticle-source` is walked as `data` -> `reticle` -> `source`; there is no
11
+ * `data` object on a three.js instance, and it throws from inside the commit phase. Unhandled there,
12
+ * the whole React tree unmounts to a white screen — and because it needs an element UPDATE rather
13
+ * than a mount, it fires long after the app looked fine.
14
+ *
15
+ * So the check is an ALLOWLIST, not a heuristic. The set of custom-reconciler intrinsics is
16
+ * unbounded and cannot be enumerated; the set of HTML and SVG tags can. Getting it wrong in the
17
+ * conservative direction costs one source pointer on an exotic element. Getting it wrong the other
18
+ * way costs the user their entire app.
19
+ *
20
+ * WHAT THIS DOES NOT COVER, stated rather than implied: a handful of three.js class names collide
21
+ * with real DOM tags — `line` is both `<line>` in SVG and `THREE.Line`, and `audio` is both. A tag
22
+ * name alone cannot separate them, and a lexical "is it under a <Canvas>" walk only sees the file
23
+ * it is transforming, so a scene component in its own file would still slip through. The complete
24
+ * answer for a three.js app is `reticle({ sourceMapping: false })` — which `reticle init` writes on
25
+ * its own when it finds one of these renderers in the manifest (see `Detection.customReconciler`).
26
+ *
27
+ * This paragraph described that as already true for a release in which it was not: nothing read the
28
+ * manifest, and the crash reached new installs anyway. The allowlist below is a mitigation, not the
29
+ * remedy; the manifest check is the remedy.
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.isDomTag = isDomTag;
33
+ /** Every HTML element name, including the deprecated ones a real codebase still contains. */
34
+ const HTML_TAGS = [
35
+ 'a',
36
+ 'abbr',
37
+ 'address',
38
+ 'area',
39
+ 'article',
40
+ 'aside',
41
+ 'audio',
42
+ 'b',
43
+ 'base',
44
+ 'bdi',
45
+ 'bdo',
46
+ 'big',
47
+ 'blockquote',
48
+ 'body',
49
+ 'br',
50
+ 'button',
51
+ 'canvas',
52
+ 'caption',
53
+ 'center',
54
+ 'cite',
55
+ 'code',
56
+ 'col',
57
+ 'colgroup',
58
+ 'data',
59
+ 'datalist',
60
+ 'dd',
61
+ 'del',
62
+ 'details',
63
+ 'dfn',
64
+ 'dialog',
65
+ 'div',
66
+ 'dl',
67
+ 'dt',
68
+ 'em',
69
+ 'embed',
70
+ 'fieldset',
71
+ 'figcaption',
72
+ 'figure',
73
+ 'font',
74
+ 'footer',
75
+ 'form',
76
+ 'frame',
77
+ 'frameset',
78
+ 'h1',
79
+ 'h2',
80
+ 'h3',
81
+ 'h4',
82
+ 'h5',
83
+ 'h6',
84
+ 'head',
85
+ 'header',
86
+ 'hgroup',
87
+ 'hr',
88
+ 'html',
89
+ 'i',
90
+ 'iframe',
91
+ 'img',
92
+ 'input',
93
+ 'ins',
94
+ 'kbd',
95
+ 'keygen',
96
+ 'label',
97
+ 'legend',
98
+ 'li',
99
+ 'link',
100
+ 'main',
101
+ 'map',
102
+ 'mark',
103
+ 'marquee',
104
+ 'menu',
105
+ 'menuitem',
106
+ 'meta',
107
+ 'meter',
108
+ 'nav',
109
+ 'nobr',
110
+ 'noframes',
111
+ 'noscript',
112
+ 'object',
113
+ 'ol',
114
+ 'optgroup',
115
+ 'option',
116
+ 'output',
117
+ 'p',
118
+ 'param',
119
+ 'picture',
120
+ 'pre',
121
+ 'progress',
122
+ 'q',
123
+ 'rp',
124
+ 'rt',
125
+ 'ruby',
126
+ 's',
127
+ 'samp',
128
+ 'script',
129
+ 'search',
130
+ 'section',
131
+ 'select',
132
+ 'slot',
133
+ 'small',
134
+ 'source',
135
+ 'span',
136
+ 'strike',
137
+ 'strong',
138
+ 'style',
139
+ 'sub',
140
+ 'summary',
141
+ 'sup',
142
+ 'table',
143
+ 'tbody',
144
+ 'td',
145
+ 'template',
146
+ 'textarea',
147
+ 'tfoot',
148
+ 'th',
149
+ 'thead',
150
+ 'time',
151
+ 'title',
152
+ 'tr',
153
+ 'track',
154
+ 'u',
155
+ 'ul',
156
+ 'var',
157
+ 'video',
158
+ 'wbr',
159
+ ];
160
+ /**
161
+ * SVG element names. Included because SVG is DOM — a `data-*` attribute on `<path>` is as valid as
162
+ * one on `<div>`, and an app whose UI is a chart would otherwise lose every source pointer in it.
163
+ *
164
+ * The camelCase members (`clipPath`, `linearGradient`, …) are real JSX intrinsics in React DOM and
165
+ * are matched exactly, so the lookup below cannot be case-folded.
166
+ */
167
+ const SVG_TAGS = [
168
+ 'animate',
169
+ 'animateMotion',
170
+ 'animateTransform',
171
+ 'circle',
172
+ 'clipPath',
173
+ 'defs',
174
+ 'desc',
175
+ 'ellipse',
176
+ 'feBlend',
177
+ 'feColorMatrix',
178
+ 'feComponentTransfer',
179
+ 'feComposite',
180
+ 'feConvolveMatrix',
181
+ 'feDiffuseLighting',
182
+ 'feDisplacementMap',
183
+ 'feDistantLight',
184
+ 'feDropShadow',
185
+ 'feFlood',
186
+ 'feFuncA',
187
+ 'feFuncB',
188
+ 'feFuncG',
189
+ 'feFuncR',
190
+ 'feGaussianBlur',
191
+ 'feImage',
192
+ 'feMerge',
193
+ 'feMergeNode',
194
+ 'feMorphology',
195
+ 'feOffset',
196
+ 'fePointLight',
197
+ 'feSpecularLighting',
198
+ 'feSpotLight',
199
+ 'feTile',
200
+ 'feTurbulence',
201
+ 'filter',
202
+ 'foreignObject',
203
+ 'g',
204
+ 'image',
205
+ 'line',
206
+ 'linearGradient',
207
+ 'marker',
208
+ 'mask',
209
+ 'metadata',
210
+ 'mpath',
211
+ 'path',
212
+ 'pattern',
213
+ 'polygon',
214
+ 'polyline',
215
+ 'radialGradient',
216
+ 'rect',
217
+ 'set',
218
+ 'stop',
219
+ 'svg',
220
+ 'switch',
221
+ 'symbol',
222
+ 'text',
223
+ 'textPath',
224
+ 'tspan',
225
+ 'use',
226
+ 'view',
227
+ ];
228
+ /**
229
+ * MathML, for completeness: also DOM, also takes `data-*`. Small enough that omitting it would be a
230
+ * deliberate blind spot rather than a saving.
231
+ */
232
+ const MATHML_TAGS = [
233
+ 'annotation',
234
+ 'annotation-xml',
235
+ 'maction',
236
+ 'math',
237
+ 'merror',
238
+ 'mfrac',
239
+ 'mi',
240
+ 'mmultiscripts',
241
+ 'mn',
242
+ 'mo',
243
+ 'mover',
244
+ 'mpadded',
245
+ 'mphantom',
246
+ 'mprescripts',
247
+ 'mroot',
248
+ 'mrow',
249
+ 'ms',
250
+ 'mspace',
251
+ 'msqrt',
252
+ 'mstyle',
253
+ 'msub',
254
+ 'msubsup',
255
+ 'msup',
256
+ 'mtable',
257
+ 'mtd',
258
+ 'mtext',
259
+ 'mtr',
260
+ 'munder',
261
+ 'munderover',
262
+ 'semantics',
263
+ ];
264
+ const DOM_TAGS = new Set([...HTML_TAGS, ...SVG_TAGS, ...MATHML_TAGS]);
265
+ /**
266
+ * Whether `tag` names a DOM element that will accept an arbitrary `data-*` attribute.
267
+ *
268
+ * Custom elements are deliberately included: a tag containing a dash is a valid custom element per
269
+ * the HTML spec, it IS a DOM node, and `data-*` on it is ordinary. That rule cannot collide with a
270
+ * custom reconciler's intrinsics, which are bare identifiers (`mesh`, `group`, `box`).
271
+ */
272
+ function isDomTag(tag) {
273
+ return DOM_TAGS.has(tag) || (tag.includes('-') && !tag.startsWith('-'));
274
+ }
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  const node_path_1 = require("node:path");
3
3
  const source_constants_1 = require("@reticlehq/core/source-constants");
4
+ const dom_tags_js_1 = require("./dom-tags.js");
4
5
  const SOURCE_ATTR = source_constants_1.DATA_RETICLE_SOURCE_ATTR;
5
6
  /**
6
7
  * Stamps `data-reticle-source="relativeFile:line:col"` on every JSX host element (lowercase
@@ -22,9 +23,16 @@ function reticleSourcePlugin({ types: t }) {
22
23
  // Host elements only (e.g. <div>, <button>) — skip components (<App />).
23
24
  if (node.name.type !== 'JSXIdentifier')
24
25
  return;
25
- const first = node.name.name[0];
26
+ const tag = node.name.name;
27
+ const first = tag[0];
26
28
  if (first === undefined || first !== first.toLowerCase())
27
29
  return;
30
+ // ...and only host elements that are DOM. React is a reconciler interface, so a lowercase
31
+ // intrinsic can belong to react-three-fiber, react-pdf or ink, whose host instances are not
32
+ // nodes. R3F reads a dashed prop as a pierced property path and throws from the commit
33
+ // phase, which unmounts the entire app to a white screen. See dom-tags.ts.
34
+ if (!(0, dom_tags_js_1.isDomTag)(tag))
35
+ return;
28
36
  const alreadyStamped = node.attributes.some((attr) => 'JSXAttribute' === attr.type &&
29
37
  'JSXIdentifier' === attr.name.type &&
30
38
  attr.name.name === SOURCE_ATTR);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reticlehq/babel-plugin",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "Babel plugin that stamps data-reticle-source=\"file:line:col\" on JSX host elements so @reticlehq/react can map a DOM node to its source (works on React 19, which dropped _debugSource).",
5
5
  "type": "commonjs",
6
6
  "license": "Apache-2.0",
@@ -35,7 +35,7 @@
35
35
  "NOTICE"
36
36
  ],
37
37
  "dependencies": {
38
- "@reticlehq/core": "2.13.0"
38
+ "@reticlehq/core": "2.14.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@babel/core": "^7"