-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfont-loader.ts
132 lines (130 loc) · 4.03 KB
/
font-loader.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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import path from "node:path"
import crypto from "node:crypto"
import fs from "node:fs"
import type { Plugin } from "vite"
import type { AstroIntegration } from "astro"
/**
* Vite plugin to load fonts from Google Fonts and bundle them as static assets.
*
* In CSS:
* ```css
* @font-face {
* font-family: "Outfit";
* src: url("font:family=Outfit") format("woff2");
* }
* :root {
* font-family: "Outfit", sans-serif;
* }
* ```
*
* In JSX:
* ```tsx
* import font from "font:family=Outfit"
*
* // the link to the font inside the bundle
* font satisfies string
*
* <link rel="preload" href={font} as="font" type="font/woff2" crossorigin>
* ```
*/
export default function (): AstroIntegration {
return {
name: "font-loader",
hooks: {
"astro:config:setup": ({ updateConfig }) => {
updateConfig({
vite: {
plugins: [ vite() ]
}
})
},
"astro:config:done" ({ injectTypes }) {
injectTypes({
filename: "types.d.ts",
content: [
`declare module 'font:*' {`,
` const href: string`,
` export default href`,
`}`,
].join("\n")
})
}
}
}
}
function vite(): Plugin {
let root: string
let metadata: Record<string, string>
function metadataFilePath() {
return path.join(root, "node_modules", ".fonts", "metadata.json")
}
function readMetadata() {
if (metadata) return metadata
const metadataFilePath = path.join(root, "node_modules", ".fonts", "metadata.json")
try {
const metadataText = fs.readFileSync(metadataFilePath, "utf8")
metadata = JSON.parse(metadataText)
} catch {
fs.writeFileSync(metadataFilePath, "{}")
metadata = {}
}
return metadata
}
function writeMetadata() {
fs.writeFileSync(metadataFilePath(), JSON.stringify(metadata, null, 4))
}
const resolveId: Plugin["resolveId"] = async function (id) {
if (id.startsWith("font:") === false && id.startsWith("/font:") === false) {
return
}
const query = id.startsWith("font:") ? id.slice(5) : id.slice(6)
try {
const metadata = readMetadata()
const filePath = metadata[query]
if (filePath) return filePath
}
catch {
// continue
}
const cssResponse = await fetch(`https://fonts.googleapis.com/css2?${query}`, {
headers: {
"User-Agent": "AppleWebKit/537.36 Chrome/130.0.0.0"
}
})
const cssText = await cssResponse.text()
const [ fontPath ] = /(?<=url\()https:\/\/fonts.gstatic.com\/(\S)+(?=\))/.exec(cssText)!
const fontResponse = await fetch(fontPath)
const fontBuffer = await fontResponse.arrayBuffer()
const fontBytes = new Uint8Array(fontBuffer)
const hash = crypto.hash("md5", fontBytes)
const filePath = path.join(root, "node_modules", ".fonts", `${hash}.woff2`)
fs.mkdirSync(path.dirname(filePath), { recursive: true })
fs.writeFileSync(filePath, fontBytes)
const metadata = readMetadata()
metadata[query] = filePath
writeMetadata()
return filePath
}
return {
name: "font-loader",
config() {
return {
resolve: {
alias: [{
find: /font:/,
replacement: "font:",
// url() in css can only be hooked into this way
customResolver: { resolveId }
}]
}
}
},
configureServer({ config }) {
root = config.root
},
configResolved(config) {
root = config.root
},
resolveId
}
}