diff --git a/README.md b/README.md
index d0a6f28..148df77 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,17 @@ behaviour. If this is set to false, this behaviour is removed and URLs are no lo
allows to import images from `node_modules`. If this is disabled, local images can still be imported
by prepending the path with `./`.
+### `includeDimensions`
+
+It can be very useful to have the dimensions of images associated to the img tag. Including the image
+size can avoid content shift. Toggle this option on to add height/width attributes to your tags when the images
+are imported via relative import. You will also need to set `cwd` option for this to work
+
+### `cwd`
+
+The current working directory of the file being parsed. Used in combination with `includeDimensions` to load images
+with their specific width/height attributes.
+
### License
[MIT](LICENSE.md) @ [Remco Haszing](https://github.com/remcohaszing)
diff --git a/__fixtures__/size/expected.jsx b/__fixtures__/size/expected.jsx
new file mode 100644
index 0000000..d25ddc8
--- /dev/null
+++ b/__fixtures__/size/expected.jsx
@@ -0,0 +1,32 @@
+/*@jsxRuntime automatic @jsxImportSource react*/
+import __0___image_png__ from './image.png';
+function _createMdxContent(props) {
+ const _components = Object.assign(
+ {
+ p: 'p',
+ img: 'img',
+ },
+ props.components,
+ );
+ return (
+ <_components.p>
+ <_components.img
+ alt="Alt text"
+ src={__0___image_png__}
+ width="512"
+ height="512"
+ />
+
+ );
+}
+function MDXContent(props = {}) {
+ const { wrapper: MDXLayout } = props.components || {};
+ return MDXLayout ? (
+
+ <_createMdxContent {...props} />
+
+ ) : (
+ _createMdxContent(props)
+ );
+}
+export default MDXContent;
diff --git a/__fixtures__/size/image.png b/__fixtures__/size/image.png
new file mode 100644
index 0000000..9490ffc
Binary files /dev/null and b/__fixtures__/size/image.png differ
diff --git a/__fixtures__/size/input.md b/__fixtures__/size/input.md
new file mode 100644
index 0000000..806b3be
--- /dev/null
+++ b/__fixtures__/size/input.md
@@ -0,0 +1 @@
+![Alt text](./image.png)
diff --git a/__fixtures__/size/options.json b/__fixtures__/size/options.json
new file mode 100644
index 0000000..344bcd8
--- /dev/null
+++ b/__fixtures__/size/options.json
@@ -0,0 +1,4 @@
+{
+ "includeDimensions": true,
+ "imagesDirectory": "__fixtures__/size"
+}
diff --git a/index.ts b/index.ts
index 679656e..d7ae53d 100644
--- a/index.ts
+++ b/index.ts
@@ -1,3 +1,6 @@
+import { join } from 'path';
+
+import { imageSize } from 'image-size';
import { Image, Parent, Root } from 'mdast';
import { MdxjsEsm, MdxJsxTextElement } from 'mdast-util-mdx';
import { Plugin } from 'unified';
@@ -13,20 +16,61 @@ export interface RemarkMdxImagesOptions {
* @default true
*/
resolve?: boolean;
+ /**
+ * Adds the width and height attributes to the image tag.
+ *
+ * @default false
+ */
+ includeDimensions?: boolean;
+ /**
+ * The current working directory of the file being parsed. Used in combination with
+ * `includeDimensions` to load images with their specific width/height attributes.
+ */
+ cwd?: string;
}
// eslint-disable-next-line unicorn/no-unsafe-regex
const urlPattern = /^(https?:)?\//;
const relativePathPattern = /\.\.?\//;
+// eslint-disable-next-line unicorn/no-unsafe-regex
+const absolutePathRegex = /^(?:[a-z]+:)?\/\//;
+
+/**
+ * Gets the size of the image.
+ *
+ * @param src The image source.
+ * @returns the image dimensions
+ */
+function getImageSize(src: string):
+ | {
+ /**
+ * Width of the image.
+ */
+ width?: number;
+ /**
+ * Height of the image.
+ */
+ height?: number;
+ }
+ | undefined {
+ if (absolutePathRegex.test(src)) {
+ return undefined;
+ }
+
+ return imageSize(src);
+}
/**
* A Remark plugin for converting Markdown images to MDX images using imports for the image source.
*/
const remarkMdxImages: Plugin<[RemarkMdxImagesOptions?], Root> =
- ({ resolve = true } = {}) =>
+ ({ cwd, includeDimensions = false, resolve = true } = {}) =>
(ast) => {
const imports: MdxjsEsm[] = [];
const imported = new Map();
+ if (includeDimensions && !cwd) {
+ throw new Error('The cwd option is required when includeDimensions is enabled.');
+ }
visit(ast, 'image', (node: Image, index: number | null, parent: Parent | null) => {
let { alt = null, title, url } = node;
@@ -38,6 +82,8 @@ const remarkMdxImages: Plugin<[RemarkMdxImagesOptions?], Root> =
}
let name = imported.get(url);
+ const size =
+ includeDimensions && imagesDirectory ? getImageSize(join(imagesDirectory, url)) : undefined;
if (!name) {
name = `__${imported.size}_${url.replace(/\W/g, '_')}__`;
@@ -93,6 +139,20 @@ const remarkMdxImages: Plugin<[RemarkMdxImagesOptions?], Root> =
if (title) {
textElement.attributes.push({ type: 'mdxJsxAttribute', name: 'title', value: title });
}
+ if (size?.width) {
+ textElement.attributes.push({
+ type: 'mdxJsxAttribute',
+ name: 'width',
+ value: String(size.width),
+ });
+ }
+ if (size?.height) {
+ textElement.attributes.push({
+ type: 'mdxJsxAttribute',
+ name: 'height',
+ value: String(size.height),
+ });
+ }
parent!.children.splice(index!, 1, textElement);
});
ast.children.unshift(...imports);
diff --git a/package-lock.json b/package-lock.json
index 706114c..b557134 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,11 +9,13 @@
"version": "2.0.0",
"license": "MIT",
"dependencies": {
+ "@types/mdast": "^3.0.0",
+ "image-size": "^1.0.2",
+ "unified": "^10.0.0",
"unist-util-visit": "^4.0.0"
},
"devDependencies": {
"@mdx-js/mdx": "^2.0.0",
- "@types/mdast": "^3.0.0",
"@types/node": "^18.0.0",
"@types/prettier": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^5.0.0",
@@ -26,11 +28,10 @@
"prettier": "^2.0.0",
"tsx": "^3.0.0",
"typescript": "^4.0.0",
- "unified": "^10.0.0",
"uvu": "^0.5.0"
},
"engines": {
- "node": ">=12.2.0"
+ "node": ">=14.0.0"
}
},
"node_modules/@babel/code-frame": {
@@ -441,7 +442,6 @@
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
"integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==",
- "dev": true,
"dependencies": {
"@types/unist": "*"
}
@@ -852,7 +852,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
- "dev": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
@@ -2507,8 +2506,7 @@
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "dev": true
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
@@ -2945,6 +2943,20 @@
"node": ">= 4"
}
},
+ "node_modules/image-size": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz",
+ "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==",
+ "dependencies": {
+ "queue": "6.0.2"
+ },
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -2993,8 +3005,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/inline-style-parser": {
"version": "0.1.1",
@@ -3081,7 +3092,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -3244,7 +3254,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
- "dev": true,
"engines": {
"node": ">=12"
},
@@ -5080,6 +5089,14 @@
"node": ">=6"
}
},
+ "node_modules/queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "dependencies": {
+ "inherits": "~2.0.3"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -5857,7 +5874,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
"integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==",
- "dev": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
@@ -5970,7 +5986,6 @@
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz",
"integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==",
- "dev": true,
"dependencies": {
"@types/unist": "^2.0.0",
"bail": "^2.0.0",
@@ -6160,7 +6175,6 @@
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.4.tgz",
"integrity": "sha512-KI+7cnst03KbEyN1+JE504zF5bJBZa+J+CrevLeyIMq0aPU681I2rQ5p4PlnQ6exFtWiUrg26QUdFMnAKR6PIw==",
- "dev": true,
"dependencies": {
"@types/unist": "^2.0.0",
"is-buffer": "^2.0.0",
@@ -6190,7 +6204,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz",
"integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==",
- "dev": true,
"dependencies": {
"@types/unist": "^2.0.0",
"unist-util-stringify-position": "^3.0.0"
@@ -6204,7 +6217,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz",
"integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==",
- "dev": true,
"dependencies": {
"@types/unist": "^2.0.0"
},
@@ -6217,7 +6229,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz",
"integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==",
- "dev": true,
"dependencies": {
"@types/unist": "^2.0.0"
},
diff --git a/package.json b/package.json
index a9a8fb0..ed84802 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
},
"dependencies": {
"@types/mdast": "^3.0.0",
+ "image-size": "^1.0.2",
"unified": "^10.0.0",
"unist-util-visit": "^4.0.0"
},