Recently, we had a crash in our NeetoCRMapplication.

As we can see in the screenshot, it looks like the error happened inneeto-widget-replay.js file.
At Neeto we have built an internal tool called NeetoReplaywhich captures users' activities in the browser. This helps us in debugging whenusers contact us for support or in investigating bugs. This is built on top ofrrweb. Just want to add that admins of theworkspace can completely opt out of NeetoReplay.
We had not changed anything in NeetoReplay for a while, so the error happeningin NeetoReplay was perplexing. Upon investigation, I found that the consoleindeed pointed to the neeto-widget-replay.js file. But I also knew that thefilename only tells us which function called console.error, not where theerror originated.
The replay widget wraps console.log, console.warn, and console.error so itcan capture console output for session replay. Once the page loads, everyconsole message passes through the widget's wrapper, causing DevTools toassociate those messages with the widget file. As a result, when the NeetoCRMapp throws an error during startup, the console makes it look like the errorcame from the replay widget even though the actual exception was thrown insidethe NeetoCRM bundle.
The way rrweb's console plugin works, it replaces console.log, console.warn,and console.error so console output can be captured for session replay. Ichecked whether rrweb provides a way to preserve the original callerinformation, but it doesn't. Removing the wrapper would also mean losing consolecapture from replays.
This isn't specific to rrweb either. Honeybadger, Sentry, PostHog, and ReactDevTools use similar wrapping and have the same behavior. React DevTools has anidentical issue documented infacebook/react#22257.
The only known mitigation is Chrome's x_google_ignoreList source map feature,which allows DevTools to hide library frames and show the host application frameinstead. However, it only works when source maps are available. Since we don'tship source maps with the production minified bundle, this isn't available inproduction. As a result, the console attribution is an unavoidable side effectof session replay tooling, but the actual error information remains intact.
The video mentioned below is the one I created for the internal Neeto folks. Thevideo is being published as-is without any modifications.
The real error is: The failure occurs because code in Tracing this back further shows that In development, Vite serves modules as native ES modules and the browserevaluates them depth-first. When In production, builds are generated using esbuild, which bundles everything intoa single file and has to choose a linear execution order. This can introducesubtle differences in behavior, particularly around module evaluation order andcircular dependencies. In the generated bundle, code from The reason this surfaced now is that a recent change introduced the followingimport in the Deals, Leads, and Contacts Those files did not previously import from The fix is to break the circular dependency by moving To help catch these issues earlier, we provide an esbuild-based developmentserver that mirrors production bundling behavior while still supportingautomatic rebuilds during development. Start the Rails server with the In a separate terminal, start esbuild in watch mode. With If JavaScript execution fails before the React tree mounts, Honeybadger neverreceives the error. This includes chunk load failures, syntax errors in theentry chunk, runtime errors during module evaluation, and bundling issues causedby incorrect module ordering. The error was thrown while evaluating the The initialization flow looked like this: When a startup failure occurs, As a result, startup failures that completely prevent the application fromloading are invisible to Honeybadger. Honeybadger should be initialized before loading the Honeybadger is now configured in With Back to the error
TypeError: I is not a function at chunk-CL4SWXVJ.digested.jsI is the minified name for createColumn, a helper defined incommons/utils.jsx.commons/constants.js callscreateColumn(...) before the function has been initialized. At runtime,createColumn is still undefined, causing the call to fail and preventing theReact application from starting. The subsequent Failed to load component Appmessage is simply the mount code reporting that application initializationfailed.commons/constants.js andcommons/utils.jsx have a circular dependency. constants.js importscreateColumn from utils.jsx while utils.jsx imports values fromconstants.js.constants.js imports createColumn fromutils.jsx, the browser evaluates utils.jsx first, which initializescreateColumn before control returns to constants.js. By the timeconstants.js calls createColumn, the function is already defined.constants.js ends uprunning before createColumn is initialized, which causes the crash.Show/index.jsx files:import { buildHeaderMoreMenu } from "components/commons/utils";commons/utils. The circulardependency already existed, but this additional import changed esbuild'sbundling order and exposed the issue. The change did not introduce the circulardependency itself; it only made the existing problem surface in production.The fix
createColumn into asmall standalone module that has no dependency on commons/constants.js. Oncethe circular import is removed, esbuild can no longer generate an executionorder where createColumn is referenced before initialization.How to replicate this behavior in the development environment
ESBUILD_DEVSERVER flag enabled.ESBUILD_DEVSERVER=true bundle exec rails serveryarn build --watchESBUILD_DEVSERVER=true, Rails serves the assets generated by esbuildinstead of the assets served by Vite. Running yarn build --watch ensures thebundles are rebuilt automatically whenever files change, allowing you to testthe application using the same bundling behavior as production.Why Honeybadger didn't catch this error
App chunk and was visible in thebrowser console for every affected user. However, no Honeybadger issue wascreated.application.js mount({ App: () => import("src/App") }) dynamic import("src/App") Honeybadger.configure() is called inside HoneybadgerErrorBoundary. Sincethat component is rendered as a descendant of , Honeybadger isinitialized only after the App chunk has been successfully loaded andrendered.import("src/App") rejects, mount.js catchesthe error and logs it using console.error(...), and the execution stops before is mounted. Because the error has already been handled, window.onerroris never triggered. Since never renders,Honeybadger.configure() is never called, which means no API key is configured,no global handlers are registered, and no error is reported.App chunk so thatfailures during application bootstrap, chunk loading, and module evaluation arecaptured and reported.The Honeybadger fix
application.js, before mount() runs:application.js Honeybadger.configure({ enableUncaught: true, ... }) // configured before mount mount({ App: () => import("src/App") }) dynamic import("src/App") succeeds enableUncaught: true, window.onerror is armed before any dynamic importruns, so failures during module evaluation are captured. And if the App chunkitself fails to load, mount() reports it explicitly viaHoneybadger.notify(...) instead of only logging to the console.
BigBinary Blog
Techmeme
Nabamart
Westwood One Sports
Trillium Montessori
BreakingNews - World