-
-
Notifications
You must be signed in to change notification settings - Fork 6
Ability to add width and height to the image #3
Comments
Thanks for opening this issue proposal! I think it’s a neat idea to support this. Currently this plugin only supports markdown style images, which only support Input: <img alt="Nessie" src="./nessie.png" title="The Loch Ness monster" width="480" height="320" /> Output (simplified): import __nessie__ from './nessie.png';
export default function MDXContent() {
return <img alt="Nessie" src={__nessie__} title="The Loch Ness monster" width="480" height="320" />
} Of course @collegewap would this solve your problem? |
@remcohaszing I was about to create the similar issue but saw your comment. I'd very much like if you could also support |
@collegewap I found one remark plugin for that https://github.com/arobase-che/remark-attr |
I just remembered I use a different solution to apply custom styling. I added some utility classes which can be applied on a preceding <span class="is-480x320"></span>
[Nessie](nessie.png 'The Loch Ness monster') .is-480x320 {
display: none;
}
.is-480x320 img {
width: 480px;
height: 320px;
} This is just a workaround. I still want to implement the proposed solution. |
Wow! a neat trick w/o breaking md syntax. |
Still not actively working on this, but I keep thinking about this issue. This only applies to markdown ( When using MDX files, one could simply use the following instead: import CustomImage from './custom-image';
import nessie from './nessie.png';
<CustomImage src={nessie} customProp="foo" /> There’s a difference between markdown and MDX on an AST level which really makes a difference of how this should work. |
After some chatting with @wooorm he came up with the idea to use determine the image width and height from the file on disk, then insert those values as props. I believe this is also a great solution to tackle the problem in the OP, which is to avoid cumulative layout shift. This also means the markdown content doesn’t have to change. However, this solution doesn’t align with the goal of this remark plugin, which is to make bundlers resolve images sources. This could be created as a separate plugin. |
Issue with this approach is it's not portable i.e. images won't render on static markdown viewers like on Github, obsidian etc. |
Wrote a plugin to do what @remcohaszing suggested, passing the images' JavaScript version import { visit } from 'unist-util-visit'
import { is } from 'unist-util-is'
import getImageSize from 'image-size'
const rehypeImageSizes = (options) => {
return (tree) => {
visit(tree, (node) => {
if (
!is(node, { type: 'element', tagName: 'img' }) ||
!node.properties ||
typeof node.properties.src !== 'string'
) {
return
}
const imagePath = `${options?.root ?? ''}${node.properties.src}`
const imageSize = getImageSize(imagePath)
node.properties.width = imageSize.width
node.properties.height = imageSize.height
})
}
}
export { rehypeImageSizes } TypeScript version import type { Plugin } from 'unified'
import { Root, Element } from 'hast'
import { visit } from 'unist-util-visit'
import { is } from 'unist-util-is'
import getImageSize from 'image-size'
type Options = Partial<{
/** Images root directory. Used to build the path for each image `(path = root image.src`). */
root: string
}>
const rehypeImageSizes: Plugin<[Options?], Root> = (options) => {
return (tree) => {
visit(tree, (node) => {
if (
!is<Element>(node, { type: 'element', tagName: 'img' }) ||
!node.properties ||
typeof node.properties.src !== 'string'
) {
return
}
const imagePath = `${options?.root ?? ''}${node.properties.src}`
const imageSize = getImageSize(imagePath)
node.properties.width = imageSize.width
node.properties.height = imageSize.height
})
}
}
export { rehypeImageSizes } And on the bundleMDX(mdxSource, {
xdmOptions: (options) => ({
...options,
rehypePlugins: [
...(options.rehypePlugins ?? []),
[rehypeImageSizes, { root: `${process.cwd()}/public` }],
],
}),
}) On // Before
<Image
src="/images/image.jpg"
alt="An image"
width="500"
height="250"
/>
// After
![An image](/images/image.jpg) |
@AgustinBrst That’s really cool! I have some small suggestions:
import type { Plugin } from 'unified'
import { Root, Element } from 'hast'
import { visit } from 'unist-util-visit'
import getImageSize from 'image-size'
const rehypeImageSizes: Plugin<[], Root> = () => {
return (tree, file) => {
// XXX Not sure if the `Element` type annotation is even necessary.
visit(tree, { type: 'element', tagName: 'img' }, (node: Element) => {
if (typeof node?.properties.src !== 'string') {
return
}
const imagePath = `${file.path}${node.properties.src}`
const imageSize = getImageSize(imagePath)
node.properties.width = imageSize.width
node.properties.height = imageSize.height
})
}
}
export default rehypeImageSizes Unfortunately this won’t work with |
Thanks for the suggestions @remcohaszing! 😄🙌 |
Why use rehype instead of remark, it seems like a good feature to add to remark-mdx-images |
Because |
Maybe I didn't express myself clearly I copied the remark-mdx-images to my local and made some changes import { dirname, join } from 'path'
import { visit } from 'unist-util-visit'
import sizeOf from 'image-size'
const urlPattern = /^(https?:)?\//
const relativePathPattern = /\.\.?\//
const remarkMdxImages =
({ resolve = true } = {}) =>
(ast, file) => {
const imports = []
const imported = new Map()
visit(ast, 'image', (node, index, parent) => {
let { alt = null, title, url } = node
if (urlPattern.test(url)) {
return
}
if (!relativePathPattern.test(url) && resolve) {
url = `./${url}`
}
let name = imported.get(url)
if (!name) {
name = `__${imported.size}_${url.replace(/\W/g, '_')}__`
imports.push({
type: 'mdxjsEsm',
value: '',
data: {
estree: {
type: 'Program',
sourceType: 'module',
body: [
{
type: 'ImportDeclaration',
source: { type: 'Literal', value: url, raw: JSON.stringify(url) },
specifiers: [
{
type: 'ImportDefaultSpecifier',
local: { type: 'Identifier', name },
},
],
},
],
},
},
})
imported.set(url, name)
}
const textElement = {
type: 'mdxJsxTextElement',
name: 'img',
children: [],
attributes: [
{ type: 'mdxJsxAttribute', name: 'alt', value: alt },
{
type: 'mdxJsxAttribute',
name: 'src',
value: {
type: 'mdxJsxAttributeValueExpression',
value: name,
data: {
estree: {
type: 'Program',
sourceType: 'module',
comments: [],
body: [
{
type: 'ExpressionStatement',
expression: { type: 'Identifier', name },
},
],
},
},
},
},
],
}
if (title) {
textElement.attributes.push({
type: 'mdxJsxAttribute',
name: 'title',
value: title,
})
}
const imagePath = join(dirname(file.path), url)
const imageSize = sizeOf(imagePath)
textElement.attributes.push(
...[
{
type: 'mdxJsxAttribute',
name: 'width',
value: imageSize.width,
},
{
type: 'mdxJsxAttribute',
name: 'height',
value: imageSize.height,
},
],
)
parent.children.splice(index, 1, textElement)
})
ast.children.unshift(...imports)
}
export default remarkMdxImages const components = {
img: (props: any) => <img {...props} loading="lazy" />
}
<MDXProvider components={components}>
// ...
</MDXProvider> {
test: /\.mdx/,
exclude: /node_modules/,
use: [
'babel-loader',
{
loader: '@mdx-js/loader',
options: {
remarkPlugins: [
remarkGfm,
remarkMdxImages,
remarkDirective,
admonitionsPlugin,
],
providerImportSource: '@mdx-js/react',
},
},
{
loader: getAbsPath('scripts/mdx-loader/index.cjs'),
},
],
}, |
There is no question in your comment? I don’t get it. |
A little late to the party, but you may find Quick notes: All local and remote images are cached at parse time in a folder of your choosing. As I'm typing this, I realize I need to handle local images differently because there will be duplication of local images (one in the original location, one in the cache directory). Also, images are resolved from the file where they are referenced. For example, a markdown file in It's a bit more verbose than |
I created |
I am using this package along with mdx-bundler and Next Image. It would be great if width and height can be added to local images. This will help in avoiding cumulative layout shift
The text was updated successfully, but these errors were encountered: