Skip to content

fxxr/remark-picture

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“Έ remark-picture

npm version License: MIT

A Remark plugin that converts Markdown <img> elements into modern <picture> elements with responsive and next-gen image formats (WebP, AVIF, etc.).

Works great together with picture-converter β€” a companion tool that converts your legacy .png, .jpg, and .gif files into efficient next-gen formats.

Why use remark-picture?

Traditional Markdown images are static and can’t take advantage of modern browser optimizations. With remark-picture, you can:

βœ… Automatically generate <picture> elements from ![alt](path.png)
βœ… Serve WebP and AVIF images for smaller size and faster loading
βœ… Define responsive sources via media queries
βœ… Add pixel-density variants (1x, 2x, etc.)
βœ… Use custom URL templates to integrate with CDNs or build pipelines
βœ… Stay fully typed with TypeScript

Installation

npm install remark-picture

πŸš€ Quick Start

import {unified} from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'
import remarkPicture from 'remark-picture'

const file = await unified()
  .use(remarkParse)
  .use(remarkPicture)
  .use(remarkRehype)
  .use(rehypeStringify)
  .process('![Sunset](images/sunset.jpg)')

console.log(String(file))

✨ Example

Input Markdown

![Sunset](images/sunset.jpg)

Output HTML

<picture>
  <source srcset="images/sunset.avif" type="image/avif">
  <source srcset="images/sunset.webp" type="image/webp">
  <img src="images/sunset.jpg" alt="Sunset">
</picture>

With Media Queries and Densities

<picture>
  <source srcset="/mobile/sunset.webp, /mobile/[email protected] 2x" 
          type="image/webp" media="(max-width: 480px)">
  <source srcset="/desktop/sunset.webp, /desktop/[email protected] 2x" 
          type="image/webp" media="(min-width: 481px)">
  <img src="images/sunset.jpg" alt="Sunset">
</picture>

🧩 Usage

import {unified} from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'
import remarkPicture from 'remark-picture'

const file = await unified()
  .use(remarkParse)
  .use(remarkPicture, {
    formatMapping: {
      jpg: ['avif', 'webp'],
      png: ['webp']
    },
    media: {
      mobile: {
        urlTemplate: '/mobile/{basename}.{ext}',
        query: '(max-width: 480px)',
        pixelDensity: [1, 2]
      },
      desktop: {
        urlTemplate: '/desktop/{basename}.{ext}',
        query: '(min-width: 481px)',
        pixelDensity: [1, 2]
      }
    }
  })
  .use(remarkRehype)
  .use(rehypeStringify)
  .process('![Sunset](images/sunset.jpg)')

console.log(String(file))

βš™οΈ Options

Option Type Default Description
formatMapping Record<ImageExtension, ImageExtension[]> { jpg/png β†’ avif, webp } Defines which formats to generate from which source
media Record<string, { urlTemplate, query?, pixelDensity? }> – Define responsive sources (e.g. mobile vs desktop)
pixelDensity number 1 Default pixel density multiplier
imageUrlTemplate string – Template for generated image URLs, e.g. "/images/{basename}@{pd}x.{ext}"
imageToPicture (image, convert, pictureBuilder) => Picture – Custom function for full control over generation

πŸ”§ URL Templating

Template strings (for imageUrlTemplate or media.urlTemplate) can use:

Placeholder Example Description
{dir} /assets/images/ Original image directory
{basename} photo Filename without extension
{originalExt} jpg Original image extension
{ext} webp Target image extension
{pd} 2 Pixel density (e.g. 1, 2)
{basepath} /assets/images/photo Directory + basename

Example:

imageUrlTemplate: "/cdn/{basename}@{pd}x.{ext}"

🧩 Custom Conversion

You can fully control how <picture> elements are generated:

const adaptiveOptions: Options = {
  formatMapping: {png: ['webp']},
  media: {
    mobile: {
      query: '(max-width: 480px)',
      urlTemplate: '{dir}mobile/{basename}-{pd}x.{ext}',
      pixelDensity: [2, 3]
    },
    desktop: {
      query: '(min-width: 481px)',
      urlTemplate: '{basepath}.{ext}'
    }
  }
}

function customImageToPicture(image: Image, convert: ConvertFn): Picture | PictureBuilder | undefined {
  if (image.url.includes('icon')) {
    // skip conversion for icons
    return undefined
  }
  if (image.url.startsWith('adaptive-images/')) {
    // use custom options
    return convert(adaptiveOptions)
  }
  // use default behavior
  return convert()
}

...
use(remarkPicture, {imageToPicture: customImageToPicture})

🧰 Default Format Mapping

{
  png: ['avif', 'webp'],
  jpg: ['avif', 'webp'],
  jpeg: ['avif', 'webp'],
  gif: ['webp'],
  apng: ['webp']
}

Contributing

Contributions, issues, and feature requests are welcome!
Feel free to open an issue or submit a pull request.

Related

Picture Converter β€” converts old image formats (.png, .jpg, .gif) to modern, efficient formats (.webp, .avif) automatically.

License

MIT Β© Anatoly Nechaev

About

A Remark plugin that converts Markdown <img> tags into modern <picture> elements with WebP/AVIF sources, responsive media queries, and pixel-density support

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors