This repo is a minimal reproduction of a Temporal Dead Zone (TDZ) error encountered when using multiple APIs from @observablehq/plot
in a production Webpack build.
To encounter the TDZ error, take the following steps:
- Run the Next.js production build:
yarn build
- Serve the production build locally:
yarn start
This will start a local development server at http://localhost:3000
.
-
Navigate to either
http:localhost:3000/histogram
orhttp:localhost:3000/strip-plot
. You should see a white screen with an bit of black text reading "Application error: a client-side exception has occurred (see the browser console for more information).". -
Open the browser console. You should see an error saying:
ReferenceError: can't access lexical declaration 'd' before initialization
(Firefox)
ReferenceError: Cannot access 'd' before initialization
(Chrome)
To get rid of the error, delete either pages/histogram.js
or pages/strip-plot.js
. Follow the steps above to re-run and serve the production build. If you visit the page you DID NOT delete, you should be able to see the histogram or strip plot generated by Plot.
The error is only visible in production builds (e.g. those run with NODE_ENV=production
). This is part of what makes it difficult to trace!
The stack trace of the reported error is as follows:
ReferenceError: Cannot access 'd' before initialization
at Object.vc (497-35515fda497bb06a.js:1:665)
at Object.27 (facet.js:13:21)
at r (bootstrap:21:23)
at Object.2186 (497-35515fda497bb06a.js:1:1422)
at r (bootstrap:21:23)
at Object.3962 (histogram-6827f696cfad69e9.js:1:297)
at r (bootstrap:21:23)
at (index):5:16
at route-loader.js:236:51
Following the stack trace is a bit difficult since the production build is minified. However, we did turn on source maps using productionBrowserSourceMaps: true
in next.config.js
. This is what allows us to trace that the error arose from L13 in facet.js
from @observablehq/plot
.
L13, C21 of facet.js
pertains to the Mark
class
imported from mark.js
. The issue is that, at the time class Facet
is being initialized to extend Mark
, Mark
itself has not yet been declared. This is what leads to the TDZ ReferenceError
. d
in the minified build pertains to class Mark
.
These types of TDZ ReferenceError
s in production Webpack builds tend, based on this comment from Webpack's core author Tobias Koppers, to be caused by cyclic dependencies.
Yes. Adding the no-cycle
rule from @eslint-plugin-import
to @observablehq/plot
resulted in 52 detected cycles. To be clear, cyclic dependencies are allowed by the ESM spec; they can just lead to these kinds of TDZ errors in Webpack. In addition, tree shaking in Webpack makes this even more tricky to track down, because, depending on which parts of the Plot API you use, the chunk Webpack generates for @observablehq/plot
will resolve these cycles in different orders. This means you may use a set of APIs that result in a perfectly fine production build with no runtime errors.
The solution with the highest probability of success would be to eliminate all cycles from the source. However, this is likely prohibitively expensive due to how many cycles currently exist and the challenges associated with such large-scale refactoring.
The specific error here appears to be the result of the following cycle:
facet.js
importsMark
frommark.js
mark.js
importsplot
fromplot.js
plot.js
importsfacets
fromfacets.js
Resolving this cycle could help to alleviate this problem, but I'm not fully certain. It appears to be the most likely candidate based on the stack trace, but I have noticed that this error appears to be mark type dependent. For example, in another project where I use the barY
and dot
marks APIs in the same project, I don't encounter this error. This suggests that the troublesome cycle may be related to some of the transforms
APIs (e.g. binX
, normalizeX
) that the histogram and strip plots use.