-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(mdx): convert remark-images-to-component plugin to a rehype plugin #10697
fix(mdx): convert remark-images-to-component plugin to a rehype plugin #10697
Conversation
🦋 Changeset detectedLatest commit: 2940a6b The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@@ -74,7 +74,7 @@ function getRehypePlugins(mdxOptions: MdxOptions): PluggableList { | |||
} | |||
} | |||
|
|||
rehypePlugins.push(...mdxOptions.rehypePlugins); | |||
rehypePlugins.push(...mdxOptions.rehypePlugins, rehypeImageToComponent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that's a bit annoying and makes this breaking. Previously, it was possible for a rehype plugin to change the options passed to the component, whereas right now it's just impossible. Wonder if anyone depended on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my understanding (don't hesitate to correct me), the properties on a node could be unrelated with HTML attributes. So I think it is still possible to pass options to the Image
component. Using a rehype plugin (or even a remark plugin), the consumer could add the options as properties on images. The properties are then mapped to attributes in rehype-images-to-component
(only src
is hard coded) so the Image
component will receive those options. However, the consumer needs to target img
instead of astro-image
as node name. So, yes I see the breaking change now (and the major
detected by changeset is justified).
However, I don't see how we could fix the linked issue without this change... From what I saw with a quick search (so I certainly missed some use cases), there are essentially two use cases:
- those who replicate existing rehype plugins to handle
astro-image
nodes - those who duplicate their logic to handle
astro-image
in addition toimg
So I think this change is beneficial (if my understanding is correct). But it is definitely a breaking change.
That said, perhaps we need to check the correct formatting of the options if they exist. In comparison with the remark plugin, I had to add a special case for widths
because it was a string instead of an array (based on existing tests) and I added decodeUri
on src
to handle paths with spaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes previously a rehype plugin could set width to 100 like this:
if (node.name === 'astro-image') {
node.attributes.push({
name: 'width',
type: 'mdxJsxAttribute',
value: '100',
});
}
now after this change it will be like this:
if (node.tagName === 'img') {
node.properties = {
...node.properties,
width: '100',
};
}
if (Array.isArray(value)) { | ||
attrs.push(createArrayAttribute(prop, value)); | ||
} else if (prop === 'widths') { | ||
attrs.push(createArrayAttribute(prop, String(value).split(' '))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't realize when creating the initial issue for this that properties were all turned into "sanitizied" strings I sort of figured if given a hProperty by a remark plugin like width: [100,200]
it would become width="[100,200]"
but you've solved this how I initially implemented the remark version's ability to add custom properties to the image component, by putting a condition on widths.
A condition will also need to be added for denisities
since this is also an array option, and
if (Array.isArray(value)) {
attrs.push(createArrayAttribute(prop, value));
}
Can be removed because it will never be true based on how unified is sanitizing things
But the reason that we implemented this Array.isArray method was to empower other image services that might make use of their own custom properties that want to accept arrays, instead of hard coding special cases for widths and denisities, but I can't exactly see another way to do this and the imageService if they wanted to make use of these properties could handing that property receiving a string of values and just .split(' ')
it themselves for its users
@Princesseuh What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes are ready (not pushed yet, I'm waiting for any other changes).
Isn't inferSize
likely to cause problems as well? Since it is supposed to be a boolean, "false"
will be evaluated as true in imageService
, right? However I don't see how to convert the attribute's value to a boolean. The value
property in MdxJsxAttribute
is typed as string | MdxJsxAttributeValueExpression | null | undefined
. Maybe we could use an empty string if the value is not "true"
, something like value: value === "true" ? value : ""
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As of right now remote images are not processed by ![]()
syntax, but if we did allow remote images inferSize would most likely be turned on by default and it would the need to be able to be turned off in a granular way with a remark/rehype plugin youre right but I don't think that we need to handle that here now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, considering that remote images are not supported at this time, it's fine to not have the best support for inferSize.
Brilliant work, thank you for tackling this! |
importedImages.set(src, importName); | ||
} | ||
|
||
// Build a component that's equivalent to <Image src={importName} alt={node.alt} title={node.title} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Build a component that's equivalent to <Image src={importName} alt={node.alt} title={node.title} /> | |
// Build a component that's equivalent to <Image src={importName} {...attributes} /> |
|
||
if (Array.isArray(value)) { | ||
attrs.push(createArrayAttribute(prop, value)); | ||
} else if (prop === 'widths') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hate this, but I guess there's no choices. As Oliver said, this needs to also handle densities
.
I would like to see a comment on top of these to explain why it's hard coded
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just pushed without seeing the edit. I'm adding the comment now.
Excellent thanks for being so quick to respond @ArmandPhilippot The only thing that's left, and I don't mind commit directly here if you'd rather that, but this PR #10754 is going into It's a pretty simple check for whether or not the url includes |
@OliverSpeir I don't mind adding the changes to this PR. However its seems #10754 is not ready yet. Like bluwy says, it does not handle all the possible cases.
Maybe I'm overthinking, but I think it is a little more complex than that. I see different possible paths format:
So import { isAbsolute } from 'node:path';
// Not the best name since `image.jpg` is also a relative source.
const isRelativeSrc = (src: string) => src.startsWith('./') || src.startsWith('../')
const isPathAlias = (src: string) => "???"
const normalizedSrc = isRelativeSrc(src) || isAbsolute(src) || isPathAlias(src) ? src : `./${src}`; But I'm having trouble finding a way to detect if it is a path alias since we cannot rely on the first character ( Another approach could be to resolve the path (ie. |
@ArmandPhilippot it looks like #10754 ended up moving the logic out of this plugin so we are off the hook, I think the actual resolve is definitely the right solution
Youre right, I definitely had under thought this solution originally So moving forward when that PR is merged we can update the branch of this PR and have no worries, I'm going to request a review here from @bluwy as well then I think we're good |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can tell, this looks good to me. However, I didn't know that we support serializing hast properties into MDX for the <Image />
component. Personally I don't think we should support it since that implies we endorse <img src="..." widths="...">
, which widths
is non-standard.
I would instead reserve a new space under node.data.astroImageProps
(perhaps) so we can control what it means. Since this is a major, maybe worth doing now? But I'm not sure if that's worth changing now if we have an ecosystem that already depends on it.
The actual |
The reason for doing it this way is that people can use already existing plugins to add attributes vs an Astro specific one. It wasn't like this a few versions ago (it was closer to what you suggested) and people often complained about remark plugins not working. I wanted to avoid needing a thousand |
My point is that by allowing these properties to exist in
I think we should continue to support remark/rehype plugins that add additional attributes. But if the attribute is an Astro specific one (which means the remark/rehype plugin isn't general purpose in the first place), I think we should have the Astro specific props in a different place instead of overloading |
Hmm, how would we handle this? The situation is the following:
I don't see how we can distinguish outside of having a list of all attributes that img supports, but that'd still require fishing them out of the node's attributes, so they're still at the wrong place. The only solution I see is having a |
I think we can handle that like this:
const str = `<Image
{...{
...${JSON.stringify(node.properties)},
...${JSON.stringify(node.data.astroImageProps}
}}
src="..."
/>` This way we don't need to check how |
Brilliant, I love it.
Also on board for this too, as the current plugin I have can be forked very easily |
But then generic plugins cannot add Astro specific properties. What people want to do is be able to use plugins that allow this kind of syntax:
where they'll mix both attributes specific to Astro and generic ones per image. I'm fine with this solution, to be honest, but I'm not sure it quite answer user need |
Hmm, I didn't expect we want a generic remark plugin to pass along attributes like this. It makes the interpretation of I agree that it's nice if we can rely on generic plugins to pass on to our I think overall, I'm concern of the current design, but not blocking the PR if this is the design we want to go for now. We can re-think about it again if there are issues. |
16de3ae
to
0a8e8f2
Compare
@ArmandPhilippot I've discussed this with Erika and the team, and I'm ok with moving forward with this for now. My comments aren't quite a blocker but perhaps we can implement a few of the ideas non-breakingly in minors. Can you help resolve the conflicts, and then change the base branch to https://github.com/withastro/astro/tree/mdx-v3 ? I'm also planning to do more breaking changes so I created a new feature branch for it. |
Image related rehype plugins was not working because the MDX integration used a remark plugin. Converting it to a rehype plugin allow us to keep the same behavior but after any user's plugins run. Fix withastro#10643
0a8e8f2
to
2940a6b
Compare
@bluwy All done, the merge conflict has been resolved and I updated the base branch! |
Awesome thanks! |
* fix(mdx): convert remark-images-to-component plugin to a rehype plugin (#10697) * Remove fs read for MDX transform (#10866) * Tag MDX component for faster checks when rendering (#10864) * Use unified plugin only for MDX transform (#10869) * Only traverse children and handle mdxJsxTextElement when optimizing (#10885) * Rename to `optimize.ignoreComponentNames` in MDX (#10884) * Allow remark/rehype plugins added after mdx to work (#10877) * Improve MDX optimize with sibling nodes (#10887) * Improve types in rehype-optimize-static.ts * Rename `ignoreComponentNames` to `ignoreElementNames` I think this better reflects what it's actually doing * Simplify plain MDX nodes in optimize option (#10934) * Format code * Minimize diff changes * Update .changeset/slimy-cobras-end.md Co-authored-by: Sarah Rainsberger <[email protected]> --------- Co-authored-by: Armand Philippot <[email protected]> Co-authored-by: Sarah Rainsberger <[email protected]>
Fix #10643
Note:
pnpm exec changeset
detects the change asmajor
. I think it is more of apatch
. I still added the changeset.Changes
The MDX integration was transforming images too early so it wasn't possible to use additional rehype plugins on images. So I converted the
remark-images-to-component
plugin with a rehype plugin (rehype-images-to-component
).Note: The
widths
attribute had numbers as value once converted to a MDX component. Now we have strings as value since we are receiving a string fromnode.properties
. Not sure if it is important or if we need an extra parsing to convert the value to a number if the prop name iswidths
. Everything seems to work correctly.Example
Before:
Now:
Testing
I wasn't sure how to add a test in the mdx package so I only tested with:
pnpm run test
pnpm link
and by addingrehype-figure
since it was mentioned in the issue.Docs
I don't think an update to the doc is necessary. It was a bug that prevented the expected behavior. Now users can use rehype plugins for their images before the MDX integration convert the images to components.