bf8ee1edb4
For some context: [https://vercel.slack.com/archives/CGU8HUTUH/p1662124179102509](https://vercel.slack.com/archives/CGU8HUTUH/p1662124179102509) Continuation of #40221 and #40227 Adds `experimental.fontLoaders`. SWC next-font-loaders (#40221) transforms font loader (e.g. #40227) call expressions into an import with the function call arguments as a query. The imports will be matched by `next-font-loader`. It runs the configured font loaders - emits font files and returns CSS. Exports are added, and the font-family is made locally scoped. The returned CSS is turned into a CSS module with `css-loader` which lets you consume the font-family. `FontLoaderManifestPlugin` creates a manifest of the preloaded font files for each entrypoint. Preload/preconnect are then added in `_document.tsx` if any font files were found for that path. Co-authored-by: JJ Kasper <jj@jjsweb.site>
135 lines
4.2 KiB
TypeScript
135 lines
4.2 KiB
TypeScript
import postcss, { Declaration } from 'postcss'
|
|
|
|
const postcssFontLoaderPlugn = (
|
|
exports: { name: any; value: any }[],
|
|
fontFamilyHash: string,
|
|
fallbackFonts: string[] = []
|
|
) => {
|
|
return {
|
|
postcssPlugin: 'postcss-font-loader',
|
|
Once(root: any) {
|
|
const fontFamilies: string[] = []
|
|
let rawFamily: string | undefined
|
|
let fontWeight: string | undefined
|
|
let fontStyle: string | undefined
|
|
|
|
const formatFamily = (family: string) => {
|
|
if (family[0] === "'" || family[0] === '"') {
|
|
family = family.slice(1, family.length - 1)
|
|
}
|
|
// Turn the font family unguessable to make it localy scoped
|
|
return `'__${family.replace(/ /g, '_')}_${fontFamilyHash}'`
|
|
}
|
|
|
|
for (const node of root.nodes) {
|
|
if (node.type === 'atrule' && node.name === 'font-face') {
|
|
const familyNode = node.nodes.find(
|
|
(decl: Declaration) => decl.prop === 'font-family'
|
|
)
|
|
if (!familyNode) {
|
|
continue
|
|
}
|
|
|
|
if (!rawFamily) {
|
|
let family: string = familyNode.value
|
|
if (family[0] === "'" || family[0] === '"') {
|
|
family = family.slice(1, family.length - 1)
|
|
}
|
|
rawFamily = family
|
|
}
|
|
const formattedFamily = formatFamily(familyNode.value)
|
|
familyNode.value = formattedFamily
|
|
|
|
if (fontFamilies.includes(formattedFamily)) {
|
|
continue
|
|
}
|
|
fontFamilies.push(formattedFamily)
|
|
|
|
// Only extract weight and style from first encountered family, the rest will treated as fallbacks
|
|
if (fontFamilies.length > 1) {
|
|
continue
|
|
}
|
|
|
|
// Extract weight and style from first encountered @font-face
|
|
const weight = node.nodes.find(
|
|
(decl: Declaration) => decl.prop === 'font-weight'
|
|
)
|
|
|
|
// Skip if the value includes ' ', then it's a range of possible values
|
|
if (weight && !weight.value.includes(' ')) {
|
|
fontWeight = weight.value
|
|
}
|
|
|
|
const style = node.nodes.find(
|
|
(decl: Declaration) => decl.prop === 'font-style'
|
|
)
|
|
// Skip if the value includes ' ', then it's a range of possible values
|
|
if (style && !style.value.includes(' ')) {
|
|
fontStyle = style.value
|
|
}
|
|
}
|
|
}
|
|
|
|
const [mainFontFamily, ...adjustFontFallbacks] = fontFamilies
|
|
// If fallback fonts were provided from the font loader, they should be used before the adjustFontFallbacks
|
|
const formattedFontFamilies = [
|
|
mainFontFamily,
|
|
...fallbackFonts,
|
|
...adjustFontFallbacks,
|
|
].join(', ')
|
|
// Add class with family, weight and style
|
|
const classRule = new postcss.Rule({ selector: '.className' })
|
|
classRule.nodes = [
|
|
new postcss.Declaration({
|
|
prop: 'font-family',
|
|
value: formattedFontFamilies,
|
|
}),
|
|
...(fontWeight
|
|
? [
|
|
new postcss.Declaration({
|
|
prop: 'font-weight',
|
|
value: fontWeight,
|
|
}),
|
|
]
|
|
: []),
|
|
...(fontStyle
|
|
? [
|
|
new postcss.Declaration({
|
|
prop: 'font-style',
|
|
value: fontStyle,
|
|
}),
|
|
]
|
|
: []),
|
|
]
|
|
root.nodes.push(classRule)
|
|
|
|
// Add class that defines a variable with the font family
|
|
const varialbeRule = new postcss.Rule({ selector: '.variable' })
|
|
varialbeRule.nodes = [
|
|
new postcss.Declaration({
|
|
prop: rawFamily
|
|
? `--next-font-${rawFamily.toLowerCase().replace(/ /g, '-')}${
|
|
fontWeight ? `-${fontWeight}` : ''
|
|
}${fontStyle === 'italic' ? `-${fontStyle}` : ''}`
|
|
: '',
|
|
value: formattedFontFamilies,
|
|
}),
|
|
]
|
|
root.nodes.push(varialbeRule)
|
|
|
|
// Export @font-face values as is
|
|
exports.push({
|
|
name: 'style',
|
|
value: {
|
|
fontFamily: formattedFontFamilies,
|
|
fontWeight: fontWeight && Number(fontWeight),
|
|
fontStyle,
|
|
},
|
|
})
|
|
},
|
|
}
|
|
}
|
|
|
|
postcssFontLoaderPlugn.postcss = true
|
|
|
|
export default postcssFontLoaderPlugn
|