docz: SyntaxError: Expected corresponding JSX closing tag
Getting SyntaxError: Expected corresponding JSX closing tag
error when using any component with children inside the Playground
component.
Using components that self close, for example <MyComponent />
works fine.
Here is a sample with the error. Please click on Components > Alert on the left menu.
https://codesandbox.io/s/docz-example-xj522
Thanks
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 11
- Comments: 15 (6 by maintainers)
I ended up creating a PR with the above fix - https://github.com/doczjs/docz/pull/1696
After a very long debug session (it took a few hours because I was not familiar with the project š¬), I came to the conclusion that:
What is the fixed Babel bug exactly?
Babel has a few utility methods to control how the AST is traversed. One of them if
path.stop()
, whose goal is to enterly stop the current traversal. You can read its documentation in our semi-official plugins handbook.However, in some circumstances it only prevented part of the remaining nodes from being traversed, rather than all of them. We fixed this bug, and now
path.stop()
behaves properly.What is the docz bug?
docz has some code to remove the
<Playground>...</Playground>
tags, transforminginto
This is the utility function that should do that transformation: https://github.com/doczjs/docz/blob/259898c25838c052048bd81277d0d74d0ee5c1e2/core/docz-utils/src/jsx.ts#L13-L18
For completeness, here is the
codeFromNode
function: https://github.com/doczjs/docz/blob/259898c25838c052048bd81277d0d74d0ee5c1e2/core/docz-utils/src/ast.ts#L7-L28codeFromNode
takes a āconditionā function, and returns a function that, when given the input code, returns a portion of the code whose AST matches the condition.const open = codeFromNode(p => p.isJSXOpeningElement()); open(code)
returns the the code of something that matchesp.isJSXOpeningElement()
.In our example above, both
<Playground>
and<Alert>
match that condition: which one does it return? If we look at thevalueFromTraverse
implementation, it:@babel/traverse
(Babel uses a DFS traversal, and when using theenter
visitor itās in visits the node in pre-order).path.stop()
to stop the traversal.Calling
path.stop()
as soon as something matches the condition means that it returns the first node that matches it. When looking for aJSXOpeningElement
it thus returns<Playground>
, while when looking for aJSXClosingElement
itās</Alert>
.(It might help looking at the AST for my example, remembering that Babel traverses nodes in āsource code orderā: when it comes to JSX, it first traverses the opening tag, then the children and then the closing tag.)
After getting
<Playground>
and</Alert>
,removeTags
removes them from the input code.We were āluckyā before because
path.stop()
didnāt work properly in that case, so it didnāt stop the traversal when it found a closing tag but continued normally (reaching</Playground>
at the end of the traversal).My suggested fix
Rather than finding an opening and a closing tag separately, I suggest getting them at the same time. Also, the current
.replace
-based approach is quite fragile. For example (even before the@babel/traverse
fix), this didnāt work:because by replacing
<Playground>
and</Playground>
with an empty string you get:I suggest to rewrite
removeTags
like this:by doing so we get the start/end location of the contents of the first JSX tag, which should be safe.
After investigation, it seems that forcing (through yarn resolution or whatever) the downgrade of the package
@babel/traverse
to v7.16.7 (or@babel/core
to v7.16.7) solves the issue. The regression was introduced for versions greater or equal thanv7.16.8
.It might be related to this issue https://github.com/babel/babel/issues/14139 and this update https://github.com/babel/babel/pull/14105
Same issue here. For me it seems to be cutting out the first ending tag, so
becomes
same problem
great work @nicolo-ribaudo, weāre going to merge and release it as soon as we can
Hello @nicolo-ribaudo š , First of all, thanks for your time and involvement š .
Iāve mentioned the https://github.com/babel/babel/pull/14105 since the issue starts to appear from
@babel/traverse@v7.16.8
and above (same issue also with@babel/traverse@7.16.10
). From the changelog, I can see that the only change introduced since was https://github.com/babel/babel/pull/14105.Iām not an active Docz maintainer but from what I see, It seems:
MDX content is parsed via remark-docz using the
@babel/generator
dependency https://github.com/doczjs/docz/blob/main/core/remark-docz/src/index.ts#L3.The parsed code is then transformed, on certain conditions, to add
React.Fragment
before being transferred to the react-live provider (via the propscode
). However, the issue might not come from thereact-live
dependency since the<Playground>
opening tag is already stripped before instantiating the react-live dependency:Maybe the maintainers @pedronauck / @renatobenks can confirm.
Hi! Babel maintainer here.
Iād be happy to help figure out if this is a Babel bug, or if itās a bug somewhere else that was hidden in this specific circumstance and unveiled by that Babel update.
Does docz use any custom Babel plugin to transform
<Playground>
tags? I only found https://github.com/doczjs/docz/tree/main/other-packages/babel-plugin-export-metadata but it looks unrelated.