@pinegrow/vite-plugin 3.0.73-alpha.1 → 3.0.73-alpha.11

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.
@@ -8,14 +8,44 @@ export interface AstroRuntimeBridgeOptions {
8
8
  export interface AstroElCacheEntry {
9
9
  el: Element
10
10
  framework: 'astro'
11
+ entryKind: 'source-node' | 'component-boundary' | 'page-root'
12
+ treeScopes: {
13
+ pageTree: boolean
14
+ appTree: boolean
15
+ }
11
16
  isAstro: true
17
+ isAstroSourceDomEntry: boolean
18
+ isAstroComponent: boolean
19
+ isAppTreeComponent: boolean
20
+ isAstroPageRoot?: boolean
12
21
  sourceId: string
13
- pgId: string
14
- key: string
22
+ pgId: string | null
23
+ key: string | null
15
24
  localFile: string | null
16
25
  sourceFile: string | null
17
26
  nodeType: string | null
18
27
  name: string
28
+ source: {
29
+ id: string
30
+ file: string | null
31
+ range: {
32
+ start: number | null
33
+ end: number | null
34
+ status: string
35
+ }
36
+ nodeType: string | null
37
+ name: string
38
+ }
39
+ component: {
40
+ file: string
41
+ name: string
42
+ kind: string
43
+ } | null
44
+ owners: Array<{
45
+ kind: string
46
+ file: string
47
+ name?: string
48
+ }>
19
49
  sourceRange: {
20
50
  start: number | null
21
51
  end: number | null
@@ -32,11 +62,53 @@ export interface AstroElCacheEntry {
32
62
  isIsland: false
33
63
  rootEl: null
34
64
  firstEl: Element
35
- lastEl: Element
36
- instance: null
65
+ lastEl: Element | null
66
+ instance: {
67
+ uid: string
68
+ isMounted: boolean
69
+ isUnmounted: boolean
70
+ type: {
71
+ __name?: string
72
+ __file?: string | null
73
+ }
74
+ props: Record<string, unknown>
75
+ attrs: Record<string, unknown>
76
+ slots: Record<string, unknown>
77
+ subTree: {
78
+ el: Element
79
+ }
80
+ appContext: unknown
81
+ }
37
82
  vnode: null
38
83
  }
39
84
 
85
+ export type AstroComponentEntry = AstroElCacheEntry & {
86
+ entryKind: 'component-boundary' | 'page-root'
87
+ treeScopes: {
88
+ pageTree: false
89
+ appTree: true
90
+ }
91
+ isAstroSourceDomEntry: false
92
+ isAstroComponent: true
93
+ isAppTreeComponent: true
94
+ pgId: null
95
+ key: null
96
+ localFile: string
97
+ sourceFile: string
98
+ nodeType: 'component-file'
99
+ component: {
100
+ file: string
101
+ name: string
102
+ kind: string
103
+ }
104
+ }
105
+
106
+ export function createAstroComponentEntries(input: {
107
+ entries: AstroElCacheEntry[]
108
+ manifest: unknown
109
+ root?: Document | Element
110
+ }): AstroComponentEntry[]
111
+
40
112
  export function createAstroElCacheEntry(input: {
41
113
  element: Element
42
114
  manifest: unknown
@@ -1,5 +1,11 @@
1
- const defaultMarkerAttribute = 'data-pg-source-id'
1
+ const defaultMarkerAttribute = 'data-pg-id'
2
+ const pinegrowMarkerAttribute = 'data-pg-id'
2
3
  const astroFrameworkKey = 'astro'
4
+ const astroSourceIdPrefix = 'pgastro-'
5
+ const elementNodeType = 1
6
+ const commentNodeType = 8
7
+ const documentPositionPreceding = 2
8
+ const documentPositionFollowing = 4
3
9
 
4
10
  const createFallbackComputed = getter => ({
5
11
  get value() {
@@ -25,13 +31,20 @@ const createFallbackWatch = (source, callback, options = {}) => {
25
31
  return () => {}
26
32
  }
27
33
 
28
- const createAstroSyntheticInstance = (manifest, node) => ({
34
+ const createAstroSyntheticInstance = (node, element) => ({
29
35
  uid: `astro:${node.id}`,
30
36
  isMounted: true,
31
37
  isUnmounted: false,
38
+ parent: null,
32
39
  type: {
33
40
  __name: node.name || 'AstroElement',
34
- __file: manifest.sourceFile || null,
41
+ __file: null,
42
+ },
43
+ props: {},
44
+ attrs: {},
45
+ slots: {},
46
+ subTree: {
47
+ el: element,
35
48
  },
36
49
  appContext: null,
37
50
  })
@@ -51,6 +64,13 @@ const isMapLike = value =>
51
64
  typeof value.delete === 'function' &&
52
65
  typeof value.entries === 'function'
53
66
 
67
+ const createReactiveMap = (pinegrow, value) => {
68
+ const map = isMapLike(value) ? value : new Map()
69
+ const reactiveFromContext = pinegrow?.reactiveFromContext
70
+
71
+ return typeof reactiveFromContext === 'function' ? reactiveFromContext(map) : map
72
+ }
73
+
54
74
  const getMarkerAttribute = (manifest, options = {}) =>
55
75
  options.markerAttribute || manifest?.marker?.attribute || defaultMarkerAttribute
56
76
 
@@ -58,6 +78,83 @@ const getManifestSourceKey = manifest => manifest?.sourceFile || manifest?.id ||
58
78
 
59
79
  const normalizeElCacheEntries = entries => Array.isArray(entries) ? entries : []
60
80
 
81
+ const getEntryFirstElement = entry => entry?.firstEl || entry?.el || null
82
+
83
+ const getEntryLastElement = entry => entry?.lastEl || entry?.el || null
84
+
85
+ const sortEntriesByDocumentPosition = entries =>
86
+ [...entries].sort((a, b) => {
87
+ const aEl = getEntryFirstElement(a)
88
+ const bEl = getEntryFirstElement(b)
89
+
90
+ if (!aEl || !bEl || aEl === bEl || typeof aEl.compareDocumentPosition !== 'function') {
91
+ return 0
92
+ }
93
+
94
+ const position = aEl.compareDocumentPosition(bEl)
95
+
96
+ if (position & documentPositionPreceding) {
97
+ return 1
98
+ }
99
+
100
+ if (position & documentPositionFollowing) {
101
+ return -1
102
+ }
103
+
104
+ return 0
105
+ })
106
+
107
+ const normalizePathLike = value => `${value || ''}`.replace(/\\/g, '/')
108
+
109
+ const getBasename = value => {
110
+ if (!value) {
111
+ return ''
112
+ }
113
+
114
+ return normalizePathLike(value).split('/').pop() || ''
115
+ }
116
+
117
+ const isAstroPageSourceFile = sourceFile =>
118
+ /(^|\/)src\/pages\/.+\.astro$/i.test(normalizePathLike(sourceFile))
119
+
120
+ const getRootElement = root => {
121
+ if (!root) {
122
+ return null
123
+ }
124
+
125
+ if (root.documentElement) {
126
+ return root.documentElement
127
+ }
128
+
129
+ if (root.body) {
130
+ return root.body
131
+ }
132
+
133
+ if (root.firstElementChild) {
134
+ return root.firstElementChild
135
+ }
136
+
137
+ return root.nodeType === 1 ? root : null
138
+ }
139
+
140
+ const createAstroComponentSyntheticInstance = (manifest, componentId, element) => ({
141
+ uid: componentId,
142
+ isMounted: true,
143
+ isUnmounted: false,
144
+ parent: null,
145
+ type: {
146
+ __name: getBasename(manifest?.sourceFile) || 'AstroComponent',
147
+ __file: manifest?.sourceFile || null,
148
+ },
149
+ props: {},
150
+ attrs: {},
151
+ slots: {},
152
+ subTree: {
153
+ el: element,
154
+ },
155
+ appContext: null,
156
+ })
157
+
61
158
  const createNodeIndex = manifest => {
62
159
  const index = new Map()
63
160
 
@@ -78,6 +175,106 @@ const queryMarkerElements = (root, markerAttribute) => {
78
175
  return Array.from(root.querySelectorAll(`[${markerAttribute}]`))
79
176
  }
80
177
 
178
+ const getChildNodes = node => Array.from(node?.childNodes || [])
179
+
180
+ const walkDomNodes = (node, visitor) => {
181
+ if (!node) {
182
+ return
183
+ }
184
+
185
+ visitor(node)
186
+ getChildNodes(node).forEach(child => walkDomNodes(child, visitor))
187
+ }
188
+
189
+ const parseAstroBoundaryComment = node => {
190
+ if (node?.nodeType !== commentNodeType) {
191
+ return null
192
+ }
193
+
194
+ const match = `${node.nodeValue || ''}`.trim().match(/^pgastro:(start|end):(.+)$/)
195
+
196
+ return match
197
+ ? {
198
+ boundary: match[1],
199
+ sourceId: match[2],
200
+ }
201
+ : null
202
+ }
203
+
204
+ const getBoundaryElements = (startComment, endComment) => {
205
+ if (!startComment?.parentNode || startComment.parentNode !== endComment?.parentNode) {
206
+ return {
207
+ firstEl: null,
208
+ lastEl: null,
209
+ }
210
+ }
211
+
212
+ let firstEl = null
213
+ let lastEl = null
214
+ let node = startComment.nextSibling
215
+
216
+ while (node && node !== endComment) {
217
+ if (node.nodeType === elementNodeType) {
218
+ if (!firstEl) {
219
+ firstEl = node
220
+ }
221
+ lastEl = node
222
+ }
223
+
224
+ node = node.nextSibling
225
+ }
226
+
227
+ return {
228
+ firstEl,
229
+ lastEl,
230
+ }
231
+ }
232
+
233
+ const queryBoundaryMarkerRanges = (root, nodeIndex) => {
234
+ const startsById = new Map()
235
+ const ranges = []
236
+
237
+ walkDomNodes(root, node => {
238
+ const marker = parseAstroBoundaryComment(node)
239
+
240
+ if (!marker || !nodeIndex.has(marker.sourceId)) {
241
+ return
242
+ }
243
+
244
+ if (marker.boundary === 'start') {
245
+ if (!startsById.has(marker.sourceId)) {
246
+ startsById.set(marker.sourceId, [])
247
+ }
248
+
249
+ startsById.get(marker.sourceId).push(node)
250
+ return
251
+ }
252
+
253
+ const starts = startsById.get(marker.sourceId)
254
+ const startComment = starts?.pop()
255
+
256
+ if (!startComment) {
257
+ return
258
+ }
259
+
260
+ const { firstEl, lastEl } = getBoundaryElements(startComment, node)
261
+
262
+ if (!firstEl) {
263
+ return
264
+ }
265
+
266
+ ranges.push({
267
+ sourceId: marker.sourceId,
268
+ firstEl,
269
+ lastEl: lastEl || firstEl,
270
+ startComment,
271
+ endComment: node,
272
+ })
273
+ })
274
+
275
+ return ranges
276
+ }
277
+
81
278
  const getElementMarkerId = (element, markerAttribute) => {
82
279
  if (!element || typeof element.getAttribute !== 'function') {
83
280
  return null
@@ -86,7 +283,48 @@ const getElementMarkerId = (element, markerAttribute) => {
86
283
  return element.getAttribute(markerAttribute)
87
284
  }
88
285
 
89
- const createAstroElCacheEntry = ({ element, manifest, node }) => {
286
+ const canOverwritePgId = currentValue =>
287
+ !currentValue || currentValue.startsWith(astroSourceIdPrefix)
288
+
289
+ const mirrorAstroSourceIdToPgId = (element, sourceId, options = {}) => {
290
+ if (
291
+ options.mirrorPgId === false ||
292
+ !sourceId ||
293
+ !element ||
294
+ typeof element.getAttribute !== 'function' ||
295
+ typeof element.setAttribute !== 'function'
296
+ ) {
297
+ return false
298
+ }
299
+
300
+ const currentPgId = element.getAttribute(pinegrowMarkerAttribute)
301
+
302
+ if (currentPgId === sourceId) {
303
+ return true
304
+ }
305
+
306
+ if (!canOverwritePgId(currentPgId)) {
307
+ return false
308
+ }
309
+
310
+ element.setAttribute(pinegrowMarkerAttribute, sourceId)
311
+
312
+ return true
313
+ }
314
+
315
+ const createAstroElCacheEntry = ({
316
+ boundary = null,
317
+ element,
318
+ firstElement = null,
319
+ lastElement = null,
320
+ manifest,
321
+ markerAttribute = defaultMarkerAttribute,
322
+ node,
323
+ mirroredPgId = false,
324
+ }) => {
325
+ const firstEl = firstElement || element
326
+ const lastEl = lastElement || firstEl
327
+ const isBoundaryRange = Boolean(boundary)
90
328
  const sourceRange = {
91
329
  start: node.sourceStart ?? null,
92
330
  end: node.sourceEnd ?? null,
@@ -94,34 +332,146 @@ const createAstroElCacheEntry = ({ element, manifest, node }) => {
94
332
  }
95
333
 
96
334
  return {
97
- el: element,
335
+ el: firstEl,
98
336
  framework: astroFrameworkKey,
337
+ entryKind: 'source-node',
338
+ treeScopes: {
339
+ pageTree: true,
340
+ appTree: false,
341
+ },
99
342
  isAstro: true,
343
+ isAstroSourceDomEntry: true,
344
+ isAstroComponent: false,
345
+ isAppTreeComponent: false,
100
346
  sourceId: node.id,
101
347
  pgId: node.id,
102
348
  key: node.id,
103
- localFile: manifest.sourceFile || null,
349
+ localFile: null,
104
350
  sourceFile: manifest.sourceFile || null,
105
351
  nodeType: node.type || null,
106
352
  name: node.name || '',
353
+ source: {
354
+ id: node.id,
355
+ file: manifest.sourceFile || null,
356
+ range: sourceRange,
357
+ nodeType: node.type || null,
358
+ name: node.name || '',
359
+ },
360
+ component: null,
361
+ owners: [],
107
362
  sourceRange,
108
363
  sourceStart: sourceRange.start,
109
364
  sourceEnd: sourceRange.end,
110
365
  sourceRangeStatus: sourceRange.status,
111
- marker: node.marker || null,
366
+ marker: {
367
+ ...(node.marker || {}),
368
+ liveAttribute: markerAttribute,
369
+ pinegrowAttribute: pinegrowMarkerAttribute,
370
+ mirroredPgId,
371
+ boundary,
372
+ },
112
373
  editability: node.editability || null,
113
374
  attributes: node.attributes || [],
375
+ isFragment: isBoundaryRange,
376
+ isRootFragment: false,
377
+ isIsland: false,
378
+ rootEl: null,
379
+ firstEl,
380
+ lastEl,
381
+ instance: createAstroSyntheticInstance(node, firstEl),
382
+ vnode: null,
383
+ }
384
+ }
385
+
386
+ const createAstroComponentEntry = ({ entries, manifest, root }) => {
387
+ const sourceFile = manifest?.sourceFile
388
+ const rootElement = getRootElement(root)
389
+ const isPageFile = isAstroPageSourceFile(sourceFile)
390
+ const isPageRoot = isPageFile && rootElement
391
+
392
+ if (!sourceFile || (!entries.length && !isPageRoot)) {
393
+ return null
394
+ }
395
+
396
+ const orderedEntries = sortEntriesByDocumentPosition(entries)
397
+ const firstEntry = orderedEntries[0]
398
+ const lastEntry = orderedEntries[orderedEntries.length - 1]
399
+ const componentId = `astro-component:${sourceFile}`
400
+ const firstEl = isPageRoot ? rootElement : getEntryFirstElement(firstEntry)
401
+ const lastEl = isPageRoot ? null : getEntryLastElement(lastEntry)
402
+ const componentName = getBasename(sourceFile)
403
+ const entryKind = isPageRoot ? 'page-root' : 'component-boundary'
404
+
405
+ return {
406
+ el: firstEl,
407
+ framework: astroFrameworkKey,
408
+ entryKind,
409
+ treeScopes: {
410
+ pageTree: false,
411
+ appTree: true,
412
+ },
413
+ isAstro: true,
414
+ isAstroSourceDomEntry: false,
415
+ isAstroComponent: true,
416
+ isAppTreeComponent: true,
417
+ isAstroPageRoot: Boolean(isPageRoot),
418
+ sourceId: componentId,
419
+ pgId: null,
420
+ key: null,
421
+ localFile: sourceFile,
422
+ sourceFile,
423
+ nodeType: 'component-file',
424
+ name: componentName,
425
+ source: {
426
+ id: componentId,
427
+ file: sourceFile,
428
+ range: {
429
+ start: null,
430
+ end: null,
431
+ status: 'component-file',
432
+ },
433
+ nodeType: 'component-file',
434
+ name: componentName,
435
+ },
436
+ component: {
437
+ file: sourceFile,
438
+ name: componentName,
439
+ kind: isPageFile ? 'page' : 'component',
440
+ },
441
+ owners: [],
442
+ sourceRange: {
443
+ start: null,
444
+ end: null,
445
+ status: 'component-file',
446
+ },
447
+ sourceStart: null,
448
+ sourceEnd: null,
449
+ sourceRangeStatus: 'component-file',
450
+ marker: null,
451
+ editability: {
452
+ mode: 'inspect',
453
+ operations: ['select', 'open-source-file'],
454
+ reasons: ['astro-component-file'],
455
+ },
456
+ attributes: [],
457
+ state: Array.isArray(manifest.state) ? manifest.state : [],
114
458
  isFragment: false,
115
459
  isRootFragment: false,
116
460
  isIsland: false,
117
461
  rootEl: null,
118
- firstEl: element,
119
- lastEl: element,
120
- instance: createAstroSyntheticInstance(manifest, node),
462
+ firstEl,
463
+ lastEl,
464
+ instance: createAstroComponentSyntheticInstance(manifest, componentId, firstEl),
121
465
  vnode: null,
122
466
  }
123
467
  }
124
468
 
469
+ const createAstroComponentEntries = ({ entries, manifest, root }) => {
470
+ const componentEntry = createAstroComponentEntry({ entries, manifest, root })
471
+
472
+ return componentEntry ? [componentEntry] : []
473
+ }
474
+
125
475
  const scanAstroSourceDomMarkers = (root, manifest, options = {}) => {
126
476
  const markerAttribute = getMarkerAttribute(manifest, options)
127
477
  const entries = []
@@ -132,6 +482,7 @@ const scanAstroSourceDomMarkers = (root, manifest, options = {}) => {
132
482
  status: 'unavailable',
133
483
  markerAttribute,
134
484
  entries,
485
+ componentEntries: [],
135
486
  missing,
136
487
  reason: manifest?.reason || 'Astro source manifest is unavailable.',
137
488
  }
@@ -142,6 +493,7 @@ const scanAstroSourceDomMarkers = (root, manifest, options = {}) => {
142
493
  status: 'unavailable',
143
494
  markerAttribute,
144
495
  entries,
496
+ componentEntries: [],
145
497
  missing,
146
498
  reason: 'A DOM root with querySelectorAll() is required.',
147
499
  }
@@ -154,17 +506,49 @@ const scanAstroSourceDomMarkers = (root, manifest, options = {}) => {
154
506
  const node = nodeIndex.get(sourceId)
155
507
 
156
508
  if (!node) {
157
- missing.push({ element, sourceId })
509
+ if (options.reportMissingMarkers) {
510
+ missing.push({ element, sourceId })
511
+ }
158
512
  return
159
513
  }
160
514
 
161
- entries.push(createAstroElCacheEntry({ element, manifest, node }))
515
+ const mirroredPgId = markerAttribute === pinegrowMarkerAttribute
516
+ ? false
517
+ : mirrorAstroSourceIdToPgId(element, sourceId, options)
518
+
519
+ entries.push(createAstroElCacheEntry({
520
+ element,
521
+ manifest,
522
+ markerAttribute,
523
+ node,
524
+ mirroredPgId,
525
+ }))
526
+ })
527
+
528
+ queryBoundaryMarkerRanges(root, nodeIndex).forEach(boundary => {
529
+ const node = nodeIndex.get(boundary.sourceId)
530
+
531
+ entries.push(createAstroElCacheEntry({
532
+ boundary: {
533
+ strategy: 'boundary-comment',
534
+ startComment: boundary.startComment,
535
+ endComment: boundary.endComment,
536
+ },
537
+ element: boundary.firstEl,
538
+ firstElement: boundary.firstEl,
539
+ lastElement: boundary.lastEl,
540
+ manifest,
541
+ markerAttribute,
542
+ node,
543
+ mirroredPgId: false,
544
+ }))
162
545
  })
163
546
 
164
547
  return {
165
548
  status: 'ready',
166
549
  markerAttribute,
167
550
  entries,
551
+ componentEntries: createAstroComponentEntries({ entries, manifest, root }),
168
552
  missing,
169
553
  }
170
554
  }
@@ -194,17 +578,16 @@ const ensurePinegrowAstroBridge = (win = getDefaultWindow()) => {
194
578
  win.pinegrow.watchFromContext = createFallbackWatch
195
579
  }
196
580
 
197
- if (!isMapLike(win.pinegrow.elCache)) {
198
- win.pinegrow.elCache = new Map()
199
- }
581
+ win.pinegrow.elCache = createReactiveMap(win.pinegrow, win.pinegrow.elCache)
200
582
 
201
583
  if (!win.pinegrow.astro) {
202
584
  win.pinegrow.astro = {}
203
585
  }
204
586
 
205
- if (!isMapLike(win.pinegrow.astro.sourceManifests)) {
206
- win.pinegrow.astro.sourceManifests = new Map()
207
- }
587
+ win.pinegrow.astro.sourceManifests = createReactiveMap(
588
+ win.pinegrow,
589
+ win.pinegrow.astro.sourceManifests
590
+ )
208
591
 
209
592
  return win.pinegrow
210
593
  }
@@ -283,7 +666,9 @@ const refreshAstroElCache = (manifest, options = {}) => {
283
666
  : removeAstroEntriesForManifest(pinegrow.elCache, manifest)
284
667
  const updatedElements = new Set(removal.elements)
285
668
 
286
- scan.entries.forEach(entry => {
669
+ const cacheEntries = [...scan.entries, ...(scan.componentEntries || [])]
670
+
671
+ cacheEntries.forEach(entry => {
287
672
  upsertAstroEntry(pinegrow.elCache, entry)
288
673
  updatedElements.add(entry.el)
289
674
  })
@@ -302,7 +687,7 @@ const refreshAstroElCache = (manifest, options = {}) => {
302
687
 
303
688
  return {
304
689
  ...scan,
305
- cached: scan.entries.length,
690
+ cached: cacheEntries.length,
306
691
  removed: removal.removed,
307
692
  }
308
693
  }
@@ -318,6 +703,7 @@ const createAstroRuntimeBridge = (manifest, options = {}) => ({
318
703
  })
319
704
 
320
705
  export {
706
+ createAstroComponentEntries,
321
707
  createAstroElCacheEntry,
322
708
  createAstroRuntimeBridge,
323
709
  ensurePinegrowAstroBridge,