-
Notifications
You must be signed in to change notification settings - Fork 26.1k
/
url.ts
93 lines (87 loc) · 2.48 KB
/
url.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import * as path from 'path'
import * as fs from 'fs'
// Cache for fs.readdirSync lookup.
// Prevent multiple blocking IO requests that have already been calculated.
const fsReadDirSyncCache = {}
/**
* Recursively parse directory for page URLs.
*/
function parseUrlForPages(urlprefix: string, directory: string) {
fsReadDirSyncCache[directory] ??= fs.readdirSync(directory, {
withFileTypes: true,
})
const res = []
fsReadDirSyncCache[directory].forEach((dirent) => {
// TODO: this should account for all page extensions
// not just js(x) and ts(x)
if (/(\.(j|t)sx?)$/.test(dirent.name)) {
if (/^index(\.(j|t)sx?)$/.test(dirent.name)) {
res.push(
`${urlprefix}${dirent.name.replace(/^index(\.(j|t)sx?)$/, '')}`
)
}
res.push(`${urlprefix}${dirent.name.replace(/(\.(j|t)sx?)$/, '')}`)
} else {
const dirPath = path.join(directory, dirent.name)
if (dirent.isDirectory() && !dirent.isSymbolicLink()) {
res.push(...parseUrlForPages(urlprefix + dirent.name + '/', dirPath))
}
}
})
return res
}
/**
* Takes a URL and does the following things.
* - Replaces `index.html` with `/`
* - Makes sure all URLs are have a trailing `/`
* - Removes query string
*/
export function normalizeURL(url: string) {
if (!url) {
return
}
url = url.split('?', 1)[0]
url = url.split('#', 1)[0]
url = url = url.replace(/(\/index\.html)$/, '/')
// Empty URLs should not be trailed with `/`, e.g. `#heading`
if (url === '') {
return url
}
url = url.endsWith('/') ? url : url + '/'
return url
}
/**
* Gets the possible URLs from a directory.
*/
export function getUrlFromPagesDirectories(
urlPrefix: string,
directories: string[]
) {
return Array.from(
// De-duplicate similar pages across multiple directories.
new Set(
directories
.flatMap((directory) => parseUrlForPages(urlPrefix, directory))
.map(
// Since the URLs are normalized we add `^` and `$` to the RegExp to make sure they match exactly.
(url) => `^${normalizeURL(url)}$`
)
)
).map((urlReg) => {
urlReg = urlReg.replace(/\[.*\]/g, '((?!.+?\\..+?).*?)')
return new RegExp(urlReg)
})
}
export function execOnce<TArgs extends any[], TResult>(
fn: (...args: TArgs) => TResult
): (...args: TArgs) => TResult {
let used = false
let result: TResult
return (...args: TArgs) => {
if (!used) {
used = true
result = fn(...args)
}
return result
}
}