1import path from 'path'; 2import { visit } from 'unist-util-visit'; 3import { URL } from 'url'; 4 5/** 6 * @typedef {import('@types/mdast').Root} Root - https://github.com/syntax-tree/mdast#root 7 * @typedef {import('vfile').VFile} VFile - https://github.com/syntax-tree/unist#file 8 */ 9 10const DEFAULT_OPTIONS = { 11 extension: 'mdx', 12 pagesDir: 'pages', 13 trailingSlash: true, 14}; 15 16// This is a fallback domain, used to parse URLs. If origin matches this, its an internal link 17const FAKE_DOMAIN = 'https://fake.domain'; 18 19/** 20 * This rewrites internal MDX links to absolute URLs from the root domain. 21 * It's the similar behavior of GitHub MDX linking, but for Next. 22 * 23 * @param {object} options 24 * @param {string} [options.extension="md"] 25 * @param {string} [options.pagesDir="pages"] 26 * @param {boolean} [options.trailingSlash=true] 27 * @returns {function} remark plugin 28 */ 29export default function remarkLinkRewrite(options) { 30 const settings = { ...DEFAULT_OPTIONS, ...options }; 31 32 /** 33 * @param {Root} tree 34 * @param {VFile} file 35 */ 36 return (tree, file) => { 37 // we can't rewrite files without knowing where the file exists 38 if (!file.cwd || !file.history || !file.history.length) { 39 return; 40 } 41 // index references should be ignored, it's handled by Next 42 const ignoredIndex = 'index' + (settings.trailingSlash ? '/' : ''); 43 44 visit(tree, 'link', node => { 45 // parse the url with a fallback fake domain, used to determine if it's internal or not 46 const ref = new URL(node.url, FAKE_DOMAIN); 47 // only rewrite internal non-url nodes 48 if (ref.origin === FAKE_DOMAIN) { 49 // if only a hash is provided, we need to calculate from the same file 50 const oldUrl = 51 ref.hash && ref.pathname === '/' 52 ? `${path.basename(file.history[0])}${ref.hash}` 53 : node.url; 54 55 // resolve the absolute path to the linked md file (supports hashes) 56 const absolute = path.resolve(path.dirname(file.history[0]), oldUrl); 57 // resolve the relative path between the linked file and our pages dir (root of the website) 58 const relative = path.relative(path.join(file.cwd, settings.pagesDir), absolute); 59 // rewrite the URL without the `.md` extension, using trailing slash or nothing 60 let newUrl = relative.replace(`.${settings.extension}`, settings.trailingSlash ? '/' : ''); 61 62 // if the url is referencing the ignored index, remove it 63 if (newUrl.includes(ignoredIndex)) { 64 newUrl = newUrl.replace(ignoredIndex, ''); 65 } 66 67 // force forward slash on non-posix systems 68 node.url = `/${newUrl.replace(/\\/g, '/')}`; 69 } 70 }); 71 }; 72} 73