This is Alpine v2.7.0.
If I do something like this:
<template x-for="item in thisdoesnotexist" :key="item">
<div></div>
</template>
I get this not very useful error in the console:
[Error] TypeError: undefined is not an object (evaluating 'items.forEach')
(anonymous function) (alpine.js:1762)
In non-trivial setups, guessing where the faulty template code is, is extremely hard.
I remember in a not-so-distant Alpine version that I saw some more context (as in: the actual script string that failed to evaluate).
I've just tried with that snippet and I got the following output
alpine.js:1855 Uncaught ReferenceError: thisdoesnotexist is not defined
at eval (eval at l (alpine.js:115), <anonymous>:3:36)
at l (alpine.js:115)
at fe.evaluateReturnExpression (alpine.js:1694)
at alpine.js:587
at k (alpine.js:507)
at alpine.js:1683
at Array.forEach (<anonymous>)
at fe.resolveBoundAttributes (alpine.js:1643)
at fe.initializeElement (alpine.js:1570)
at alpine.js:1554
eval @ VM1550:3
l @ alpine.js:115
evaluateReturnExpression @ alpine.js:1694
(anonymous) @ alpine.js:587
k @ alpine.js:507
(anonymous) @ alpine.js:1683
resolveBoundAttributes @ alpine.js:1643
initializeElement @ alpine.js:1570
(anonymous) @ alpine.js:1554
(anonymous) @ alpine.js:1544
e @ alpine.js:87
e @ alpine.js:91
walkAndSkipNestedComponents @ alpine.js:1532
initializeElements @ alpine.js:1549
fe @ alpine.js:1459
initializeComponent @ alpine.js:1852
(anonymous) @ alpine.js:1795
(anonymous) @ alpine.js:1811
discoverComponents @ alpine.js:1810
start @ alpine.js:1794
setTimeout (async)
initializeComponent @ alpine.js:1854
(anonymous) @ alpine.js:1795
(anonymous) @ alpine.js:1811
discoverComponents @ alpine.js:1810
start @ alpine.js:1794
async function (async)
start @ alpine.js:59
(anonymous) @ alpine.js:1884
(anonymous) @ alpine.js:4
(anonymous) @ alpine.js:1
alpine.js:510 Uncaught TypeError: l.forEach is not a function
at k (alpine.js:510)
at alpine.js:1683
at Array.forEach (<anonymous>)
at fe.resolveBoundAttributes (alpine.js:1643)
at fe.initializeElement (alpine.js:1570)
at alpine.js:1554
at alpine.js:1544
at e (alpine.js:87)
at e (alpine.js:91)
at fe.walkAndSkipNestedComponents (alpine.js:1532)
k @ alpine.js:510
(anonymous) @ alpine.js:1683
resolveBoundAttributes @ alpine.js:1643
initializeElement @ alpine.js:1570
(anonymous) @ alpine.js:1554
(anonymous) @ alpine.js:1544
e @ alpine.js:87
e @ alpine.js:91
walkAndSkipNestedComponents @ alpine.js:1532
initializeElements @ alpine.js:1549
fe @ alpine.js:1459
initializeComponent @ alpine.js:1852
(anonymous) @ alpine.js:1795
(anonymous) @ alpine.js:1811
discoverComponents @ alpine.js:1810
start @ alpine.js:1794
setTimeout (async)
initializeComponent @ alpine.js:1854
(anonymous) @ alpine.js:1795
(anonymous) @ alpine.js:1811
discoverComponents @ alpine.js:1810
start @ alpine.js:1794
async function (async)
start @ alpine.js:59
(anonymous) @ alpine.js:1884
(anonymous) @ alpine.js:4
(anonymous) @ alpine.js:1
I don't think Alpine has ever printed the html element but the error has a bit more context that the one you posted, though.
Is it with a specific browser? I used Chrome (Version 85.0.4183.102 (Official Build) (64-bit)).
@SimoTod OK, I may be wrong about the "it was better a long time ago", I have since I opened this issue seen that I get slightly more relevant info in other error situation.
So, your stacktrace isn't very much more useful than what I have.
If you do for item in items and you have ... 100s of those spread around, you get the needle in the haystack situation.
Yeah, although you don't get the error randomly after an arbitrary amount of time. A developer will write a component and if there's a typo it won't work straight away when testing it so they'll know roughly where the error is.
Unfortunately, since those strings are evaluated at runtime from the dom, I don't think there's a way to print out the exact point of the page originating it. You can maybe print out the outerHTML of the whole component but it won't tell you where to look at if you have similar components in the same page.
P.s. I know that the stacktrace is not the best, I'm not trying to say the opposite but I'm not sure how it could be improved.
We could print out the expression which caused the issue?
It would still be a generic item in items, not sure how much it would help
A developer will write a component and if there's a typo it won't work straight away when testing it so they'll know roughly where the error is.
These errors can come from many places (merge errors, typos from some other developer ...). In an ideal world I would love to have clickable errors with linenumber/column pointing back to the file I was editing, but that is probably pushing it.
But I assume it should be possible to provide some more context. The document DOM is available? Why not print some of the elements surrounding the error?
It's possible (probably just a try catch in a sensible place). You can maybe check with @calebporzio if he would accept that PR.
I saved myself a 3 hour head-scratching debug session by manually inserting a console log:
// Wrap in a try/catch so that we don't prevent other components
// from initializing when one component contains an error.
try {
el.__x = new Component(el);
} catch (error) {
console.log(el);
setTimeout(() => {
throw error;
}, 0);
}
Finding the DOM element/component is ... handy.
@bep maybe we can add the element to the error?
@HugoDF yes, something like that would be super-useful.
Wondering if there's a way to get an additional error property to print out when the error hits the page
For "completeness", it would be great to add the el logging when errors occurs here as well:
evaluateReturnExpression(el, expression, extraVars = () => {}) {
I agree that Alpine could make error reporting nicer.
To me, the most useful information would be the opening tag of the offending element. The contents would be too big to report I think, and the closing tag doesn't matter all that much.
Anyhow, I haven't looked into the internals for this to know if there is a good way to centralize this behavior.
I do imagine that attaching this behavior to the runtime evaluators would be the best place.
I also don't think this is a high priority. It would be nice, but I wouldn't want to bog down the core very much for a feature like this until the demand is more clear.
The simplest way to do this would probably be to ditch the setTimeout and do
// we can pre-process `el.innerHTML` to output only the opening tag
console.error(`Alpine: Error at element ${el.innerHTML}`);
console.error(error.stack);
I also don't think this is a high priority. It would be nice, but I wouldn't want to bog down the core very much for a feature like this until the demand is more clear.
Saving many hours of peoples' time doing extremely tedious stuff isn't a clear demand?
The simplest way to do this would probably be to ditch the
setTimeoutand do// we can pre-process `el.innerHTML` to output only the opening tag console.error(`Alpine: Error at element ${el.innerHTML}`); console.error(error.stack);
@bep thoughts on this?
Would it cause any loss of information?
Shouldn't it be outerHTML?
Shouldn't it be outerHTML?
Good catch.
For reference, I think this file is how the Vue compiler does things https://github.com/vuejs/vue/blob/2.6/src/compiler/error-detector.js.
You can use window.addEventListener('error', doSomething) to catch the errors thrown by Alpine.
You can use
window.addEventListener('error', doSomething)to catch the errors thrown by Alpine.
yeah, but that won't fix the "I don't know at which element an error occured" issue right?
Yeah, for that we should change throw error; to throw errorMaker(baseerror, el, …) or something.
FWIW, when I wrote #447, I mentioned that we might want an Alpine.onerror handler, but I think delaying that decision was the right call because it was worth waiting for the right API instead of just doing the first one that came to mind.
@carlmjohnson yeah that makes a lot of sense actually, let's see what we can come up with
worth waiting for the right API instead of just doing the first one that came to mind.
So, the current API is throw error which I think is fine and dandy and what most people expect.
I would, however, not consider the _error value_ thrown to be part of the "API spec" (esp. since it's untyped), so adding some more context (e.g. the start element name and attributes) and then revise that later with some more/other info would be a good thing.
I have some experience building development tools and one of the "lessons learned" from this is that the quality of error messages thrown is extremely important, especially for non-expert users.
And the current situation in AlpineJS is, not great, and what I would consider a defect/bug. "There was an error somewhere about something" is what we get out of it.
@bep cc @calebporzio I've gone ahead and created a PR that means errors should at least have the "expression" where the error occured and the component in which it's happening.
Most helpful comment
So, the current API is
throw errorwhich I think is fine and dandy and what most people expect.I would, however, not consider the _error value_ thrown to be part of the "API spec" (esp. since it's untyped), so adding some more context (e.g. the start element name and attributes) and then revise that later with some more/other info would be a good thing.
I have some experience building development tools and one of the "lessons learned" from this is that the quality of error messages thrown is extremely important, especially for non-expert users.
And the current situation in AlpineJS is, not great, and what I would consider a defect/bug. "There was an error somewhere about something" is what we get out of it.