posthtml-relative-paths
Version:
Convert the absolute paths in HTML files to relative paths
124 lines (110 loc) • 3.93 kB
text/typescript
import type { Node, Plugin } from "posthtml"
import { dirname, resolve, isAbsolute } from "path"
import { basename, join, relative } from "path/posix"
import { existsSync } from "fs"
import { prependDot, removeDot, toPosixPath } from "./utils"
import glob from "fast-glob"
import memoize from "micro-memoize"
function calculateRelativePath(url: string, filePath: string, root: string, src: string): string {
let resolvedFile = join(root, url)
if (!existsSync(resolvedFile)) {
let found = false
// if the file is not found in the root directory, search for it in the src directory
const srcFile = join(src, url)
if (existsSync(srcFile)) {
console.log(`Replaced ${resolvedFile} with ${srcFile}`)
resolvedFile = srcFile
found = true
}
// HACK
/*
* If the url is copied as is into the HTML file.
*
* For example, This can happen with Astro
*
* <head>
* <link rel="stylesheet" href="./contact.scss" class="href">
* </head>
*/
const filesSearched = memoizedSearchInSourceFolder(src, url)
if (filesSearched.length === 1) {
const fileSearched = filesSearched[0]
console.warn(`Replaced ${resolvedFile} with ${fileSearched}`)
resolvedFile = fileSearched
found = true
}
// HACK for astro imagetools bug
// https://github.com/RafidMuhymin/astro-imagetools/issues/102
const jpegFile = `${resolvedFile}.jpeg`
if (existsSync(jpegFile)) {
console.warn(`Replaced ${resolvedFile} with ${jpegFile} due to astro-imagetools' bug`)
resolvedFile = jpegFile
found = true
}
if (!found) {
console.error(`Could not resolve ${url} in ${filePath}`)
return url
}
}
const folder = toPosixPath(dirname(filePath))
let relativeFile = relative(folder, resolvedFile)
relativeFile = prependDot(relativeFile)
return relativeFile
}
const memoizedCalculateRelativePath = memoize(calculateRelativePath)
function searchInSourceFolder(src: string, url: string) {
const files = glob.sync([`${src}/**/${removeDot(url)}`], {
dot: true,
onlyFiles: true,
})
if (files.length === 0) {
return glob.sync([`${src}/**/${basename(url)}`], {
dot: true,
onlyFiles: true,
})
} else {
return files
}
}
const memoizedSearchInSourceFolder = memoize(searchInSourceFolder)
/**
* A Post HTML plugin that makes the urls in a HTML file relative to the root
*
* @param filePathGiven The HTML file path
* @param rootGiven The root directory. Defaults to `"./dist"`
* @param srcGiven The src directory. Defaults to `"./src"`. Used to resolve the files that are not found in the root
* directory.
* @returns A POSTHTML plugin
*/
export default function PostHTMLRelativePaths(
filePathGiven: string,
rootGiven: string = resolve("./dist"),
srcGiven: string = resolve("./src"),
): Plugin<Node> {
// make the file paths absolute and posix
const root = toPosixPath(!isAbsolute(rootGiven) ? resolve(rootGiven) : rootGiven)
const src = toPosixPath(!isAbsolute(srcGiven) ? resolve(srcGiven) : srcGiven)
const filePath = toPosixPath(!isAbsolute(filePathGiven) ? resolve(filePathGiven) : filePathGiven)
return (tree: Node) => {
tree.walk((node) => {
if (typeof node.attrs !== "object") {
// attrs might be void
return node
}
for (const [tag, url] of Object.entries(node.attrs)) {
// if the tag contains a url
// and the tag is one of the followings
// and the url starts with /
if (
typeof url === "string" &&
["src", "href", "srcset", "content"].includes(tag) &&
(url.startsWith("/") || url.startsWith("./") || url.startsWith("../"))
) {
// make it relative to the root
node.attrs[tag] = memoizedCalculateRelativePath(url, filePath, root, src)
}
}
return node
})
}
}