Gathering detailed insights and metrics for rehype-smart-links
Gathering detailed insights and metrics for rehype-smart-links
Gathering detailed insights and metrics for rehype-smart-links
Gathering detailed insights and metrics for rehype-smart-links
A rehype plugin for Astro that adds different styling for internal and external links
npm install rehype-smart-links
Typescript
Module System
Min. Node Version
Node Version
NPM Version
MDX (51.06%)
HTML (19.48%)
TypeScript (16.38%)
Astro (9.1%)
JavaScript (2.54%)
SCSS (1.43%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
5 Stars
12 Commits
1 Watchers
2 Branches
1 Contributors
Updated on May 17, 2025
Latest Version
0.3.0
Package Id
rehype-smart-links@0.3.0
Unpacked Size
52.26 kB
Size
12.97 kB
File Count
9
NPM Version
9.2.0
Node Version
20.16.0
Published on
Apr 05, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
25
A rehype plugin for Astro that adds different styles to internal and external links:
1# npm 2npm install rehype-smart-links 3 4# yarn 5yarn add rehype-smart-links 6 7# pnpm 8pnpm add rehype-smart-links
Add the plugin to your Astro configuration:
1// astro.config.mjs 2import { defineConfig } from "astro/config"; 3import rehypeSmartLinks from "rehype-smart-links"; 4 5export default defineConfig({ 6 markdown: { 7 rehypePlugins: [ 8 // Basic usage (default settings) 9 rehypeSmartLinks, 10 11 // Or with custom options 12 [ 13 rehypeSmartLinks, 14 { 15 content: { type: "text", value: "↗" }, 16 internalLinkClass: "internal-link", 17 externalLinkClass: "external-link", 18 brokenLinkClass: "broken-link", 19 contentClass: "external-icon", 20 target: "_blank", 21 rel: "noopener noreferrer", 22 publicDir: "./dist", 23 routesFile: "./.smart-links-routes.json", 24 includeFileExtensions: ["html", "pdf", "zip"], // Only include specific file types 25 includeAllFiles: false // Set to true to include all file types 26 } 27 ] 28 ] 29 } 30});
For accurate detection of valid internal links, a two-phase build process is recommended:
rehype-smart-links provides a built-in CLI command to simplify the routes file generation process:
package.json
:1{ 2 "scripts": { 3 "build:with-routes": "astro build && rehype-smart-links build && astro build" 4 } 5}
1npm run build:with-routes
This command will:
rehype-smart-links build
command to scan the build output and generate a routes fileThe CLI command supports the following options:
Options:
-d, --dir <path> Build directory path (default: "./dist")
-o, --output <path> Output path for the routes file (default: "./.smart-links-routes.json")
-a, --all Include all file types (default: false)
-e, --extensions <ext> File extensions to include (default: ["html"])
-h, --help Show help information
You can also write a custom build script:
1// In your build script 2import { generateRoutesFile } from "rehype-smart-links"; 3 4// First perform a preliminary build 5await build(); 6 7// Then generate a routes file from the build output directory 8generateRoutesFile("./dist", "./.smart-links-routes.json", { 9 includeAllFiles: true, // Include all file types 10 // Or only include specific file types 11 includeFileExtensions: ["html", "pdf", "zip"] 12}); 13 14// Finally perform the final build 15await build();
package.json
:1{ 2 "scripts": { 3 "build": "node ./scripts/build-with-routes.js" 4 } 5}
scripts/build-with-routes.js
):1import { execSync } from "node:child_process"; 2import { generateRoutesFile } from "rehype-smart-links"; 3 4// Phase 1: Initial build 5console.log("[PHASE 1] Initial build..."); 6execSync("astro build", { stdio: "inherit" }); 7 8// Generate routes mapping file 9console.log("[PHASE 2] Generating routes map..."); 10generateRoutesFile("./dist", "./.smart-links-routes.json", { 11 includeAllFiles: true // Include all file types 12}); 13 14// Phase 2: Build again with routes information 15console.log("[PHASE 3] Final build with routes..."); 16execSync("astro build", { stdio: "inherit" }); 17 18console.log("[SUCCESS] Build complete!");
In addition to adding classes, you can fully customize the HTML structure of the links:
1import rehypeSmartLinks from "rehype-smart-links"; 2 3export default defineConfig({ 4 markdown: { 5 rehypePlugins: [ 6 [ 7 rehypeSmartLinks, 8 { 9 wrapperTemplate: (node, type, className) => { 10 // Create tooltip wrapper 11 if (type === "external") { 12 // Example structure for external links 13 const tooltip = { 14 type: "element", 15 tagName: "div", 16 properties: { 17 className: ["tooltip"], 18 dataTooltip: "This is an external link" 19 }, 20 children: [node] 21 }; 22 23 // You can also modify the original node 24 if (className) { 25 node.properties.className 26 = [...(node.properties.className || []), className]; 27 } 28 29 return tooltip; 30 } 31 else if (type === "broken") { 32 // Example structure for broken links 33 const wrapper = { 34 type: "element", 35 tagName: "span", 36 properties: { 37 className: ["broken-link-wrapper"], 38 dataError: "Page doesn't exist" 39 }, 40 children: [node] 41 }; 42 43 // Add a warning icon 44 node.children.push({ 45 type: "element", 46 tagName: "span", 47 properties: { className: ["warning-icon"] }, 48 children: [{ type: "text", value: "⚠" }] 49 }); 50 51 return wrapper; 52 } 53 54 // Only add class for internal links 55 if (className) { 56 node.properties.className 57 = [...(node.properties.className || []), className]; 58 } 59 60 return node; 61 } 62 } 63 ] 64 ] 65 } 66});
This 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.
Add CSS styles for different link types:
1/* Default style for internal links */ 2.internal-link { 3 /* Custom styles */ 4} 5 6/* External links with icons */ 7.external-link { 8 /* Custom styles */ 9} 10.external-link .external-icon { 11 margin-left: 0.25em; 12 font-size: 0.75em; 13} 14 15/* Style for broken links (similar to Wikipedia) */ 16.broken-link { 17 color: red; 18}
Option | Type | Default | Description |
---|---|---|---|
content | { type: string, value: string } | { type: 'text', value: '↗' } | Content to add after external links |
internalLinkClass | string | 'internal-link' | Class for internal links to existing pages |
externalLinkClass | string | 'external-link' | Class for external links |
brokenLinkClass | string | 'broken-link' | Class for internal links to non-existent pages |
contentClass | string | 'external-icon' | Class for the content element added to external links |
target | string | '_blank' | Target attribute for external links |
rel | string | 'noopener noreferrer' | Rel attribute for external links |
publicDir | string | './dist' | Path to the build output directory |
routesFile | string | './.smart-links-routes.json' | Path to the routes mapping file |
includeFileExtensions | string[] | ['html'] | List of file extensions to include |
includeAllFiles | boolean | false | Set to true to include all file types |
wrapperTemplate | (node, type, className) => Element | undefined | Template function for custom link structure |
customInternalLinkTransform | (node) => void | undefined | Custom transform function for internal links |
customExternalLinkTransform | (node) => void | undefined | Custom transform function for external links |
customBrokenLinkTransform | (node) => void | undefined | Custom transform function for broken links |
In addition to wrapperTemplate
, you can use separate transform functions for finer control:
1import rehypeSmartLinks from "rehype-smart-links"; 2 3// Example custom transform function for external links 4function customExternalLinkTransform(node) { 5 // Add custom icon or structure 6 node.properties.class = [...(node.properties.class || []), "my-external-link"]; 7 node.properties.target = "_blank"; 8 node.properties.rel = "noopener"; 9 10 // Add custom SVG icon 11 const svgIcon = { 12 type: "element", 13 tagName: "span", 14 properties: { class: "custom-icon" }, 15 children: [{ type: "text", value: "🔗" }] 16 }; 17 18 node.children.push(svgIcon); 19} 20 21export default { 22 markdown: { 23 rehypePlugins: [ 24 [ 25 rehypeSmartLinks, 26 { 27 customExternalLinkTransform 28 } 29 ] 30 ] 31 } 32};
1// Example using TailwindCSS class names 2const tailwindWrapper = (node, type, className) => { 3 // Save original link content 4 const linkChildren = [...node.children]; 5 6 // Clear original link content 7 node.children = []; 8 9 if (type === "external") { 10 // Add Tailwind class names for external links 11 node.properties.className = ["text-blue-500", "hover:text-blue-700", "inline-flex", "items-center", "gap-1"]; 12 13 // Add original content 14 node.children = [ 15 ...linkChildren, 16 { 17 type: "element", 18 tagName: "svg", 19 properties: { 20 className: ["w-4", "h-4"], 21 viewBox: "0 0 24 24", 22 fill: "none", 23 stroke: "currentColor" 24 }, 25 children: [{ 26 type: "element", 27 tagName: "path", 28 properties: { 29 strokeLinecap: "round", 30 strokeLinejoin: "round", 31 strokeWidth: "2", 32 d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" 33 }, 34 children: [] 35 }] 36 } 37 ]; 38 39 return node; 40 } 41 else if (type === "broken") { 42 // Create broken link wrapper 43 const wrapper = { 44 type: "element", 45 tagName: "span", 46 properties: { 47 className: ["group", "relative", "inline-block"] 48 }, 49 children: [ 50 { 51 ...node, 52 properties: { 53 ...node.properties, 54 className: ["text-red-500", "underline", "underline-offset-2", "decoration-wavy", "decoration-red-500"] 55 }, 56 children: linkChildren 57 }, 58 { 59 type: "element", 60 tagName: "span", 61 properties: { 62 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"] 63 }, 64 children: [{ type: "text", value: "Page doesn't exist" }] 65 } 66 ] 67 }; 68 69 return wrapper; 70 } 71 else { 72 // Add Tailwind class names for internal links 73 node.properties.className = ["text-green-600", "hover:text-green-800", "transition-colors"]; 74 node.children = linkChildren; 75 return node; 76 } 77};
This plugin includes a comprehensive test suite to ensure functionality works as expected.
1# Install dependencies first 2npm install 3 4# Run the tests 5npm test
If you're experiencing an issue or want to add a new test case:
Add a new test case to tests/cases/testCases.ts
following the existing pattern.
Run the tests to verify your test case:
1npm test
tests/results/report.html
with visual comparison between expected and actual outputs.If you find a bug or have a feature request, please open an issue with:
Pull requests are always welcome!
No vulnerabilities found.
No security vulnerabilities found.