jq79 0.5.5 → 0.5.6
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/dist/jq79.cjs +12 -12
- package/dist/jq79.cjs.map +1 -1
- package/dist/jq79.global.js +12 -12
- package/dist/jq79.global.js.map +1 -1
- package/dist/jq79.js +12 -12
- package/dist/jq79.js.map +1 -1
- package/package.json +1 -1
- package/src/jq79.ts +66 -2
package/package.json
CHANGED
package/src/jq79.ts
CHANGED
|
@@ -127,6 +127,36 @@ const reportedExprErrors = new Set<string>()
|
|
|
127
127
|
let pendingScripts = 0
|
|
128
128
|
let flushScheduled = false
|
|
129
129
|
|
|
130
|
+
// everything else an expression can throw - overwhelmingly a member access on
|
|
131
|
+
// an undefined value, `{{ game.is.loaded }}` over a game with no `is`. There is
|
|
132
|
+
// no pure question to ask about it the way there is for a missing name: `in`
|
|
133
|
+
// answers "declared anywhere?" without evaluating, and nothing answers "would
|
|
134
|
+
// this still throw?" without running the expression again, which is exactly
|
|
135
|
+
// what mustn't happen (`@click="count++ + missing"` increments twice).
|
|
136
|
+
//
|
|
137
|
+
// What is pure is watching the evaluations that were going to happen anyway. An
|
|
138
|
+
// expression whose value is merely late is re-evaluated when the value arrives -
|
|
139
|
+
// the reads that did land registered as dependencies, so the write wakes the
|
|
140
|
+
// effect - and it succeeds then. So transient means "succeeds later", and the
|
|
141
|
+
// re-check is to clear the entry on the next successful evaluation and report
|
|
142
|
+
// only what is still queued once the page has gone quiet.
|
|
143
|
+
//
|
|
144
|
+
// Quiet is a timer rather than the microtask the other queue flushes on,
|
|
145
|
+
// because a value from fetch() arrives many tasks later and clearing on success
|
|
146
|
+
// exists to let it. A second covers that and still lands the warning inside the
|
|
147
|
+
// page load. It is a guess, and the cost is a slower fetch warning about correct
|
|
148
|
+
// code with nothing to take it back - accepted because the silence it replaces
|
|
149
|
+
// hid a render that was actively wrong
|
|
150
|
+
const FAILED_EXPR_DELAY = 1000
|
|
151
|
+
|
|
152
|
+
// keyed by expression text alone, not `message|expr`: the expression is what the
|
|
153
|
+
// author edits, and one broken path throwing two messages is still one bug. The
|
|
154
|
+
// entries hold no scope, only strings, so this queue cannot retain a live
|
|
155
|
+
// component the way pendingReports can
|
|
156
|
+
const failedExprs = new Map<string, string>()
|
|
157
|
+
const reportedFailedExprs = new Set<string>()
|
|
158
|
+
let failedFlushTimer: ReturnType<typeof setTimeout> | undefined
|
|
159
|
+
|
|
130
160
|
const flushExprReports = () => {
|
|
131
161
|
flushScheduled = false
|
|
132
162
|
if (pendingScripts > 0) return // a script started meanwhile; its release re-schedules
|
|
@@ -149,6 +179,25 @@ const scheduleExprReportFlush = () => {
|
|
|
149
179
|
queueMicrotask(flushExprReports)
|
|
150
180
|
}
|
|
151
181
|
|
|
182
|
+
const flushFailedExprs = () => {
|
|
183
|
+
failedFlushTimer = undefined
|
|
184
|
+
if (pendingScripts > 0) return // a script started meanwhile; its release re-schedules
|
|
185
|
+
failedExprs.forEach((message, expr) => {
|
|
186
|
+
reportedFailedExprs.add(expr)
|
|
187
|
+
console.warn(
|
|
188
|
+
`jq79: ${message} - evaluating "${expr}". It rendered as nothing and was ` +
|
|
189
|
+
`still failing a moment later, so the value is probably never coming: check ` +
|
|
190
|
+
`the path. If it does arrive asynchronously, guard it - "a?.b", or :if on the element.`
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
failedExprs.clear()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const scheduleFailedExprFlush = () => {
|
|
197
|
+
if (failedFlushTimer !== undefined || pendingScripts > 0 || !failedExprs.size) return
|
|
198
|
+
failedFlushTimer = setTimeout(flushFailedExprs, FAILED_EXPR_DELAY)
|
|
199
|
+
}
|
|
200
|
+
|
|
152
201
|
// scripts run before the template renders, so the counter is already up when
|
|
153
202
|
// the first evaluation fails. Both script modes settle through a promise;
|
|
154
203
|
// the factory's has to cover the merge, not just the module body
|
|
@@ -157,12 +206,20 @@ const trackScript = (settled: Promise<unknown>) => {
|
|
|
157
206
|
const release = () => {
|
|
158
207
|
pendingScripts--
|
|
159
208
|
scheduleExprReportFlush()
|
|
209
|
+
scheduleFailedExprFlush()
|
|
160
210
|
}
|
|
161
211
|
settled.then(release, release)
|
|
162
212
|
}
|
|
163
213
|
|
|
214
|
+
const reportFailedExpr = (expr: string, error: unknown) => {
|
|
215
|
+
if (reportedFailedExprs.has(expr) || failedExprs.has(expr)) return
|
|
216
|
+
if (failedExprs.size >= MAX_PENDING_REPORTS) return
|
|
217
|
+
failedExprs.set(expr, (error as Error)?.message || String(error))
|
|
218
|
+
scheduleFailedExprFlush()
|
|
219
|
+
}
|
|
220
|
+
|
|
164
221
|
const reportExprError = (expr: string, scope: Record<string, any>, error: unknown) => {
|
|
165
|
-
if (!(error instanceof ReferenceError)) return
|
|
222
|
+
if (!(error instanceof ReferenceError)) return reportFailedExpr(expr, error)
|
|
166
223
|
const match = MISSING_NAME_RE.exec(error.message)
|
|
167
224
|
const name = match?.[1] ?? match?.[2]
|
|
168
225
|
if (!name) return // an engine whose wording we don't know: stay quiet, as before
|
|
@@ -184,7 +241,12 @@ const runExpr = (expr: string, scope: Record<string, any>, extras?: Record<strin
|
|
|
184
241
|
|
|
185
242
|
const evalExpr = (expr: string, scope: Record<string, any>, extras?: Record<string, any>): any => {
|
|
186
243
|
try {
|
|
187
|
-
|
|
244
|
+
const value = runExpr(expr, scope, extras)
|
|
245
|
+
// the re-check for the failed-expression queue: this run is the proof that
|
|
246
|
+
// the earlier throw was a value arriving late. Guarded on size so the
|
|
247
|
+
// normal page - where nothing has ever thrown - pays one comparison
|
|
248
|
+
if (failedExprs.size) failedExprs.delete(expr)
|
|
249
|
+
return value
|
|
188
250
|
} catch (error) {
|
|
189
251
|
reportExprError(expr, scope, error)
|
|
190
252
|
return undefined
|
|
@@ -2276,6 +2338,8 @@ export class Component79 {
|
|
|
2276
2338
|
// a different key
|
|
2277
2339
|
reportedExprErrors.clear()
|
|
2278
2340
|
pendingReports.clear()
|
|
2341
|
+
reportedFailedExprs.clear()
|
|
2342
|
+
failedExprs.clear()
|
|
2279
2343
|
const marker = this.startMarker
|
|
2280
2344
|
const rendered = !!(marker && this.content)
|
|
2281
2345
|
|