{"id":27075857,"url":"https://github.com/evesunmaple/rehype-smart-links","last_synced_at":"2025-10-25T05:33:26.776Z","repository":{"id":286146022,"uuid":"960516934","full_name":"EveSunMaple/rehype-smart-links","owner":"EveSunMaple","description":"A rehype plugin for Astro that adds different styling for internal and external links","archived":false,"fork":false,"pushed_at":"2025-04-05T08:00:19.000Z","size":324,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-10T23:39:52.720Z","etag":null,"topics":["astro","rehype","rehype-plugin"],"latest_commit_sha":null,"homepage":"https://rehype-smart-links.vercel.app/","language":"MDX","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EveSunMaple.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-04-04T15:12:17.000Z","updated_at":"2025-04-05T08:00:22.000Z","dependencies_parsed_at":"2025-04-09T20:04:53.629Z","dependency_job_id":"25de307d-1cea-4e7c-a4fb-caa145f186be","html_url":"https://github.com/EveSunMaple/rehype-smart-links","commit_stats":null,"previous_names":["evesunmaple/rehype-smart-links"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EveSunMaple%2Frehype-smart-links","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EveSunMaple%2Frehype-smart-links/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EveSunMaple%2Frehype-smart-links/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EveSunMaple%2Frehype-smart-links/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EveSunMaple","download_url":"https://codeload.github.com/EveSunMaple/rehype-smart-links/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248317731,"owners_count":21083525,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["astro","rehype","rehype-plugin"],"created_at":"2025-04-06T00:18:11.515Z","updated_at":"2025-10-25T05:33:21.740Z","avatar_url":"https://github.com/EveSunMaple.png","language":"MDX","funding_links":[],"categories":[],"sub_categories":[],"readme":"[English](README.md) | [中文](README.zh-CN.md)\n\n# rehype-smart-links\n\nA rehype plugin for Astro that adds different styles to internal and external links:\n\n- Internal links (pointing to existing pages): Default style with customizable class\n- Internal links (pointing to non-existent pages): Red style with customizable class (similar to Wikipedia's broken links)\n- External links: Adds \"↗\" icon (or custom content) and sets target=\"\\_blank\", with customizable class\n\n## Installation\n\n```bash\n# npm\nnpm install rehype-smart-links\n\n# yarn\nyarn add rehype-smart-links\n\n# pnpm\npnpm add rehype-smart-links\n```\n\n## Usage\n\n### Basic Configuration\n\nAdd the plugin to your Astro configuration:\n\n```js\n// astro.config.mjs\nimport { defineConfig } from \"astro/config\";\nimport rehypeSmartLinks from \"rehype-smart-links\";\n\nexport default defineConfig({\n  markdown: {\n    rehypePlugins: [\n      // Basic usage (default settings)\n      rehypeSmartLinks,\n\n      // Or with custom options\n      [\n        rehypeSmartLinks,\n        {\n          content: { type: \"text\", value: \"↗\" },\n          internalLinkClass: \"internal-link\",\n          externalLinkClass: \"external-link\",\n          brokenLinkClass: \"broken-link\",\n          contentClass: \"external-icon\",\n          target: \"_blank\",\n          rel: \"noopener noreferrer\",\n          publicDir: \"./dist\",\n          routesFile: \"./.smart-links-routes.json\",\n          includeFileExtensions: [\"html\", \"pdf\", \"zip\"], // Only include specific file types\n          includeAllFiles: false // Set to true to include all file types\n        }\n      ]\n    ]\n  }\n});\n```\n\n### Two-Phase Build Process (Recommended)\n\nFor accurate detection of valid internal links, a two-phase build process is recommended:\n\n#### Method 1: Using the Built-in CLI Command (Recommended)\n\nrehype-smart-links provides a built-in CLI command to simplify the routes file generation process:\n\n1. Add a build script to your `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"build:with-routes\": \"astro build \u0026\u0026 rehype-smart-links build \u0026\u0026 astro build\"\n  }\n}\n```\n\n2. Run the script to execute the two-phase build:\n\n```bash\nnpm run build:with-routes\n```\n\nThis command will:\n\n1. First build your site\n2. Use the `rehype-smart-links build` command to scan the build output and generate a routes file\n3. Build the site again, this time using the generated routes information\n\nThe CLI command supports the following options:\n\n```\nOptions:\n  -d, --dir \u003cpath\u003e        Build directory path (default: \"./dist\")\n  -o, --output \u003cpath\u003e     Output path for the routes file (default: \"./.smart-links-routes.json\")\n  -a, --all               Include all file types (default: false)\n  -e, --extensions \u003cext\u003e  File extensions to include (default: [\"html\"])\n  -h, --help              Show help information\n```\n\n#### Method 2: Using the API Functions\n\nYou can also write a custom build script:\n\n1. First build the site and create a routes mapping file:\n\n```js\n// In your build script\nimport { generateRoutesFile } from \"rehype-smart-links\";\n\n// First perform a preliminary build\nawait build();\n\n// Then generate a routes file from the build output directory\ngenerateRoutesFile(\"./dist\", \"./.smart-links-routes.json\", {\n  includeAllFiles: true, // Include all file types\n  // Or only include specific file types\n  includeFileExtensions: [\"html\", \"pdf\", \"zip\"]\n});\n\n// Finally perform the final build\nawait build();\n```\n\n2. Add a build script to your `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"build\": \"node ./scripts/build-with-routes.js\"\n  }\n}\n```\n\n3. Create a build script (e.g., `scripts/build-with-routes.js`):\n\n```js\nimport { execSync } from \"node:child_process\";\nimport { generateRoutesFile } from \"rehype-smart-links\";\n\n// Phase 1: Initial build\nconsole.log(\"[PHASE 1] Initial build...\");\nexecSync(\"astro build\", { stdio: \"inherit\" });\n\n// Generate routes mapping file\nconsole.log(\"[PHASE 2] Generating routes map...\");\ngenerateRoutesFile(\"./dist\", \"./.smart-links-routes.json\", {\n  includeAllFiles: true // Include all file types\n});\n\n// Phase 2: Build again with routes information\nconsole.log(\"[PHASE 3] Final build with routes...\");\nexecSync(\"astro build\", { stdio: \"inherit\" });\n\nconsole.log(\"[SUCCESS] Build complete!\");\n```\n\n## Customizing Link Structure\n\nIn addition to adding classes, you can fully customize the HTML structure of the links:\n\n```js\nimport rehypeSmartLinks from \"rehype-smart-links\";\n\nexport default defineConfig({\n  markdown: {\n    rehypePlugins: [\n      [\n        rehypeSmartLinks,\n        {\n          wrapperTemplate: (node, type, className) =\u003e {\n            // Create tooltip wrapper\n            if (type === \"external\") {\n              // Example structure for external links\n              const tooltip = {\n                type: \"element\",\n                tagName: \"div\",\n                properties: {\n                  className: [\"tooltip\"],\n                  dataTooltip: \"This is an external link\"\n                },\n                children: [node]\n              };\n\n              // You can also modify the original node\n              if (className) {\n                node.properties.className\n                  = [...(node.properties.className || []), className];\n              }\n\n              return tooltip;\n            }\n            else if (type === \"broken\") {\n              // Example structure for broken links\n              const wrapper = {\n                type: \"element\",\n                tagName: \"span\",\n                properties: {\n                  className: [\"broken-link-wrapper\"],\n                  dataError: \"Page doesn't exist\"\n                },\n                children: [node]\n              };\n\n              // Add a warning icon\n              node.children.push({\n                type: \"element\",\n                tagName: \"span\",\n                properties: { className: [\"warning-icon\"] },\n                children: [{ type: \"text\", value: \"⚠\" }]\n              });\n\n              return wrapper;\n            }\n\n            // Only add class for internal links\n            if (className) {\n              node.properties.className\n                = [...(node.properties.className || []), className];\n            }\n\n            return node;\n          }\n        }\n      ]\n    ]\n  }\n});\n```\n\nThis approach allows you to create completely different HTML structures for different types of links, not just add class names, making it ideal for use with component libraries like DaisyUI and TailwindCSS.\n\n## Styling\n\nAdd CSS styles for different link types:\n\n```css\n/* Default style for internal links */\n.internal-link {\n  /* Custom styles */\n}\n\n/* External links with icons */\n.external-link {\n  /* Custom styles */\n}\n.external-link .external-icon {\n  margin-left: 0.25em;\n  font-size: 0.75em;\n}\n\n/* Style for broken links (similar to Wikipedia) */\n.broken-link {\n  color: red;\n}\n```\n\n## Options\n\n| Option                        | Type                                 | Default                         | Description                                           |\n| ----------------------------- | ------------------------------------ | ------------------------------- | ----------------------------------------------------- |\n| `content`                     | `{ type: string, value: string }`    | `{ type: 'text', value: '↗' }` | Content to add after external links                   |\n| `internalLinkClass`           | `string`                             | `'internal-link'`               | Class for internal links to existing pages            |\n| `externalLinkClass`           | `string`                             | `'external-link'`               | Class for external links                              |\n| `brokenLinkClass`             | `string`                             | `'broken-link'`                 | Class for internal links to non-existent pages        |\n| `contentClass`                | `string`                             | `'external-icon'`               | Class for the content element added to external links |\n| `target`                      | `string`                             | `'_blank'`                      | Target attribute for external links                   |\n| `rel`                         | `string`                             | `'noopener noreferrer'`         | Rel attribute for external links                      |\n| `publicDir`                   | `string`                             | `'./dist'`                      | Path to the build output directory                    |\n| `routesFile`                  | `string`                             | `'./.smart-links-routes.json'`  | Path to the routes mapping file                       |\n| `includeFileExtensions`       | `string[]`                           | `['html']`                      | List of file extensions to include                    |\n| `includeAllFiles`             | `boolean`                            | `false`                         | Set to true to include all file types                 |\n| `wrapperTemplate`             | `(node, type, className) =\u003e Element` | `undefined`                     | Template function for custom link structure           |\n| `customInternalLinkTransform` | `(node) =\u003e void`                     | `undefined`                     | Custom transform function for internal links          |\n| `customExternalLinkTransform` | `(node) =\u003e void`                     | `undefined`                     | Custom transform function for external links          |\n| `customBrokenLinkTransform`   | `(node) =\u003e void`                     | `undefined`                     | Custom transform function for broken links            |\n\n## Advanced Customization\n\n### Using Custom Transform Functions\n\nIn addition to `wrapperTemplate`, you can use separate transform functions for finer control:\n\n```js\nimport rehypeSmartLinks from \"rehype-smart-links\";\n\n// Example custom transform function for external links\nfunction customExternalLinkTransform(node) {\n  // Add custom icon or structure\n  node.properties.class = [...(node.properties.class || []), \"my-external-link\"];\n  node.properties.target = \"_blank\";\n  node.properties.rel = \"noopener\";\n\n  // Add custom SVG icon\n  const svgIcon = {\n    type: \"element\",\n    tagName: \"span\",\n    properties: { class: \"custom-icon\" },\n    children: [{ type: \"text\", value: \"🔗\" }]\n  };\n\n  node.children.push(svgIcon);\n}\n\nexport default {\n  markdown: {\n    rehypePlugins: [\n      [\n        rehypeSmartLinks,\n        {\n          customExternalLinkTransform\n        }\n      ]\n    ]\n  }\n};\n```\n\n### Using with TailwindCSS\n\n```js\n// Example using TailwindCSS class names\nconst tailwindWrapper = (node, type, className) =\u003e {\n  // Save original link content\n  const linkChildren = [...node.children];\n\n  // Clear original link content\n  node.children = [];\n\n  if (type === \"external\") {\n    // Add Tailwind class names for external links\n    node.properties.className = [\"text-blue-500\", \"hover:text-blue-700\", \"inline-flex\", \"items-center\", \"gap-1\"];\n\n    // Add original content\n    node.children = [\n      ...linkChildren,\n      {\n        type: \"element\",\n        tagName: \"svg\",\n        properties: {\n          className: [\"w-4\", \"h-4\"],\n          viewBox: \"0 0 24 24\",\n          fill: \"none\",\n          stroke: \"currentColor\"\n        },\n        children: [{\n          type: \"element\",\n          tagName: \"path\",\n          properties: {\n            strokeLinecap: \"round\",\n            strokeLinejoin: \"round\",\n            strokeWidth: \"2\",\n            d: \"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14\"\n          },\n          children: []\n        }]\n      }\n    ];\n\n    return node;\n  }\n  else if (type === \"broken\") {\n    // Create broken link wrapper\n    const wrapper = {\n      type: \"element\",\n      tagName: \"span\",\n      properties: {\n        className: [\"group\", \"relative\", \"inline-block\"]\n      },\n      children: [\n        {\n          ...node,\n          properties: {\n            ...node.properties,\n            className: [\"text-red-500\", \"underline\", \"underline-offset-2\", \"decoration-wavy\", \"decoration-red-500\"]\n          },\n          children: linkChildren\n        },\n        {\n          type: \"element\",\n          tagName: \"span\",\n          properties: {\n            className: [\"invisible\", \"group-hover:visible\", \"absolute\", \"bottom-full\", \"left-1/2\", \"-translate-x-1/2\", \"bg-red-100\", \"text-red-800\", \"text-xs\", \"px-2\", \"py-1\", \"rounded\", \"whitespace-nowrap\"]\n          },\n          children: [{ type: \"text\", value: \"Page doesn't exist\" }]\n        }\n      ]\n    };\n\n    return wrapper;\n  }\n  else {\n    // Add Tailwind class names for internal links\n    node.properties.className = [\"text-green-600\", \"hover:text-green-800\", \"transition-colors\"];\n    node.children = linkChildren;\n    return node;\n  }\n};\n```\n\n## Testing\n\nThis plugin includes a comprehensive test suite to ensure functionality works as expected.\n\n### Running Tests\n\n```bash\n# Install dependencies first\nnpm install\n\n# Run the tests\nnpm test\n```\n\n### Adding Test Cases\n\nIf you're experiencing an issue or want to add a new test case:\n\n1. Add a new test case to `tests/cases/testCases.ts` following the existing pattern.\n\n2. Run the tests to verify your test case:\n\n```bash\nnpm test\n```\n\n3. The test report will be generated at `tests/results/report.html` with visual comparison between expected and actual outputs.\n\n### Reporting Issues\n\nIf you find a bug or have a feature request, please [open an issue](https://github.com/yourusername/rehype-smart-links/issues) with:\n\n1. A clear description of the problem\n2. Steps to reproduce (or ideally, a test case that fails)\n3. Expected vs. actual behavior\n4. Version information for rehype-smart-links and your environment\n\nPull requests are always welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevesunmaple%2Frehype-smart-links","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevesunmaple%2Frehype-smart-links","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevesunmaple%2Frehype-smart-links/lists"}