jq79 0.4.12 → 0.4.13
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.
- package/README.md +1 -1
- package/dev/vite.ts +47 -0
- package/dist/jq79.cjs +13 -13
- package/dist/jq79.cjs.map +1 -1
- package/dist/jq79.d.ts +6 -0
- package/dist/jq79.global.js +13 -13
- package/dist/jq79.global.js.map +1 -1
- package/dist/jq79.js +13 -13
- package/dist/jq79.js.map +1 -1
- package/dist/vite.cjs +39 -0
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.js +39 -0
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/jq79.ts +250 -13
package/src/jq79.ts
CHANGED
|
@@ -216,6 +216,12 @@ const findComponentKey = (scope: Record<string, any>, tag: string): string | nul
|
|
|
216
216
|
return null
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
// how deep a component may nest inside itself before the runtime calls it a
|
|
220
|
+
// cycle. Deeper than any real tree, shallower than the JS stack: a truncated
|
|
221
|
+
// render with an error on the console beats a stack overflow with none
|
|
222
|
+
const MAX_NESTING_DEPTH = 200
|
|
223
|
+
let nestingDepth = 0
|
|
224
|
+
|
|
219
225
|
// <MyComponent :user :title="'str'"></MyComponent> - renders a child
|
|
220
226
|
// component instance at this position. Props: `:name="expr"` evaluates expr
|
|
221
227
|
// in the parent scope (`:name` alone is shorthand for `:name="name"`), plain
|
|
@@ -329,9 +335,34 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
329
335
|
let currentDef: Component79 | null = null
|
|
330
336
|
let childFx: EffectScope | null = null
|
|
331
337
|
|
|
338
|
+
// a usage site that resolves to no component renders nothing, which is
|
|
339
|
+
// deliberate - `undefined` while an `await import(...)` is in flight has to
|
|
340
|
+
// wait quietly, and the child appears when it lands. Two cases can never
|
|
341
|
+
// resolve, though, and both are wiring mistakes worth naming: a value that
|
|
342
|
+
// isn't a component (and so will never become one by waiting), and a name
|
|
343
|
+
// the component declared as a prop that the parent passed nothing for. Once
|
|
344
|
+
// each, per usage site: an effect re-runs
|
|
345
|
+
const reported = new Set<string>()
|
|
346
|
+
const reportUnresolved = (value: any) => {
|
|
347
|
+
if (value === undefined || value === null) {
|
|
348
|
+
const unfilled: Set<string> | undefined = (scope as any)[UNFILLED_PROPS]
|
|
349
|
+
if (!unfilled?.has(key) || reported.has("unfilled")) return
|
|
350
|
+
reported.add("unfilled")
|
|
351
|
+
console.error(
|
|
352
|
+
`jq79: <${node.tag}> is declared as a prop and the parent passed nothing - nothing renders here. ` +
|
|
353
|
+
`Pass it (:${key}="…"), or drop it from the signature to use the one declared in this file.`
|
|
354
|
+
)
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
if (reported.has("type")) return
|
|
358
|
+
reported.add("type")
|
|
359
|
+
console.error(`jq79: <${node.tag}> is ${typeof value}, not a component - nothing renders here`)
|
|
360
|
+
}
|
|
361
|
+
|
|
332
362
|
fx.effect(() => {
|
|
333
363
|
const value = evalExpr(key, scope)
|
|
334
364
|
const nextDef = value instanceof Component79 ? value : null
|
|
365
|
+
if (!nextDef) reportUnresolved(value)
|
|
335
366
|
if (nextDef === currentDef) return
|
|
336
367
|
|
|
337
368
|
childFx?.dispose()
|
|
@@ -349,6 +380,11 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
349
380
|
styles: nextDef.styles,
|
|
350
381
|
modules: nextDef.modules,
|
|
351
382
|
filename: nextDef.filename,
|
|
383
|
+
// its file's other components, and which of them it is: without the
|
|
384
|
+
// first a child rendered here loses the siblings its definition could
|
|
385
|
+
// see, and without the second hot reload can't tell it what it is
|
|
386
|
+
siblings: nextDef.siblings,
|
|
387
|
+
name: nextDef.name,
|
|
352
388
|
})
|
|
353
389
|
// the writeback half of :model - one event, one contract. The name is
|
|
354
390
|
// normalized like the attribute was (kebab->camel; absent means default),
|
|
@@ -395,7 +431,24 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
395
431
|
// shadow-rendered child keeps its <style> elements inline, next to the DOM
|
|
396
432
|
// they style, and the parent's shadow root is what scopes both
|
|
397
433
|
const holder = document.createDocumentFragment()
|
|
398
|
-
|
|
434
|
+
// rendering a child happens on this same stack, so a component that
|
|
435
|
+
// renders itself recurses as deep as its data does - and a cycle in that
|
|
436
|
+
// data would recurse until the JS stack gave out, ~900 identical frames
|
|
437
|
+
// naming nothing. Cut and named instead, exactly like the effect runner
|
|
438
|
+
// cuts an effect that wakes itself
|
|
439
|
+
if (nestingDepth >= MAX_NESTING_DEPTH) {
|
|
440
|
+
console.error(
|
|
441
|
+
`jq79: <${node.tag}> is ${MAX_NESTING_DEPTH} levels deep inside itself; giving up here. ` +
|
|
442
|
+
"A component that renders itself stops when its data stops - is there a cycle in it?"
|
|
443
|
+
)
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
nestingDepth++
|
|
447
|
+
try {
|
|
448
|
+
;(shadow ? instance.renderShadow(seed) : instance.render(seed)).mount(holder)
|
|
449
|
+
} finally {
|
|
450
|
+
nestingDepth--
|
|
451
|
+
}
|
|
399
452
|
endAnchor.parentNode!.insertBefore(holder, endAnchor)
|
|
400
453
|
|
|
401
454
|
const syncFx = createEffectScope(scope)
|
|
@@ -889,6 +942,15 @@ type ComponentParts = {
|
|
|
889
942
|
// where this component came from (a URL for fetch(), a path for the vite
|
|
890
943
|
// plugin). Names the setup scripts in devtools - see scriptSourceUrl
|
|
891
944
|
filename?: string
|
|
945
|
+
// the components the file's <template name="..."> blocks declared, by name.
|
|
946
|
+
// Every component parsed out of one file holds this same map - itself
|
|
947
|
+
// included - which is what makes a sibling usable without an import, and
|
|
948
|
+
// what lets a <template name="TreeNode"> render a <TreeNode>
|
|
949
|
+
siblings?: Record<string, Component79>
|
|
950
|
+
// which of the file's components this is: a template's name, or undefined
|
|
951
|
+
// for the file's own. The file is the hot-reload unit, so a reparse hands
|
|
952
|
+
// each live instance the parts belonging to the component it is
|
|
953
|
+
name?: string
|
|
892
954
|
}
|
|
893
955
|
|
|
894
956
|
const VOID_ELEMENTS = new Set([
|
|
@@ -1015,9 +1077,16 @@ const scopeCss = (css: string, scope: string): string => {
|
|
|
1015
1077
|
return Array.from(sheet.cssRules).map(rule => rule.cssText).join("\n")
|
|
1016
1078
|
}
|
|
1017
1079
|
|
|
1080
|
+
// a component name has to be PascalCase to be usable: findComponentKey only
|
|
1081
|
+
// ever considers capitalized scope keys, so a lowercase name would declare a
|
|
1082
|
+
// component no tag could reference. It is also what keeps the named exports
|
|
1083
|
+
// from colliding with a definition's own fields, which are all lowercase
|
|
1084
|
+
const COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/
|
|
1085
|
+
|
|
1018
1086
|
// converts a string of HTML into an AST representation of the component:
|
|
1019
1087
|
// - template: the non-script/style top-level elements, as TemplateNodes
|
|
1020
1088
|
// - scripts/styles: { attrs, content } blocks in source order
|
|
1089
|
+
// - siblings: the components its top-level <template name="..."> declared
|
|
1021
1090
|
const parseComponentString = (component: string): ComponentParts => {
|
|
1022
1091
|
// example
|
|
1023
1092
|
// <script :setup="{ fname, lname }">
|
|
@@ -1043,11 +1112,64 @@ const parseComponentString = (component: string): ComponentParts => {
|
|
|
1043
1112
|
const parsedDOM = new DOMParser().parseFromString(`<template>${prepared}</template>`, "text/html")
|
|
1044
1113
|
const root = parsedDOM.querySelector("template") as HTMLTemplateElement
|
|
1045
1114
|
|
|
1115
|
+
// a top-level <template> declares another component of this file; everything
|
|
1116
|
+
// else is this one's own
|
|
1117
|
+
const own: Element[] = []
|
|
1118
|
+
const declarations: HTMLTemplateElement[] = []
|
|
1119
|
+
Array.from(root.content.children).forEach(el => {
|
|
1120
|
+
if (el.tagName === "TEMPLATE") declarations.push(el as HTMLTemplateElement)
|
|
1121
|
+
else own.push(el)
|
|
1122
|
+
})
|
|
1123
|
+
|
|
1124
|
+
// the file's own component hashes the whole file: every component in it
|
|
1125
|
+
// re-renders on any edit anyway (the file is the hot-reload unit), so a
|
|
1126
|
+
// stamp that changes when a sibling is edited costs nothing, and scopeHash
|
|
1127
|
+
// gets to keep hashing the source it was handed
|
|
1128
|
+
const parts = componentPartsFrom(own, component)
|
|
1129
|
+
|
|
1130
|
+
// one map, shared by reference: it is filled below, after each definition
|
|
1131
|
+
// has already been handed it, so every component of the file sees all the
|
|
1132
|
+
// others *and itself* - which is what makes a recursive component possible
|
|
1133
|
+
const siblings: Record<string, Component79> = {}
|
|
1134
|
+
declarations.forEach(el => {
|
|
1135
|
+
const name = el.getAttribute("name")
|
|
1136
|
+
// ignored rather than fatal, like every other malformed thing here: a bad
|
|
1137
|
+
// save mid-typing must not take the page down, least of all under HMR
|
|
1138
|
+
if (name === null) {
|
|
1139
|
+
console.warn("jq79: a top-level <template> without a name declares nothing and was ignored")
|
|
1140
|
+
return
|
|
1141
|
+
}
|
|
1142
|
+
if (!COMPONENT_NAME_RE.test(name)) {
|
|
1143
|
+
console.warn(
|
|
1144
|
+
`jq79: <template name="${name}"> was ignored - a component name has to be PascalCase, ` +
|
|
1145
|
+
"or no tag could ever reference it (only capitalized names resolve as components)"
|
|
1146
|
+
)
|
|
1147
|
+
return
|
|
1148
|
+
}
|
|
1149
|
+
if (name in siblings) {
|
|
1150
|
+
console.warn(`jq79: two <template name="${name}"> in one file; the second was ignored`)
|
|
1151
|
+
return
|
|
1152
|
+
}
|
|
1153
|
+
// its own source is its own scope: a named template is a shadow root
|
|
1154
|
+
// inside a shadow root, so the file's scoped rules stop at its boundary
|
|
1155
|
+
// and its own stop there too
|
|
1156
|
+
siblings[name] = new Component79({ ...componentPartsFrom(Array.from(el.content.children), el.innerHTML), siblings, name })
|
|
1157
|
+
})
|
|
1158
|
+
if (Object.keys(siblings).length) parts.siblings = siblings
|
|
1159
|
+
|
|
1160
|
+
return parts
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// the script/style/markup split of one component's top-level elements, with
|
|
1164
|
+
// <style scoped> resolved against the source those elements came from - the
|
|
1165
|
+
// whole file for its own component, a <template>'s contents for a named one,
|
|
1166
|
+
// so the two get different stamps and neither can style the other
|
|
1167
|
+
const componentPartsFrom = (elements: Element[], hashSource: string): ComponentParts => {
|
|
1046
1168
|
const scripts: TagBlock[] = []
|
|
1047
1169
|
const styles: TagBlock[] = []
|
|
1048
1170
|
const template: TemplateNode[] = []
|
|
1049
1171
|
|
|
1050
|
-
|
|
1172
|
+
elements.forEach(el => {
|
|
1051
1173
|
const block: TagBlock = { attrs: elementAttrs(el), content: el.textContent ?? "" }
|
|
1052
1174
|
|
|
1053
1175
|
if (el.tagName === "SCRIPT") scripts.push(block)
|
|
@@ -1074,7 +1196,7 @@ const parseComponentString = (component: string): ComponentParts => {
|
|
|
1074
1196
|
// in something that isn't CSS yet would only garble what devtools shows
|
|
1075
1197
|
const isScoped = (style: TagBlock) => "scoped" in style.attrs && !("lang" in style.attrs)
|
|
1076
1198
|
if (styles.some(isScoped)) {
|
|
1077
|
-
const scope = scopeHash(
|
|
1199
|
+
const scope = scopeHash(hashSource)
|
|
1078
1200
|
stampScope(template, scope)
|
|
1079
1201
|
styles.forEach(style => {
|
|
1080
1202
|
if (isScoped(style)) style.scoped = scopeCss(style.content, scope)
|
|
@@ -1193,6 +1315,50 @@ const declareProps = (store: Record<string, any>, props: PropDecl[] | null) => {
|
|
|
1193
1315
|
})
|
|
1194
1316
|
}
|
|
1195
1317
|
|
|
1318
|
+
// every prop name a component's scripts declare, across both script modes.
|
|
1319
|
+
// Read before the store exists, because what a component declares decides
|
|
1320
|
+
// which of its file's sibling components it can still see: declaring a name
|
|
1321
|
+
// says it comes from the parent, so the file's own definition of that name is
|
|
1322
|
+
// deliberately not in this component's scope
|
|
1323
|
+
const declaredPropNames = (scripts: TagBlock[]): Set<string> => {
|
|
1324
|
+
const names = new Set<string>()
|
|
1325
|
+
scripts.forEach(script => {
|
|
1326
|
+
const declarations = parseFactoryProps(script.content) ?? parsePropsPattern(script.attrs[":setup"])
|
|
1327
|
+
declarations?.forEach(({ name }) => names.add(name))
|
|
1328
|
+
})
|
|
1329
|
+
return names
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// the sibling components this one resolves by name, or null when there are
|
|
1333
|
+
// none left to resolve. They go on the store's *prototype* rather than in it:
|
|
1334
|
+
// the component-key scan walks the chain, so <Row> resolves; they stay out of
|
|
1335
|
+
// the data, so Object.keys, snapshots and spreads never see them; and an own
|
|
1336
|
+
// key shadows a prototype one, so a prop the parent did pass wins for free
|
|
1337
|
+
const siblingsInScope = (
|
|
1338
|
+
siblings: Record<string, Component79> | undefined,
|
|
1339
|
+
declared: Set<string>
|
|
1340
|
+
): Record<string, Component79> | null => {
|
|
1341
|
+
if (!siblings) return null
|
|
1342
|
+
// null-prototype, for the same reason storeApi is: `key in scope` must not
|
|
1343
|
+
// start answering true for toString, constructor and the rest
|
|
1344
|
+
const inScope: Record<string, Component79> = Object.create(null)
|
|
1345
|
+
let any = false
|
|
1346
|
+
Object.entries(siblings).forEach(([name, component]) => {
|
|
1347
|
+
if (declared.has(name)) return
|
|
1348
|
+
inScope[name] = component
|
|
1349
|
+
any = true
|
|
1350
|
+
})
|
|
1351
|
+
return any ? inScope : null
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// names a component declared as props and the parent passed nothing for. Such
|
|
1355
|
+
// a name can never become a component later - there is no binding on the tag
|
|
1356
|
+
// to update it - so a <Tag> reading one is a wiring mistake that can be named
|
|
1357
|
+
// on sight, unlike the `undefined` of an import still in flight. Symbol-keyed
|
|
1358
|
+
// and non-enumerable: it rides the scope chain (so an :each item scope finds
|
|
1359
|
+
// it too) without ever showing up as data
|
|
1360
|
+
const UNFILLED_PROPS = Symbol("jq79.unfilledProps")
|
|
1361
|
+
|
|
1196
1362
|
// default-import interop for factory scripts: real modules expose .default,
|
|
1197
1363
|
// while importing an .html component resolves to the Component79 itself
|
|
1198
1364
|
const interopDefault = (mod: any) => (mod && mod.default !== undefined ? mod.default : mod)
|
|
@@ -1297,6 +1463,13 @@ export const hotUpdate = (filename: string, src: string): number => {
|
|
|
1297
1463
|
// parsed once and shared by every instance - which is already what a
|
|
1298
1464
|
// definition and the clones :component makes from it do
|
|
1299
1465
|
const parts = parseComponentString(src)
|
|
1466
|
+
// the file is the hot-reload unit, so one reparse serves every component it
|
|
1467
|
+
// declares: an instance is handed the parts of the component it *is*, by
|
|
1468
|
+
// name. A name that is no longer in the file (a <template> renamed or
|
|
1469
|
+
// deleted) has no parts to be given, and only a reload can fix the page
|
|
1470
|
+
let orphaned = false
|
|
1471
|
+
const partsFor = (instance: Component79): ComponentParts | null =>
|
|
1472
|
+
instance.name === undefined ? parts : parts.siblings?.[instance.name] ?? null
|
|
1300
1473
|
|
|
1301
1474
|
let rerendered = 0
|
|
1302
1475
|
for (const [name, refs] of hotRegistry) {
|
|
@@ -1307,11 +1480,16 @@ export const hotUpdate = (filename: string, src: string): number => {
|
|
|
1307
1480
|
refs.delete(ref) // collected since the last update
|
|
1308
1481
|
continue
|
|
1309
1482
|
}
|
|
1310
|
-
|
|
1483
|
+
const next = partsFor(instance)
|
|
1484
|
+
if (!next) {
|
|
1485
|
+
orphaned = true
|
|
1486
|
+
continue
|
|
1487
|
+
}
|
|
1488
|
+
if (instance.hotReplace(next)) rerendered++
|
|
1311
1489
|
}
|
|
1312
1490
|
if (!refs.size) hotRegistry.delete(name)
|
|
1313
1491
|
}
|
|
1314
|
-
return rerendered
|
|
1492
|
+
return orphaned ? 0 : rerendered
|
|
1315
1493
|
}
|
|
1316
1494
|
|
|
1317
1495
|
// starts tracking instances, so hotUpdate can find them. jq79/dev's client
|
|
@@ -1324,6 +1502,14 @@ export const enableHotReload = (): void => {
|
|
|
1324
1502
|
|
|
1325
1503
|
type EmitListener = (event: CustomEvent, payload: any) => void
|
|
1326
1504
|
|
|
1505
|
+
const fetchComponent = async (url: string): Promise<Component79> => {
|
|
1506
|
+
const response = await fetch(url)
|
|
1507
|
+
if (!response.ok) throw new Error(`failed to fetch component from ${url}: ${response.status}`)
|
|
1508
|
+
// the URL names the component's scripts in devtools, and is where the
|
|
1509
|
+
// browser will look for the source when a breakpoint lands in one
|
|
1510
|
+
return new Component79(await response.text(), { filename: url })
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1327
1513
|
// a parsed single-file component. Typical lifecycle:
|
|
1328
1514
|
//
|
|
1329
1515
|
// const jq79 = new Component79(src) // or await Component79.fetch(url)
|
|
@@ -1341,6 +1527,14 @@ export class Component79 {
|
|
|
1341
1527
|
modules?: Record<string, any>
|
|
1342
1528
|
// the component's origin, used to name its scripts in devtools
|
|
1343
1529
|
filename?: string
|
|
1530
|
+
// the other components declared in the same file, by name (see
|
|
1531
|
+
// ComponentParts.siblings). They are also this definition's own properties,
|
|
1532
|
+
// so `const { Row } = await Component79.fetch(url)` reaches them
|
|
1533
|
+
siblings?: Record<string, Component79>
|
|
1534
|
+
// this component's name inside its file, for the components a <template>
|
|
1535
|
+
// declared; the file's own component has none - it is the default, and a
|
|
1536
|
+
// default is named by whoever imports it
|
|
1537
|
+
name?: string
|
|
1344
1538
|
|
|
1345
1539
|
data: ReactiveDeepData<Record<string, any>> | null = null
|
|
1346
1540
|
|
|
@@ -1372,9 +1566,29 @@ export class Component79 {
|
|
|
1372
1566
|
this.styles = parts.styles
|
|
1373
1567
|
this.modules = options.modules ?? (typeof src === "string" ? undefined : src.modules)
|
|
1374
1568
|
this.filename = options.filename ?? (typeof src === "string" ? undefined : src.filename)
|
|
1569
|
+
this.siblings = parts.siblings
|
|
1570
|
+
this.name = parts.name
|
|
1571
|
+
this.adoptSiblings()
|
|
1375
1572
|
hotRegister(this) // a no-op unless the page enabled hot reload
|
|
1376
1573
|
}
|
|
1377
1574
|
|
|
1575
|
+
// the parser builds a file's sibling definitions before anyone has told it
|
|
1576
|
+
// where the file came from, so whoever holds the parse hands its origin down
|
|
1577
|
+
// - and keeps doing it after a hot reload, which parses the file afresh.
|
|
1578
|
+
// Without it a reloaded child would have no filename, and an instance with
|
|
1579
|
+
// no filename is not tracked: the next edit would never reach it
|
|
1580
|
+
private adoptSiblings() {
|
|
1581
|
+
if (!this.siblings) return
|
|
1582
|
+
Object.entries(this.siblings).forEach(([name, sibling]) => {
|
|
1583
|
+
sibling.filename ??= this.filename
|
|
1584
|
+
sibling.modules ??= this.modules
|
|
1585
|
+
// the file's own component also *is* the file: its named components hang
|
|
1586
|
+
// off it as properties, which is what `const { Row } = …` reads (and
|
|
1587
|
+
// what the bundler re-exports by name)
|
|
1588
|
+
if (!this.name) (this as any)[name] = sibling
|
|
1589
|
+
})
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1378
1592
|
// swaps this component's parsed parts for `src`'s and, if it is on the page,
|
|
1379
1593
|
// re-renders it where it stands - seeded with a snapshot of its data, so
|
|
1380
1594
|
// props and store values survive (the setup script runs again, so whatever it
|
|
@@ -1410,6 +1624,11 @@ export class Component79 {
|
|
|
1410
1624
|
this.template = parts.template
|
|
1411
1625
|
this.scripts = parts.scripts
|
|
1412
1626
|
this.styles = parts.styles
|
|
1627
|
+
// the file's other components as they are now: the next render resolves
|
|
1628
|
+
// <Row> against these, so a parent picks up an edited child even when the
|
|
1629
|
+
// child's own instances are patched separately
|
|
1630
|
+
this.siblings = parts.siblings
|
|
1631
|
+
this.adoptSiblings()
|
|
1413
1632
|
if (!rendered) return false // a definition: its clones re-render themselves
|
|
1414
1633
|
|
|
1415
1634
|
this.renderWith(data, shadow)
|
|
@@ -1424,12 +1643,13 @@ export class Component79 {
|
|
|
1424
1643
|
return true
|
|
1425
1644
|
}
|
|
1426
1645
|
|
|
1427
|
-
static
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1646
|
+
static fetch(url: string): Promise<Component79>
|
|
1647
|
+
static fetch(urls: string[]): Promise<Component79[]>
|
|
1648
|
+
// an array of URLs fetches them all at once and resolves to the components in
|
|
1649
|
+
// the same order, so one await destructures them - and, like Promise.all, the
|
|
1650
|
+
// first failure rejects the whole thing
|
|
1651
|
+
static fetch(urls: string | string[]): Promise<Component79 | Component79[]> {
|
|
1652
|
+
return Array.isArray(urls) ? Promise.all(urls.map(fetchComponent)) : fetchComponent(urls)
|
|
1433
1653
|
}
|
|
1434
1654
|
|
|
1435
1655
|
// subscribes to this instance's $emit events, on top of the DOM CustomEvent
|
|
@@ -1460,7 +1680,18 @@ export class Component79 {
|
|
|
1460
1680
|
private renderWith(data: Record<string, any>, shadow: boolean): this {
|
|
1461
1681
|
this.destroy()
|
|
1462
1682
|
|
|
1463
|
-
|
|
1683
|
+
// what this component can see of its file's other components, and which of
|
|
1684
|
+
// its declared props arrived empty - both decided by the signature, before
|
|
1685
|
+
// the store exists (see siblingsInScope / UNFILLED_PROPS)
|
|
1686
|
+
const declared = declaredPropNames(this.scripts)
|
|
1687
|
+
const siblingScope = siblingsInScope(this.siblings, declared)
|
|
1688
|
+
const raw: Record<string, any> = siblingScope
|
|
1689
|
+
? Object.assign(Object.create(siblingScope), data)
|
|
1690
|
+
: { ...data }
|
|
1691
|
+
const unfilled = new Set([...declared].filter(name => !(name in data)))
|
|
1692
|
+
if (unfilled.size) Object.defineProperty(raw, UNFILLED_PROPS, { value: unfilled })
|
|
1693
|
+
|
|
1694
|
+
const store = $reactive(raw)
|
|
1464
1695
|
const fx = createEffectScope(store)
|
|
1465
1696
|
this.data = store
|
|
1466
1697
|
this.fx = fx
|
|
@@ -1536,7 +1767,13 @@ export class Component79 {
|
|
|
1536
1767
|
const defer = (code: string) => `await $mounted();${code}`
|
|
1537
1768
|
|
|
1538
1769
|
this.scripts.forEach((script, index) => {
|
|
1539
|
-
|
|
1770
|
+
// the file's other components are passed as parameters of the compiled
|
|
1771
|
+
// script, not just left on the store's prototype: a factory script runs
|
|
1772
|
+
// as plain lexical JS with no `with`, so a bare `Row` in one would
|
|
1773
|
+
// resolve to nothing at all. In setup mode this composes with `with` -
|
|
1774
|
+
// scriptScope's `has` declines any name that is a helper, so the
|
|
1775
|
+
// parameter is what the name resolves to
|
|
1776
|
+
const instanceHelpers = { $emit, $mounted, $self, $$self, ...siblingScope }
|
|
1540
1777
|
const at: ScriptLocation = { filename: this.filename, index }
|
|
1541
1778
|
const factoryCode = transformFactoryScript(script.content)
|
|
1542
1779
|
if (factoryCode !== null) {
|