npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

gatsby-remark-link-summary

v0.1.4

Published

Gatsby plugin to scrape link nodes and replace the link nodes to your designed summary nodes using scraped data

Downloads

7

Readme

gatsby-remark-link-summary

npm version test License: MIT

This plugin scrapes link nodes and replaces the link nodes to summary nodes that you designed using scraped data (e.g. og:title, og:image, og:description).

Example Output

This plugin can convert text links in a markdown file to links with images as follows:

from

[--small-card--](https://www.npmjs.com/package/gatsby-remark-link-summary)
[--large-card--](https://github.com/blyoa/gatsby-remark-link-summary)

to

card with small image card with large image

Installation

npm install gatsby-remark-link-summary

Usage

// In your gatsby-config.js
plugins: [
  {
    resolve: `gatsby-transformer-remark`,
    options: {
      plugins: [
        {
          // put `gatsby-remark-link-summary` before `gatsby-remark-images`
          // if generated nodes by `generator` includes img elements and
          // the image nodes should be processed by `gatsby-remark-images`
          resolve: `gatsby-remark-link-summary`,
          options: {
            cacheRootDirPath: 'path/to/cache',
            destinationSubDirPath: 'path/to/sub/dir',
            sites: [
              {
                pattern: /^https:\/\/github\.com\/.*/,
                generator: async ({ metadata: { url, title, image }, cacheRemoteFile }) => {
                  const deployedPath = await cacheRemoteFile(image, true)
                  return `<a href="${url}"><img src="${deployedPath}"/></a>`
                },
                rules: [
                  require('metascraper-url')(),
                  require('metascraper-image')(),
                ],
                gotOptions: {
                  // e.g.
                  // timeout: ...
                  // retry: ...
                },
                cacheExpirationSecond: 60 * 60 * 24 * 10, // 10 days
              },
              {
                // replace link nodes that have the text "--summary-card--"
                // e.g.
                // [--summary-card--](https://example.com)
                pattern: (node) => {
                  if (node.children.length === 0) return false
                  const [firstChild] = node.children
                  return  (firstChild.type === 'text' && firstChild.value === '--summary-card--')
                  // NOTE: The URL of a link node can be obtained from `node.url`
                },
                generator: async ({ metadata: { url, title, image }, cacheRemoteFile }) => {
                  const deployedPath = await cacheRemoteFile(image, true)
                  return `<a href="${url}"><img src="${deployedPath}"/></a>`
                },
                rules: [
                  require('metascraper-url')(),
                  require('metascraper-image')(),
                ],
              },
            ],
          },
        },
      ],
    },
  },
],

Options

| name | type | description | |:---|:---|:---| | cacheRootDirPath | string \| undefined | A directory to place remote files fetched by cacheRemoteFile of HTMLGeneratorParams and a catalog file about the fetched files and scraped metadata. If this option is undefined, the catalog file is not saved. | | destinationSubDirPath | string | A subdirectory of the directory for built artifacts (i.e., the public directory) to place remote files fetched in the second argument of generator. | | sites | SiteOptions[] | Node generator options for sites. |

SiteOptions

| name | type | description | |:---|:---|:---| | pattern | RegExp \| (node: Link) => boolean | A target link node to be substituted. RegExp specifies URLs of the link node to be substituted, and the function returns whether the link node should be substituted or not. Link is the type defined in mdast package. | | generator | (params: HTMLGeneratorParams) => string \| Node \| Promise<string \| Node> | A function to generate the node that substitute a link node. | | rules | Rule[] | Rules for metascraper. | | gotOptions | Options[] \| undefined | Options for got. | | cacheExpirationSecond | number \| undefined | The expiration second of scraped metadata and fetched remote files. The metadata and the path to the fetched remote file are reused until this expiration second reaches.If this option is undefined, the fetched metadata and the fetched remote files never expire. |

HTMLGeneratorParams

| name | type | description | |:---|:---|:---| | metadata | Record<string, string> | Metadata scraped by metascraper. | | orignalNode | Link | An original link node that generator substitutes. | | cacheRemoteFile | (fileURL: string, persistent?: boolean) => Promise<string> | A function to fetch a remote file and returns the path to the fetched file in destinationSubDirPath. fileURL is the URL of the remote file. When persistent is true, the fetched file is also saved in cachedRootDirPath. |

Example Settings

Card With Small Image

card with small image

gatsby-config.js

const descriptionRule = require("metascraper-description")
const imageRule = require("metascraper-image")
const titleRule = require("metascraper-title")
const urlRule = require("metascraper-url")

module.exports = {
  // snip...
  plugins: [
    // snip...
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-link-summary`,
            options: {
              cacheRootDirPath: "cache/link-summary",
              destinationSubDirPath: "link-summary",
              sites: [
                {
                  pattern: /^https:\/\/www\.npmjs\.com\/.*/,
                  generator: async ({
                    metadata: { image, url, title, description },
                    cacheRemoteFile,
                  }) => {
                    const filePath = await cacheRemoteFile(image, true)
                    return `
                      <div class="summary-card">
                        <a href="${url}">
                          <img
                            class="summary-card__image"
                            src="${filePath}"
                          />
                          <div class="summary-card__description">
                            <div class="summary-card__description__title"
                              >${title}</div
                            >
                            <div class="summary-card__description__summary"
                              >${description}</div
                            >
                            <div class="summary-card__description__url"
                              >${url}</div
                            >
                          </div>
                        </a>
                      </div>
                    `
                  },
                  rules: [
                    urlRule(),
                    titleRule(),
                    imageRule(),
                    descriptionRule(),
                  ],
                },
              ],
            },
          },
          // snip...
        ],
      },
    },
    // snip...
  ],
}

your-style-file.css

/* import css, e.g. from gatsby-browser.js */
.summary-card {
  line-height: 1.5;
}

.summary-card a {
  color: inherit;
  text-decoration: none;
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  border: 1px solid #e5e5e5;
  border-radius: 3px;
}

.summary-card__image {
  width: 14rem;
  height: 10rem;
  object-fit: cover;
  border-inline-start: 1px solid #e5e5e5;
}

.summary-card__description {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 0 1.2rem;
  overflow: hidden;
}

.summary-card__description__title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  font-weight: 700;
  max-height: calc(2rem * 1.5);
  overflow: hidden;
}

.summary-card__description__summary {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  margin-block-start: 0.4rem;
  font-size: 0.8rem;
  max-height: calc(1.6rem * 1.5);
  overflow: hidden;
  color: #a3a3a3;
}

.summary-card__description__url {
  margin-block-start: 0.6rem;
  font-size: 0.8rem;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #a3a3a3;
}

Card With Large Image

card with large image

gatsby-config.js

const descriptionRule = require("metascraper-description")
const imageRule = require("metascraper-image")
const titleRule = require("metascraper-title")
const urlRule = require("metascraper-url")

module.exports = {
  // snip...
  plugins: [
    // snip...
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-link-summary`,
            options: {
              cacheRootDirPath: "cache/link-summary",
              destinationSubDirPath: "link-summary",
              sites: [
                {
                  pattern: /^https:\/\/github\.com\/.*/,
                  generator: async ({
                    metadata: { image, url, title, description },
                    cacheRemoteFile,
                  }) => {
                    const filePath = await cacheRemoteFile(image, true)
                    return `
                      <div class="large-image-summary-card">
                        <a href="${url}">
                          <img
                            class="large-image-summary-card__image"
                            src="${filePath}"
                          />
                          <div class="large-image-summary-card__description">
                            <div class="large-image-summary-card__description__title"
                              >${title}</div
                            >
                            <div class="large-image-summary-card__description__summary"
                              >${description}</div
                            >
                            <div class="large-image-summary-card__description__url"
                              >${url}</div
                            >
                          </div>
                        </a>
                      </div>
                    `
                  },
                  rules: [
                    urlRule(),
                    titleRule(),
                    imageRule(),
                    descriptionRule(),
                  ],
                },
              ],
            },
          },
          // snip...
        ],
      },
    },
    // snip...
  ],
}
/* import css, e.g. from gatsby-browser.js */
.large-image-summary-card {
  line-height: 1.5;
}

.large-image-summary-card a {
  display: flex;
  color: inherit;
  text-decoration: none;
  flex-direction: column;
  border: 1px solid #e5e5e5;
  border-radius: 3px;
}

.large-image-summary-card__image {
  width: 100%;
  object-fit: cover;
  border-block-end: 1px solid #e5e5e5;
}

.large-image-summary-card__description {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 1.2rem;
}

.large-image-summary-card__description__title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  font-weight: 700;
  max-height: calc(2rem * 1.5);
  overflow: hidden;
}

.large-image-summary-card__description__summary {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  margin-top: 0.2rem;
  font-size: 0.8rem;
  max-height: calc(1.6rem * 1.5);
  overflow: hidden;
  color: #a3a3a3;
}

.large-image-summary-card__description__url {
  margin-top: 0.4rem;
  font-size: 0.8rem;
  color: #a3a3a3;
  overflow: hidden;
  text-overflow: ellipsis;
}