Closed ElMassimo closed 2 years ago
You can already create and pass recma plugins, no change needed here?
As for whether to create a whole gh org and parser/compiler and such, I'm open to it, but was waiting for what the people will do first!
I meant if you would publish the recma plugins that are inside this repo so that they can be used independently in a granular way, just like micromark extensions.
For example, in îles it's beneficial to skip the jsx rewrite to allow providing components, because that enables Vue to detect which components need resolution.
Right now there's no way to skip the default plugins in xdm?
Some of the plugins are very tightly coupled to what's going on to compile md(x) to js(x), so uhm, instead of publishing them all:
can you pass jsx: true to keep the jsx and then compile it away the way you want to?
Yes, that's the current approach.
what if it's possible to turn this plugin off instead?
It would be enough to avoid the need to fork 😃
It might be necessary to also remove the layout component, since that identifier wouldn't be defined when the components transform is not run?
Would you accept a PR to implement these changes, and what name do you think the setting should have?
compile it away the way you want to
I meant to let xdm still create jsx elements according to how it works in babel/esbuild/swc, then leaving those with jsx: true, and using a new recma plugin to do things in a way you want to.
I'm not quite sure what you'd want to do differently in your plugin, and so I'm not sure whether it's a good idea in which place?
I am leveraging that capability with jsx: true
, and then running https://github.com/vuejs/babel-plugin-transform-vue-jsx on the resulting JSX.
By not adding this component and layout layer (which is custom in xdm), all JSX tooling is still able to process it correctly (output is valid JSX), with the additional benefit of Vue being able to detect which components should be resolved (could be globally registered components).
Seems reasonable to make those extensions optional, especially since the resulting code is leaner, and allows other recma plugins to modify it in different ways (which is not my goal, just saying).
with the additional benefit of Vue being able to detect which components should be resolved (could be globally registered components).
What prevents the current setup from allowing this auto-detection? I’ve used Vue a little bit, and know that the current setup, with jsx: true
and babel-plugin-transform-vue-jsx
support Vue nicely. But I don’t yet get how recmaDocument
is interfering?
especially since the resulting code is leaner
recmaDocument
is providing some important document features of MDX (such as passing components and the layout). I don’t yet see a good reason for making a different version of MDX that has less features. I’m assuming users would expect those features to exist.
What I don’t get is: you can already turn vuejs/babel-plugin-transform-vue-jsx
into a recma plugin, right? Why is that not enough?
The components layer added by recma-jsx-rewrite prevents the vue-jsx babel plugin from detecting unresolved components, as recma-jsx-rewrite defines local variables for all components, which vue-jsx assumes are component definitions (it has no way to know they might be undefined).
Without recma-jsx-rewrite, vue-jsx can correctly detect and transform unresolved components to resolveComponent
calls.
This is why being able to skip the recma-jsx-rewrite
plugin would be great for certain use cases (as well as skipping the layout in the recma-document
plugin whose output is not valid when skipping recma-jsx-rewrite
).
I understand that this means removing "features" from xdm. This is why I started with the question of whether you would publish some of these plugins (especially remarkWrapAndUnravel and buildJsx) so that custom pipelines can be created without affecting the vision of xdm.
The components layer added by recma-jsx-rewrite prevents the vue-jsx babel plugin from detecting unresolved components, as recma-jsx-rewrite defines local variables for all components, which vue-jsx assumes are component definitions (it has no way to know they might be undefined).
Can you expand on how this mechanism in Vue works?
This is why being able to skip the recma-jsx-rewrite plugin would be great for certain use cases (as well as skipping the layout in the recma-document plugin whose output is not valid when skipping recma-jsx-rewrite).
If a plugin X does thing Y, and you don’t want Y, that does not mean that plugin X should be removed. Perhaps there is a way thing Y can be changed to support both use cases. Especially as you later say that plugin Z doesn’t work without X. If we continue with that strain of thought we might just end up with nothing 😅 How could both mechanisms be supported? What is breaking the Vue mechanism specifically? What output would work for you?
This is why I started with the question of whether you would publish some of these plugins
You can use these plugins without forking. You can import somePlugin from 'xdm/lib/plugin/*.js'
fine?
Is recma-vue-jsx-build
in your fork a direct replacement of https://github.com/syntax-tree/estree-util-build-jsx, and a fork of vuejs/babel-plugin-transform-vue-jsx
? If so, that would also be useful as a separate recma plugin, and to suggest for Vue users here in xdm and in mdx!?
Can you expand on how this mechanism in Vue works?
Tags that have a name matching a local identifier, such as those in variable or import declarations, are compiled as createVNode(identifier, props, slots)
.
Known HTML tags are compiled as a simple call createVNode("a", props, slots)
.
The rest will be compiled as createVNode(resolveComponent("tagName"), props, slots)
, which allows resolving globally registered components.
Currently, once the recmaJsxRewrite plugin runs, it will define local identifiers for all used tags, which prevents using the algorithm of checking for local declarations (as used in the vue-jsx babel plugin, and my recma-build-vue-jsx plugin).
plugin Z doesn’t work without X
That's the coupling that you mentioned earlier as a reason not to release them independently, and I agree.
You can import somePlugin from 'xdm/lib/plugin`
Wasn't sure if the plugins were considered internal, which means breaking changes could happen in patch releases.
that would also be useful as a separate recma plugin
Definitely! It's a fork of estree-util-build-jsx
. Finished a working draft yesterday, will release it once it has tests.
Had an idea, what about flagging unresolved tag names by adding a custom flag to the nodes at a stage prior to the jsx-rewrite? (just like it's currently flagging "explicit jsx")
That would allow any Compiler
plugin later in the pipeline (such as build-vue-jsx
) to detect these and render resolveComponent
calls, or the explicit checks you added recently.
Is there a version of resolveComponent(tagName)
that resolves all know components?
Because this 3rd mechanism is very close to what we already have with context based providers: https://github.com/wooorm/xdm#optionsproviderimportsource (open details for diff).
What if @mdx-js/vue
used resolveComponent
? https://github.com/mdx-js/mdx/blob/7ff979c8dc2d6f75a6190c84eaffc802e294b0d2/packages/vue/lib/index.js.
One feature of MDX that folks like a lot is being able to pass p: MyParagraph
, the Vue algorithm you describe does not have that. It might be nice if we find a way that does allow this feature to remain.
There was some discussion somewhere about making the behavior of which component names can be passed configurable.
@mdx-js/mdx@1
, the behavior was that any name, whether div
or Component
, whether written as markdown (*em*
) or JSX (<em>
), can be passed@mdx-js/mdx@2.0.0-rc.1
), the behavior is similar, except that explicit JSX (<em>
) can’t be overwritten.Both have some downsides though. Perhaps an option to change this behavior could also support the behavior you want.
Wasn't sure if the plugins were considered internal, which means breaking changes could happen in patch releases.
That can indeed occur, but you can step around that by using ~
/ pinning it. Less maintenance that a whole fork IMO
Is there a version of resolveComponent(tagName) that resolves all know components?
Not in the public API.
One feature of MDX that folks like a lot is being able to pass p: MyParagraph
If the component destructuring property nodes are flagged explicitly (or the names of unresolved components are shared across plugins), then a recma plugin could rewrite:
const {
h1 = 'h1',
NoteIcon,
} = props.component
to
const {
h1 = 'h1',
NoteIcon = resolveComponent("NoteIcon"),
} = props.component
which combines the best of both worlds.
Not as clean as the provider option, and requires flagging those nodes (should be possible given that the identifiers are being tracked to add _missingReference
calls), but it would enable Vue users to get the best out of xdm.
Not in the public API.
Where is that code even? 🤔 Do you happen to know?
NoteIcon = resolveComponent("NoteIcon"),
How would that work with members? <MyObject.MyComponent />
. And because functions in JS are also objects, people might also use <MyObject />
on its own
That’s why _missingMdxReference
is after it.
_missingMdxReference
is a good idea! Currently we have:
function _createMdxContent() {
const {C} = props.components || ({});
if (!C) _missingMdxReference("C", false);
return <C />;
}
function _missingMdxReference(id, isComponent) {
throw new Error("Expected " + (isComponent ? "component" : "object") + " `" + id + "` to be defined: you likely forgot to import, pass, or provide it.");
}
What if we, by default, turned it into:
function _createMdxContent() {
const {C} = props.components || ({});
- if (!C) _missingMdxReference("C", false);
+ if (!C) C = _missingMdxReference("C", false);
return <C />;
}
And then, somehow, add useMDXComponent
support (the providers currently have useMDXComponents
, note the plural). When this is supported the output somehow:
import {useMDXComponent as _useMDXComponent} from 'some-place'
// …
function _missingMdxReference(id, component) {
const component = useMDXComponent(id)
if (!component) throw new Error("Expected " + (component ? "component" : "object") + " `" + id + "` to be defined: you likely forgot to import, pass, or provide it.");
return component
}
These questions remain open:
Both useMDXComponents
and useMDXComponent
is superfluous:
const components = Object.assing({}, props.components, useMDXComponents())
// …
function _missingMdxReference(id, component) {
const component = useMDXComponent(id)
id
is a serialized identifier, so 'a:b'
, 'div'
, 'Component'
, or 'object.another.component'
<X.Y />
) and assigning? what if X
is passed but Y
is provided, then
adding Y
to X
is weird side-effectWhere is that code even?
Here inside resolveComponent
. Technically @mdx/vue
could "provide" getCurrentInstance().app context.components
, though it wouldn't cover name-casing differences.
What if we, by default, turned it into:
That would be great!
Adding it to every provider and backporting it is some work
What about toggling the addition of useMDXComponent
with a setting, similar to the pragma and provider ones?
How to handle objects?
Vue doesn't allow object namespaces, so useMDXComponent
would only be added if the component
is true. Failing for missing namespaces or objects seems reasonable.
Thanks for your patience going through this use case! Sorry for not providing more details about component resolution upfront 😅
If it sounds good, I'll do a spike by creating a recma plugin to:
missingReference
resolveComponent
call when the component
is truerecma-build-vue-jsx
If it works as nicely as it seems in theory, I'll be happy to contribute 1 and 2 back to XDM, and we can discuss whether to add a setting to enable 3 as well (with an agnostic name such as useMDXComponent
).
4 doesn't need changes to XDM, since adding the plugin to the pipeline should replace any previously assigned Compiler
.
What about toggling the addition of
useMDXComponent
with a setting, similar to the pragma and provider ones?
Yeah, but what would the option be called? Is it something where current providers with useMDXComponents
are “old” / “legacy”, and useMDXComponent
is the modern version? Or are we getting different providers: options.providerIsVue
/ options.providerIsSingular
(?) switches to useMDXComponent
but the default is useMDXComponents
?
Downside of a toggle is, it would work for fine for you as you know so much about xdm/mdx, but there are also many users that don’t read readmes and will be stumped by why things don’t work.
Vue doesn't allow object namespaces, so
useMDXComponent
would only be added if the component is true. Failing for missing namespaces or objects seems reasonable.
Oh so you mean that both id
(the component name) and component
(whether it’s a component or an object) are passed? Smart. But, that’s just for Vue. Everyone else does support them and has this unsolved problem.
Thanks for your patience going through this use case! Sorry for not providing more details about component resolution upfront 😅
<3
If it sounds good, I'll do a spike by creating a recma plugin to:
Sounds perfect! I’ll close this, as the original question is somewhat answered, and we figured out a good way to add something else if needed in the future. We can keep on discussing here, or when you’re ready, we can discuss on a (WIP) PR?
Here's a prototype of the idea above.
Since the recma-build-jsx
can not be removed from the chain, I decided to drop recma-vue-build-jsx
and provide a custom jsx runtime that uses createVNode
.
The missingReference
function is replaced with a strict version of resolveComponent
.
It seems to be working nicely:
resolveComponent
calls are static which allows automatic import with unplugin-vue-components
.I'll be making a PR to replace const
with let
in component definitions, which should have no practical downsides.
Would also love it if we could flag the estree nodes relative to the assertion of missing components so that my implementation can be more resilient to changes (though it should be easy to update as xdm evolves).
Thanks again for your help thinking through this! The recent addition of missingReference
enabled this solution which is cleaner and should be easier to maintain :smiley:
Hi, I'd like to know if you have plans to start releasing plugins for the new
recma
ecosystem.That would make it possible to customize MDX compilation in the same way that remark and rehype plugins allow to customize Markdown and HTML processing.
For example, I have plans to create a recma plugin that users can add to convert JSX to Vue render functions, skipping the need for a babel transform. Right now the only way to use that while leveraging all the plugins in xdm, is to fork.