main repo

This commit is contained in:
Basilosaurusrex
2025-11-24 18:09:40 +01:00
parent b636ee5e70
commit f027651f9b
34146 changed files with 4436636 additions and 0 deletions

View File

@@ -0,0 +1 @@
export declare const allowedDisplayValues: string[];

View File

@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.allowedDisplayValues = void 0;
exports.allowedDisplayValues = [
'auto',
'block',
'swap',
'fallback',
'optional',
];

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
/**
* Formats an array of values into a string that can be used error messages.
* ["a", "b", "c"] => "`a`, `b`, `c`"
*/
export declare const formatAvailableValues: (values: string[]) => string;

View File

@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatAvailableValues = void 0;
/**
* Formats an array of values into a string that can be used error messages.
* ["a", "b", "c"] => "`a`, `b`, `c`"
*/
const formatAvailableValues = (values) => values.map((val) => `\`${val}\``).join(', ');
exports.formatAvailableValues = formatAvailableValues;

View File

@@ -0,0 +1,8 @@
/**
* Fetches the CSS containing the @font-face declarations from Google Fonts.
* The fetch has a user agent header with a modern browser to ensure we'll get .woff2 files.
*
* The env variable NEXT_FONT_GOOGLE_MOCKED_RESPONSES may be set containing a path to mocked data.
* It's used to define mocked data to avoid hitting the Google Fonts API during tests.
*/
export declare function fetchCSSFromGoogleFonts(url: string, fontFamily: string, isDev: boolean): Promise<string>;

View File

@@ -0,0 +1,79 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchCSSFromGoogleFonts = void 0;
// @ts-ignore
const node_fetch_1 = __importDefault(require("next/dist/compiled/node-fetch"));
const next_font_error_1 = require("../next-font-error");
const get_proxy_agent_1 = require("./get-proxy-agent");
async function retry(fn, attempts) {
let cnt = attempts;
while (true) {
try {
return await fn();
}
catch (err) {
cnt--;
if (cnt <= 0)
throw err;
console.error(err.message + `\n\nRetrying ${attempts - cnt}/3...`);
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
}
/**
* Fetches the CSS containing the @font-face declarations from Google Fonts.
* The fetch has a user agent header with a modern browser to ensure we'll get .woff2 files.
*
* The env variable NEXT_FONT_GOOGLE_MOCKED_RESPONSES may be set containing a path to mocked data.
* It's used to define mocked data to avoid hitting the Google Fonts API during tests.
*/
async function fetchCSSFromGoogleFonts(url, fontFamily, isDev) {
// Check if mocked responses are defined, if so use them instead of fetching from Google Fonts
let mockedResponse;
if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
const mockFile = require(process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES);
mockedResponse = mockFile[url];
if (!mockedResponse) {
(0, next_font_error_1.nextFontError)('Missing mocked response for URL: ' + url);
}
}
let cssResponse;
if (mockedResponse) {
// Just use the mocked CSS if it's set
cssResponse = mockedResponse;
}
else {
// Retry the fetch a few times in case of network issues as some font files
// are quite large:
// https://github.com/vercel/next.js/issues/45080
cssResponse = await retry(async () => {
const controller = isDev && typeof AbortController !== 'undefined'
? new AbortController()
: undefined;
const signal = controller === null || controller === void 0 ? void 0 : controller.signal;
const timeoutId = controller
? setTimeout(() => controller.abort(), 3000)
: undefined;
const res = await (0, node_fetch_1.default)(url, {
agent: (0, get_proxy_agent_1.getProxyAgent)(),
// Add a timeout in dev
signal,
headers: {
// The file format is based off of the user agent, make sure woff2 files are fetched
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36',
},
}).finally(() => {
timeoutId && clearTimeout(timeoutId);
});
if (!res.ok) {
(0, next_font_error_1.nextFontError)(`Failed to fetch font \`${fontFamily}\`.\nURL: ${url}\n\nPlease check if the network is available.`);
}
return res.text();
}, 3);
}
return cssResponse;
}
exports.fetchCSSFromGoogleFonts = fetchCSSFromGoogleFonts;

View File

@@ -0,0 +1,4 @@
/**
* Fetch the url and return a buffer with the font file.
*/
export declare function fetchFontFile(url: string, isDev: boolean): Promise<any>;

View File

@@ -0,0 +1,36 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchFontFile = void 0;
// @ts-ignore
const node_fetch_1 = __importDefault(require("next/dist/compiled/node-fetch"));
const get_proxy_agent_1 = require("./get-proxy-agent");
/**
* Fetch the url and return a buffer with the font file.
*/
async function fetchFontFile(url, isDev) {
// Check if we're using mocked data
if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
// If it's an absolute path, read the file from the filesystem
if (url.startsWith('/')) {
return require('fs').readFileSync(url);
}
// Otherwise just return a unique buffer
return Buffer.from(url);
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const arrayBuffer = await (0, node_fetch_1.default)(url, {
agent: (0, get_proxy_agent_1.getProxyAgent)(),
// Add a timeout in dev
signal: isDev ? controller.signal : undefined,
})
.then((r) => r.arrayBuffer())
.finally(() => {
clearTimeout(timeoutId);
});
return Buffer.from(arrayBuffer);
}
exports.fetchFontFile = fetchFontFile;

View File

@@ -0,0 +1,9 @@
/**
* Find all font files in the CSS response and determine which files should be preloaded.
* In Google Fonts responses, the @font-face's subset is above it in a comment.
* Walk through the CSS from top to bottom, keeping track of the current subset.
*/
export declare function findFontFilesInCss(css: string, subsetsToPreload?: string[]): {
googleFontFileUrl: string;
preloadFontFile: boolean;
}[];

View File

@@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findFontFilesInCss = void 0;
/**
* Find all font files in the CSS response and determine which files should be preloaded.
* In Google Fonts responses, the @font-face's subset is above it in a comment.
* Walk through the CSS from top to bottom, keeping track of the current subset.
*/
function findFontFilesInCss(css, subsetsToPreload) {
var _a, _b;
// Find font files to download
const fontFiles = [];
// Keep track of the current subset
let currentSubset = '';
for (const line of css.split('\n')) {
const newSubset = (_a = /\/\* (.+?) \*\//.exec(line)) === null || _a === void 0 ? void 0 : _a[1];
if (newSubset) {
// Found new subset in a comment above the next @font-face declaration
currentSubset = newSubset;
}
else {
const googleFontFileUrl = (_b = /src: url\((.+?)\)/.exec(line)) === null || _b === void 0 ? void 0 : _b[1];
if (googleFontFileUrl &&
!fontFiles.some((foundFile) => foundFile.googleFontFileUrl === googleFontFileUrl)) {
// Found the font file in the @font-face declaration.
fontFiles.push({
googleFontFileUrl,
preloadFontFile: !!(subsetsToPreload === null || subsetsToPreload === void 0 ? void 0 : subsetsToPreload.includes(currentSubset)),
});
}
}
}
return fontFiles;
}
exports.findFontFilesInCss = findFontFilesInCss;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,90 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const find_font_files_in_css_1 = require("./find-font-files-in-css");
describe('findFontFilesInCss', () => {
it('should find all font files and preload requested subsets', () => {
const css = `/* latin */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 300;
src: url(latin1.woff2) format('woff2');
}
/* greek */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 300;
src: url(greek1.woff2) format('woff2');
}
/* latin */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 400;
src: url(latin2.woff2) format('woff2');
}
/* greek */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 400;
src: url(greek2.woff2) format('woff2');
}
/* cyrilic */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 400;
src: url(cyrilic.woff2) format('woff2');
}
`;
expect((0, find_font_files_in_css_1.findFontFilesInCss)(css, ['latin', 'cyrilic'])).toEqual([
{ googleFontFileUrl: 'latin1.woff2', preloadFontFile: true },
{ googleFontFileUrl: 'greek1.woff2', preloadFontFile: false },
{ googleFontFileUrl: 'latin2.woff2', preloadFontFile: true },
{ googleFontFileUrl: 'greek2.woff2', preloadFontFile: false },
{ googleFontFileUrl: 'cyrilic.woff2', preloadFontFile: true },
]);
});
it('should not return duplicate font files when several variants use the same font file', () => {
const css = `/* latin */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUu8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib14c7qv8oRcTn.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUu8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib14c7qv8oRcTn.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUu8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib14c7qv8oRcTn.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
`;
expect((0, find_font_files_in_css_1.findFontFilesInCss)(css)).toEqual([
{
googleFontFileUrl: 'https://fonts.gstatic.com/s/fraunces/v24/6NUu8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib14c7qv8oRcTn.woff2',
preloadFontFile: false,
},
]);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
/**
* Get precalculated fallback font metrics for the Google Fonts family.
*
* TODO:
* We might want to calculate these values with fontkit instead (like in next/font/local).
* That way we don't have to update the precalculated values every time a new font is added to Google Fonts.
*/
export declare function getFallbackFontOverrideMetrics(fontFamily: string): {
fallbackFont: any;
ascentOverride: string;
descentOverride: string;
lineGapOverride: string;
sizeAdjust: string;
} | undefined;

View File

@@ -0,0 +1,53 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFallbackFontOverrideMetrics = void 0;
// @ts-ignore
const font_utils_1 = require("next/dist/server/font-utils");
// @ts-ignore
const Log = __importStar(require("next/dist/build/output/log"));
/**
* Get precalculated fallback font metrics for the Google Fonts family.
*
* TODO:
* We might want to calculate these values with fontkit instead (like in next/font/local).
* That way we don't have to update the precalculated values every time a new font is added to Google Fonts.
*/
function getFallbackFontOverrideMetrics(fontFamily) {
try {
const { ascent, descent, lineGap, fallbackFont, sizeAdjust } = (0, font_utils_1.calculateSizeAdjustValues)(fontFamily);
return {
fallbackFont,
ascentOverride: `${ascent}%`,
descentOverride: `${descent}%`,
lineGapOverride: `${lineGap}%`,
sizeAdjust: `${sizeAdjust}%`,
};
}
catch {
Log.error(`Failed to find font override values for font \`${fontFamily}\``);
}
}
exports.getFallbackFontOverrideMetrics = getFallbackFontOverrideMetrics;

View File

@@ -0,0 +1,8 @@
/**
* Validates and gets the data for each font axis required to generate the Google Fonts URL.
*/
export declare function getFontAxes(fontFamily: string, weights: string[], styles: string[], selectedVariableAxes?: string[]): {
wght?: string[];
ital?: string[];
variableAxes?: [string, string][];
};

View File

@@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFontAxes = void 0;
const format_available_values_1 = require("../format-available-values");
const next_font_error_1 = require("../next-font-error");
const google_fonts_metadata_1 = require("./google-fonts-metadata");
/**
* Validates and gets the data for each font axis required to generate the Google Fonts URL.
*/
function getFontAxes(fontFamily, weights, styles, selectedVariableAxes) {
const hasItalic = styles.includes('italic');
const hasNormal = styles.includes('normal');
// Make sure the order is correct, otherwise Google Fonts will return an error
// If only normal is set, we can skip returning the ital axis as normal is the default
const ital = hasItalic ? [...(hasNormal ? ['0'] : []), '1'] : undefined;
// Weights will always contain one element if it's a variable font
if (weights[0] === 'variable') {
// Get all the available axes for the current font from the metadata file
const allAxes = google_fonts_metadata_1.googleFontsMetadata[fontFamily].axes;
if (!allAxes) {
throw new Error('invariant variable font without axes');
}
if (selectedVariableAxes) {
// The axes other than weight and style that can be defined for the current variable font
const defineAbleAxes = allAxes
.map(({ tag }) => tag)
.filter((tag) => tag !== 'wght');
if (defineAbleAxes.length === 0) {
(0, next_font_error_1.nextFontError)(`Font \`${fontFamily}\` has no definable \`axes\``);
}
if (!Array.isArray(selectedVariableAxes)) {
(0, next_font_error_1.nextFontError)(`Invalid axes value for font \`${fontFamily}\`, expected an array of axes.\nAvailable axes: ${(0, format_available_values_1.formatAvailableValues)(defineAbleAxes)}`);
}
selectedVariableAxes.forEach((key) => {
if (!defineAbleAxes.some((tag) => tag === key)) {
(0, next_font_error_1.nextFontError)(`Invalid axes value \`${key}\` for font \`${fontFamily}\`.\nAvailable axes: ${(0, format_available_values_1.formatAvailableValues)(defineAbleAxes)}`);
}
});
}
let weightAxis;
let variableAxes;
for (const { tag, min, max } of allAxes) {
if (tag === 'wght') {
// In variable fonts the weight is a range
weightAxis = `${min}..${max}`;
}
else if (selectedVariableAxes === null || selectedVariableAxes === void 0 ? void 0 : selectedVariableAxes.includes(tag)) {
if (!variableAxes) {
variableAxes = [];
}
variableAxes.push([tag, `${min}..${max}`]);
}
}
return {
wght: weightAxis ? [weightAxis] : undefined,
ital,
variableAxes,
};
}
else {
return {
ital,
wght: weights,
};
}
}
exports.getFontAxes = getFontAxes;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const get_font_axes_1 = require("./get-font-axes");
describe('getFontAxes errors', () => {
test('Setting axes on font without definable axes', () => {
expect(() => (0, get_font_axes_1.getFontAxes)('Lora', ['variable'], [], [])).toThrowErrorMatchingInlineSnapshot(`"Font \`Lora\` has no definable \`axes\`"`);
});
test('Invalid axes value', async () => {
expect(() => (0, get_font_axes_1.getFontAxes)('Inter', ['variable'], [], true))
.toThrowErrorMatchingInlineSnapshot(`
"Invalid axes value for font \`Inter\`, expected an array of axes.
Available axes: \`slnt\`"
`);
});
test('Invalid value in axes array', async () => {
expect(() => (0, get_font_axes_1.getFontAxes)('Roboto Flex', ['variable'], [], ['INVALID']))
.toThrowErrorMatchingInlineSnapshot(`
"Invalid axes value \`INVALID\` for font \`Roboto Flex\`.
Available axes: \`GRAD\`, \`XTRA\`, \`YOPQ\`, \`YTAS\`, \`YTDE\`, \`YTFI\`, \`YTLC\`, \`YTUC\`, \`opsz\`, \`slnt\`, \`wdth\`"
`);
});
});

View File

@@ -0,0 +1,8 @@
/**
* Generate the Google Fonts URL given the requested weight(s), style(s) and additional variable axes
*/
export declare function getGoogleFontsUrl(fontFamily: string, axes: {
wght?: string[];
ital?: string[];
variableAxes?: [string, string][];
}, display: string): string;

View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGoogleFontsUrl = void 0;
const sort_fonts_variant_values_1 = require("./sort-fonts-variant-values");
/**
* Generate the Google Fonts URL given the requested weight(s), style(s) and additional variable axes
*/
function getGoogleFontsUrl(fontFamily, axes, display) {
var _a, _b;
// Variants are all combinations of weight and style, each variant will result in a separate font file
const variants = [];
if (axes.wght) {
for (const wght of axes.wght) {
if (!axes.ital) {
variants.push([['wght', wght], ...((_a = axes.variableAxes) !== null && _a !== void 0 ? _a : [])]);
}
else {
for (const ital of axes.ital) {
variants.push([
['ital', ital],
['wght', wght],
...((_b = axes.variableAxes) !== null && _b !== void 0 ? _b : []),
]);
}
}
}
}
else if (axes.variableAxes) {
// Variable fonts might not have a range of weights, just add optional variable axes in that case
variants.push([...axes.variableAxes]);
}
// Google api requires the axes to be sorted, starting with lowercase words
if (axes.variableAxes) {
variants.forEach((variant) => {
variant.sort(([a], [b]) => {
const aIsLowercase = a.charCodeAt(0) > 96;
const bIsLowercase = b.charCodeAt(0) > 96;
if (aIsLowercase && !bIsLowercase)
return -1;
if (bIsLowercase && !aIsLowercase)
return 1;
return a > b ? 1 : -1;
});
});
}
let url = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/ /g, '+')}`;
if (variants.length > 0) {
url = `${url}:${variants[0].map(([key]) => key).join(',')}@${variants
.map((variant) => variant.map(([, val]) => val).join(','))
.sort(sort_fonts_variant_values_1.sortFontsVariantValues)
.join(';')}`;
}
url = `${url}&display=${display}`;
return url;
}
exports.getGoogleFontsUrl = getGoogleFontsUrl;

View File

@@ -0,0 +1,6 @@
/// <reference types="node" />
import { Agent } from 'https';
/**
* If the http(s)_proxy environment variables is set, return a proxy agent.
*/
export declare function getProxyAgent(): Agent | undefined;

View File

@@ -0,0 +1,24 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getProxyAgent = void 0;
// @ts-ignore
const https_proxy_agent_1 = __importDefault(require("next/dist/compiled/https-proxy-agent"));
// @ts-ignore
const http_proxy_agent_1 = __importDefault(require("next/dist/compiled/http-proxy-agent"));
/**
* If the http(s)_proxy environment variables is set, return a proxy agent.
*/
function getProxyAgent() {
const httpsProxy = process.env['https_proxy'] || process.env['HTTPS_PROXY'];
if (httpsProxy) {
return new https_proxy_agent_1.default(httpsProxy);
}
const httpProxy = process.env['http_proxy'] || process.env['HTTP_PROXY'];
if (httpProxy) {
return new http_proxy_agent_1.default(httpProxy);
}
}
exports.getProxyAgent = getProxyAgent;

View File

@@ -0,0 +1,15 @@
type GoogleFontsMetadata = {
[fontFamily: string]: {
weights: string[];
styles: string[];
subsets: string[];
axes?: Array<{
tag: string;
min: number;
max: number;
defaultValue: number;
}>;
};
};
export declare const googleFontsMetadata: GoogleFontsMetadata;
export {};

View File

@@ -0,0 +1,8 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.googleFontsMetadata = void 0;
const font_data_json_1 = __importDefault(require("./font-data.json"));
exports.googleFontsMetadata = font_data_json_1.default;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,3 @@
import type { FontLoader } from 'next/font';
declare const nextFontGoogleFontLoader: FontLoader;
export default nextFontGoogleFontLoader;

View File

@@ -0,0 +1,165 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
// @ts-ignore
const Log = __importStar(require("next/dist/build/output/log"));
const validate_google_font_function_call_1 = require("./validate-google-font-function-call");
const get_font_axes_1 = require("./get-font-axes");
const get_google_fonts_url_1 = require("./get-google-fonts-url");
const next_font_error_1 = require("../next-font-error");
const find_font_files_in_css_1 = require("./find-font-files-in-css");
const get_fallback_font_override_metrics_1 = require("./get-fallback-font-override-metrics");
const fetch_css_from_google_fonts_1 = require("./fetch-css-from-google-fonts");
const fetch_font_file_1 = require("./fetch-font-file");
const cssCache = new Map();
const fontCache = new Map();
// regexp is based on https://github.com/sindresorhus/escape-string-regexp
const reHasRegExp = /[|\\{}()[\]^$+*?.-]/;
const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g;
function escapeStringRegexp(str) {
// see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23
if (reHasRegExp.test(str)) {
return str.replace(reReplaceRegExp, '\\$&');
}
return str;
}
const nextFontGoogleFontLoader = async ({ functionName, data, emitFontFile, isDev, isServer, }) => {
var _a;
const { fontFamily, weights, styles, display, preload, selectedVariableAxes, fallback, adjustFontFallback, variable, subsets, } = (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)(functionName, data[0]);
// Validate and get the font axes required to generated the URL
const fontAxes = (0, get_font_axes_1.getFontAxes)(fontFamily, weights, styles, selectedVariableAxes);
// Generate the Google Fonts URL from the font family, axes and display value
const url = (0, get_google_fonts_url_1.getGoogleFontsUrl)(fontFamily, fontAxes, display);
// Get precalculated fallback font metrics, used to generate the fallback font CSS
const adjustFontFallbackMetrics = adjustFontFallback ? (0, get_fallback_font_override_metrics_1.getFallbackFontOverrideMetrics)(fontFamily) : undefined;
const result = {
fallbackFonts: fallback,
weight: weights.length === 1 && weights[0] !== 'variable'
? weights[0]
: undefined,
style: styles.length === 1 ? styles[0] : undefined,
variable,
adjustFontFallback: adjustFontFallbackMetrics,
};
try {
/**
* Hacky way to make sure the fetch is only done once.
* Otherwise both the client and server compiler would fetch the CSS.
* The reason we need to return the actual CSS from both the server and client is because a hash is generated based on the CSS content.
*/
const hasCachedCSS = cssCache.has(url);
// Fetch CSS from Google Fonts or get it from the cache
let fontFaceDeclarations = hasCachedCSS
? cssCache.get(url)
: await (0, fetch_css_from_google_fonts_1.fetchCSSFromGoogleFonts)(url, fontFamily, isDev).catch((err) => {
console.error(err);
return null;
});
if (!hasCachedCSS) {
cssCache.set(url, fontFaceDeclarations !== null && fontFaceDeclarations !== void 0 ? fontFaceDeclarations : null);
}
else {
cssCache.delete(url);
}
if (fontFaceDeclarations == null) {
(0, next_font_error_1.nextFontError)(`Failed to fetch \`${fontFamily}\` from Google Fonts.`);
}
// CSS Variables may be set on a body tag, ignore them to keep the CSS module pure
fontFaceDeclarations = fontFaceDeclarations.split('body {')[0];
// Find font files to download, provide the array of subsets we want to preload if preloading is enabled
const fontFiles = (0, find_font_files_in_css_1.findFontFilesInCss)(fontFaceDeclarations, preload ? subsets : undefined);
// Download the font files extracted from the CSS
const downloadedFiles = await Promise.all(fontFiles.map(async ({ googleFontFileUrl, preloadFontFile }) => {
const hasCachedFont = fontCache.has(googleFontFileUrl);
// Download the font file or get it from cache
const fontFileBuffer = hasCachedFont
? fontCache.get(googleFontFileUrl)
: await (0, fetch_font_file_1.fetchFontFile)(googleFontFileUrl, isDev).catch((err) => {
console.error(err);
return null;
});
if (!hasCachedFont) {
fontCache.set(googleFontFileUrl, fontFileBuffer !== null && fontFileBuffer !== void 0 ? fontFileBuffer : null);
}
else {
fontCache.delete(googleFontFileUrl);
}
if (fontFileBuffer == null) {
(0, next_font_error_1.nextFontError)(`Failed to fetch \`${fontFamily}\` from Google Fonts.`);
}
const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(googleFontFileUrl)[1];
// Emit font file to .next/static/media
const selfHostedFileUrl = emitFontFile(fontFileBuffer, ext, preloadFontFile, !!adjustFontFallbackMetrics);
return {
googleFontFileUrl,
selfHostedFileUrl,
};
}));
/**
* Replace the @font-face sources with the self-hosted files we just downloaded to .next/static/media
*
* E.g.
* @font-face {
* font-family: 'Inter';
* src: url(https://fonts.gstatic.com/...) -> url(/_next/static/media/_.woff2)
* }
*/
let updatedCssResponse = fontFaceDeclarations;
for (const { googleFontFileUrl, selfHostedFileUrl } of downloadedFiles) {
updatedCssResponse = updatedCssResponse.replace(new RegExp(escapeStringRegexp(googleFontFileUrl), 'g'), selfHostedFileUrl);
}
return {
...result,
css: updatedCssResponse,
};
}
catch (err) {
if (isDev) {
if (isServer) {
Log.error(`Failed to download \`${fontFamily}\` from Google Fonts. Using fallback font instead.\n\n${err.message}}`);
}
// In dev we should return the fallback font instead of throwing an error
let css = `@font-face {
font-family: '${fontFamily} Fallback';
src: local("${(_a = adjustFontFallbackMetrics === null || adjustFontFallbackMetrics === void 0 ? void 0 : adjustFontFallbackMetrics.fallbackFont) !== null && _a !== void 0 ? _a : 'Arial'}");`;
if (adjustFontFallbackMetrics) {
css += `
ascent-override:${adjustFontFallbackMetrics.ascentOverride};
descent-override:${adjustFontFallbackMetrics.descentOverride};
line-gap-override:${adjustFontFallbackMetrics.lineGapOverride};
size-adjust:${adjustFontFallbackMetrics.sizeAdjust};`;
}
css += '\n}';
return {
...result,
css,
};
}
else {
throw err;
}
}
};
exports.default = nextFontGoogleFontLoader;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,144 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const loader_1 = __importDefault(require("./loader"));
// @ts-ignore
const node_fetch_1 = __importDefault(require("next/dist/compiled/node-fetch"));
jest.mock('next/dist/compiled/node-fetch');
describe('next/font/google loader', () => {
afterEach(() => {
jest.resetAllMocks();
});
describe('URL from options', () => {
const fixtures = [
[
'Inter',
{},
'https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap',
],
[
'Inter',
{ weight: '400' },
'https://fonts.googleapis.com/css2?family=Inter:wght@400&display=swap',
],
[
'Inter',
{ weight: '900', display: 'block' },
'https://fonts.googleapis.com/css2?family=Inter:wght@900&display=block',
],
[
'Source_Sans_3',
{ weight: '900', display: 'auto' },
'https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@900&display=auto',
],
[
'Source_Sans_3',
{ weight: '200', style: 'italic' },
'https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@1,200&display=swap',
],
[
'Roboto_Flex',
{ display: 'swap' },
'https://fonts.googleapis.com/css2?family=Roboto+Flex:wght@100..1000&display=swap',
],
[
'Roboto_Flex',
{ display: 'fallback', weight: 'variable', axes: ['opsz'] },
'https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,100..1000&display=fallback',
],
[
'Roboto_Flex',
{
display: 'optional',
axes: ['YTUC', 'slnt', 'wdth', 'opsz', 'XTRA', 'YTAS'],
},
'https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,slnt,wdth,wght,XTRA,YTAS,YTUC@8..144,-10..0,25..151,100..1000,323..603,649..854,528..760&display=optional',
],
[
'Oooh_Baby',
{ weight: '400' },
'https://fonts.googleapis.com/css2?family=Oooh+Baby:wght@400&display=swap',
],
[
'Albert_Sans',
{ weight: 'variable', style: 'italic' },
'https://fonts.googleapis.com/css2?family=Albert+Sans:ital,wght@1,100..900&display=swap',
],
[
'Fraunces',
{ weight: 'variable', style: 'italic', axes: ['WONK', 'opsz', 'SOFT'] },
'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@1,9..144,100..900,0..100,0..1&display=swap',
],
[
'Molle',
{ weight: '400' },
'https://fonts.googleapis.com/css2?family=Molle:ital,wght@1,400&display=swap',
],
[
'Roboto',
{ weight: ['500', '300', '400'], style: ['normal', 'italic'] },
'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap',
],
[
'Roboto Mono',
{ style: ['italic', 'normal'] },
'https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap',
],
[
'Fraunces',
{
style: ['normal', 'italic'],
axes: ['WONK', 'opsz', 'SOFT'],
},
'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1;1,9..144,100..900,0..100,0..1&display=swap',
],
[
'Poppins',
{ weight: ['900', '400', '100'] },
'https://fonts.googleapis.com/css2?family=Poppins:wght@100;400;900&display=swap',
],
[
'Nabla',
{},
'https://fonts.googleapis.com/css2?family=Nabla&display=swap',
],
[
'Nabla',
{ axes: ['EDPT', 'EHLT'] },
'https://fonts.googleapis.com/css2?family=Nabla:EDPT,EHLT@0..200,0..24&display=swap',
],
[
'Ballet',
{},
'https://fonts.googleapis.com/css2?family=Ballet&display=swap',
],
];
test.each(fixtures)('%s', async (functionName, fontFunctionArguments, expectedUrl) => {
node_fetch_1.default.mockResolvedValue({
ok: true,
text: async () => 'OK',
});
const { css } = await (0, loader_1.default)({
functionName,
data: [
{
adjustFontFallback: false,
subsets: [],
...fontFunctionArguments,
},
],
emitFontFile: jest.fn(),
resolve: jest.fn(),
loaderContext: {},
isDev: false,
isServer: true,
variableName: 'myFont',
});
expect(css).toBe('OK');
expect(node_fetch_1.default).toHaveBeenCalledTimes(1);
expect(node_fetch_1.default).toHaveBeenCalledWith(expectedUrl, expect.any(Object));
});
});
});

View File

@@ -0,0 +1,5 @@
/**
* Callback function for sorting font variant values.
* Used as a parameter in `Array.prototype.sort` function to ensure correct sorting.
*/
export declare function sortFontsVariantValues(valA: string, valB: string): number;

View File

@@ -0,0 +1,27 @@
"use strict";
/**
* Callback function for sorting font variant values.
* Used as a parameter in `Array.prototype.sort` function to ensure correct sorting.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.sortFontsVariantValues = void 0;
function sortFontsVariantValues(valA, valB) {
// If both values contain commas, it indicates they are in "ital,wght" format
if (valA.includes(',') && valB.includes(',')) {
// Split the values into prefix and suffix
const [aPrefix, aSuffix] = valA.split(',');
const [bPrefix, bSuffix] = valB.split(',');
// Compare the prefixes (ital values)
if (aPrefix === bPrefix) {
// If prefixes are equal, then compare the suffixes (wght values)
return parseInt(aSuffix) - parseInt(bSuffix);
}
else {
// If prefixes are different, then compare the prefixes directly
return parseInt(aPrefix) - parseInt(bPrefix);
}
}
// If values are not in "ital,wght" format, then directly compare them as integers
return parseInt(valA) - parseInt(valB);
}
exports.sortFontsVariantValues = sortFontsVariantValues;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const sort_fonts_variant_values_1 = require("./sort-fonts-variant-values");
describe('sortFontsVariantValues', () => {
it('should correctly compare and return result for plain integer values', () => {
// Testing plain integer values
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('100', '200')).toBe(-100);
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('200', '100')).toBe(100);
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('50', '150')).toBe(-100);
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('150', '50')).toBe(100);
});
it('should correctly compare and return result for comma-separated values', () => {
// Testing "ital,wght" format
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('1,100', '0,200')).toBe(1);
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('0,200', '1,100')).toBe(-1);
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('1,100', '1,200')).toBe(-100);
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('1,200', '1,100')).toBe(100);
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('0,100', '0,200')).toBe(-100);
expect((0, sort_fonts_variant_values_1.sortFontsVariantValues)('0,200', '0,100')).toBe(100);
});
it('should sort an array of plain integer values correctly', () => {
const unsortedArray = ['100', '1000', '300', '200', '500'];
const sortedArray = unsortedArray.slice().sort(sort_fonts_variant_values_1.sortFontsVariantValues);
expect(sortedArray).toEqual(['100', '200', '300', '500', '1000']);
});
it('should sort an array of values with comma-separated values correctly', () => {
const unsortedArray = ['1,100', '1,200', '0,100', '0,200'];
const sortedArray = unsortedArray.slice().sort(sort_fonts_variant_values_1.sortFontsVariantValues);
expect(sortedArray).toEqual(['0,100', '0,200', '1,100', '1,200']);
});
});

View File

@@ -0,0 +1,17 @@
type FontOptions = {
fontFamily: string;
weights: string[];
styles: string[];
display: string;
preload: boolean;
selectedVariableAxes?: string[];
fallback?: string[];
adjustFontFallback: boolean;
variable?: string;
subsets: string[];
};
/**
* Validate the data recieved from next-swc next-transform-font on next/font/google calls
*/
export declare function validateGoogleFontFunctionCall(functionName: string, fontFunctionArgument: any): FontOptions;
export {};

View File

@@ -0,0 +1,97 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateGoogleFontFunctionCall = void 0;
const constants_1 = require("../constants");
const format_available_values_1 = require("../format-available-values");
const next_font_error_1 = require("../next-font-error");
const google_fonts_metadata_1 = require("./google-fonts-metadata");
/**
* Validate the data recieved from next-swc next-transform-font on next/font/google calls
*/
function validateGoogleFontFunctionCall(functionName, fontFunctionArgument) {
let { weight, style, preload = true, display = 'swap', axes, fallback, adjustFontFallback = true, variable, subsets, } = fontFunctionArgument || {};
if (functionName === '') {
(0, next_font_error_1.nextFontError)(`next/font/google has no default export`);
}
const fontFamily = functionName.replace(/_/g, ' ');
// Get the Google font metadata, we'll use this to validate the font arguments and to print better error messages
const fontFamilyData = google_fonts_metadata_1.googleFontsMetadata[fontFamily];
if (!fontFamilyData) {
(0, next_font_error_1.nextFontError)(`Unknown font \`${fontFamily}\``);
}
const availableSubsets = fontFamilyData.subsets;
if (availableSubsets.length === 0) {
// If the font doesn't have any preloadeable subsets, disable preload
preload = false;
}
else if (preload) {
if (!subsets) {
(0, next_font_error_1.nextFontError)(`Preload is enabled but no subsets were specified for font \`${fontFamily}\`. Please specify subsets or disable preloading if your intended subset can't be preloaded.\nAvailable subsets: ${(0, format_available_values_1.formatAvailableValues)(availableSubsets)}\n\nRead more: https://nextjs.org/docs/messages/google-fonts-missing-subsets`);
}
subsets.forEach((subset) => {
if (!availableSubsets.includes(subset)) {
(0, next_font_error_1.nextFontError)(`Unknown subset \`${subset}\` for font \`${fontFamily}\`.\nAvailable subsets: ${(0, format_available_values_1.formatAvailableValues)(availableSubsets)}`);
}
});
}
const fontWeights = fontFamilyData.weights;
const fontStyles = fontFamilyData.styles;
// Get the unique weights and styles from the function call
const weights = !weight
? []
: [...new Set(Array.isArray(weight) ? weight : [weight])];
const styles = !style
? []
: [...new Set(Array.isArray(style) ? style : [style])];
if (weights.length === 0) {
// Set variable as default, throw if not available
if (fontWeights.includes('variable')) {
weights.push('variable');
}
else {
(0, next_font_error_1.nextFontError)(`Missing weight for font \`${fontFamily}\`.\nAvailable weights: ${(0, format_available_values_1.formatAvailableValues)(fontWeights)}`);
}
}
if (weights.length > 1 && weights.includes('variable')) {
(0, next_font_error_1.nextFontError)(`Unexpected \`variable\` in weight array for font \`${fontFamily}\`. You only need \`variable\`, it includes all available weights.`);
}
weights.forEach((selectedWeight) => {
if (!fontWeights.includes(selectedWeight)) {
(0, next_font_error_1.nextFontError)(`Unknown weight \`${selectedWeight}\` for font \`${fontFamily}\`.\nAvailable weights: ${(0, format_available_values_1.formatAvailableValues)(fontWeights)}`);
}
});
if (styles.length === 0) {
if (fontStyles.length === 1) {
// Handle default style for fonts that only have italic
styles.push(fontStyles[0]);
}
else {
// Otherwise set default style to normal
styles.push('normal');
}
}
styles.forEach((selectedStyle) => {
if (!fontStyles.includes(selectedStyle)) {
(0, next_font_error_1.nextFontError)(`Unknown style \`${selectedStyle}\` for font \`${fontFamily}\`.\nAvailable styles: ${(0, format_available_values_1.formatAvailableValues)(fontStyles)}`);
}
});
if (!constants_1.allowedDisplayValues.includes(display)) {
(0, next_font_error_1.nextFontError)(`Invalid display value \`${display}\` for font \`${fontFamily}\`.\nAvailable display values: ${(0, format_available_values_1.formatAvailableValues)(constants_1.allowedDisplayValues)}`);
}
if (weights[0] !== 'variable' && axes) {
(0, next_font_error_1.nextFontError)('Axes can only be defined for variable fonts');
}
return {
fontFamily,
weights,
styles,
display,
preload,
selectedVariableAxes: axes,
fallback,
adjustFontFallback,
variable,
subsets,
};
}
exports.validateGoogleFontFunctionCall = validateGoogleFontFunctionCall;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const validate_google_font_function_call_1 = require("./validate-google-font-function-call");
describe('validateFontFunctionCall errors', () => {
test('Missing function name', () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('', // default import
undefined)).toThrowErrorMatchingInlineSnapshot(`"next/font/google has no default export"`);
});
test('Unknown font', () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Unknown_Font', undefined)).toThrowErrorMatchingInlineSnapshot(`"Unknown font \`Unknown Font\`"`);
});
test('Unknown weight', () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Inter', {
weight: '123',
subsets: ['latin'],
})).toThrowErrorMatchingInlineSnapshot(`
"Unknown weight \`123\` for font \`Inter\`.
Available weights: \`100\`, \`200\`, \`300\`, \`400\`, \`500\`, \`600\`, \`700\`, \`800\`, \`900\`, \`variable\`"
`);
});
test('Missing weight for non variable font', () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Abel', { subsets: ['latin'] }))
.toThrowErrorMatchingInlineSnapshot(`
"Missing weight for font \`Abel\`.
Available weights: \`400\`"
`);
});
test('Unknown style', () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Molle', {
weight: '400',
style: 'normal',
subsets: ['latin'],
})).toThrowErrorMatchingInlineSnapshot(`
"Unknown style \`normal\` for font \`Molle\`.
Available styles: \`italic\`"
`);
});
test('Invalid display value', () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Inter', {
display: 'Invalid',
subsets: ['latin'],
})).toThrowErrorMatchingInlineSnapshot(`
"Invalid display value \`Invalid\` for font \`Inter\`.
Available display values: \`auto\`, \`block\`, \`swap\`, \`fallback\`, \`optional\`"
`);
});
test('Variable in weight array', async () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Inter', {
weight: ['100', 'variable'],
subsets: ['latin'],
})).toThrowErrorMatchingInlineSnapshot(`"Unexpected \`variable\` in weight array for font \`Inter\`. You only need \`variable\`, it includes all available weights."`);
});
test('Invalid subset in call', async () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Inter', { subsets: ['latin', 'oops'] })).toThrowErrorMatchingInlineSnapshot(`
"Unknown subset \`oops\` for font \`Inter\`.
Available subsets: \`cyrillic\`, \`cyrillic-ext\`, \`greek\`, \`greek-ext\`, \`latin\`, \`latin-ext\`, \`vietnamese\`"
`);
});
test('Missing subsets in config and call', async () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Inter', {}))
.toThrowErrorMatchingInlineSnapshot(`
"Preload is enabled but no subsets were specified for font \`Inter\`. Please specify subsets or disable preloading if your intended subset can't be preloaded.
Available subsets: \`cyrillic\`, \`cyrillic-ext\`, \`greek\`, \`greek-ext\`, \`latin\`, \`latin-ext\`, \`vietnamese\`
Read more: https://nextjs.org/docs/messages/google-fonts-missing-subsets"
`);
});
test('Setting axes on non variable font', async () => {
expect(() => (0, validate_google_font_function_call_1.validateGoogleFontFunctionCall)('Abel', {
weight: '400',
axes: [],
subsets: ['latin'],
})).toThrowErrorMatchingInlineSnapshot(`"Axes can only be defined for variable fonts"`);
});
});

View File

@@ -0,0 +1,18 @@
import type { Font } from 'fontkit';
import type { AdjustFontFallback } from 'next/font';
/**
* Given a font file and category, calculate the fallback font override values.
* The returned values can be used to generate a CSS @font-face declaration.
*
* For example:
* @font-face {
* font-family: local-font;
* src: local(Arial);
* size-adjust: 90%;
* }
*
* Read more about this technique in these texts by the Google Aurora team:
* https://developer.chrome.com/blog/font-fallbacks/
* https://docs.google.com/document/d/e/2PACX-1vRsazeNirATC7lIj2aErSHpK26hZ6dA9GsQ069GEbq5fyzXEhXbvByoftSfhG82aJXmrQ_sJCPBqcx_/pub
*/
export declare function getFallbackMetricsFromFontFile(font: Font, category?: string): AdjustFontFallback;

View File

@@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFallbackMetricsFromFontFile = void 0;
// The font metadata of the fallback fonts, retrieved with fontkit on system font files
// The average width is calculated with the calcAverageWidth function below
const DEFAULT_SANS_SERIF_FONT = {
name: 'Arial',
azAvgWidth: 934.5116279069767,
unitsPerEm: 2048,
};
const DEFAULT_SERIF_FONT = {
name: 'Times New Roman',
azAvgWidth: 854.3953488372093,
unitsPerEm: 2048,
};
/**
* Calculate the average character width of a font file.
* Used to calculate the size-adjust property by comparing the fallback average with the loaded font average.
*/
function calcAverageWidth(font) {
try {
/**
* Finding the right characters to use when calculating the average width is tricky.
* We can't just use the average width of all characters, because we have to take letter frequency into account.
* We also have to take word length into account, because the font's space width usually differ a lot from other characters.
* The goal is to find a string that'll give you a good average width, given most texts in most languages.
*
* TODO: Currently only works for the latin alphabet. Support more languages by finding the right characters for additional languages.
*
* The used characters were decided through trial and error with letter frequency and word length tables as a guideline.
* E.g. https://en.wikipedia.org/wiki/Letter_frequency
*/
const avgCharacters = 'aaabcdeeeefghiijklmnnoopqrrssttuvwxyz ';
// Check if the font file has all the characters we need to calculate the average width
const hasAllChars = font
.glyphsForString(avgCharacters)
.flatMap((glyph) => glyph.codePoints)
.every((codePoint) => font.hasGlyphForCodePoint(codePoint));
if (!hasAllChars)
return undefined;
const widths = font
.glyphsForString(avgCharacters)
.map((glyph) => glyph.advanceWidth);
const totalWidth = widths.reduce((sum, width) => sum + width, 0);
return totalWidth / widths.length;
}
catch {
// Could not calculate average width from the font file, skip size-adjust
return undefined;
}
}
function formatOverrideValue(val) {
return Math.abs(val * 100).toFixed(2) + '%';
}
/**
* Given a font file and category, calculate the fallback font override values.
* The returned values can be used to generate a CSS @font-face declaration.
*
* For example:
* @font-face {
* font-family: local-font;
* src: local(Arial);
* size-adjust: 90%;
* }
*
* Read more about this technique in these texts by the Google Aurora team:
* https://developer.chrome.com/blog/font-fallbacks/
* https://docs.google.com/document/d/e/2PACX-1vRsazeNirATC7lIj2aErSHpK26hZ6dA9GsQ069GEbq5fyzXEhXbvByoftSfhG82aJXmrQ_sJCPBqcx_/pub
*/
function getFallbackMetricsFromFontFile(font, category = 'serif') {
const fallbackFont = category === 'serif' ? DEFAULT_SERIF_FONT : DEFAULT_SANS_SERIF_FONT;
const azAvgWidth = calcAverageWidth(font);
const { ascent, descent, lineGap, unitsPerEm } = font;
const fallbackFontAvgWidth = fallbackFont.azAvgWidth / fallbackFont.unitsPerEm;
let sizeAdjust = azAvgWidth
? azAvgWidth / unitsPerEm / fallbackFontAvgWidth
: 1;
return {
ascentOverride: formatOverrideValue(ascent / (unitsPerEm * sizeAdjust)),
descentOverride: formatOverrideValue(descent / (unitsPerEm * sizeAdjust)),
lineGapOverride: formatOverrideValue(lineGap / (unitsPerEm * sizeAdjust)),
fallbackFont: fallbackFont.name,
sizeAdjust: formatOverrideValue(sizeAdjust),
};
}
exports.getFallbackMetricsFromFontFile = getFallbackMetricsFromFontFile;

View File

@@ -0,0 +1,21 @@
import type { CssVariable, Display, NextFont, NextFontWithVariable } from '../types';
type LocalFont<T extends CssVariable | undefined = undefined> = {
src: string | Array<{
path: string;
weight?: string;
style?: string;
}>;
display?: Display;
weight?: string;
style?: string;
adjustFontFallback?: 'Arial' | 'Times New Roman' | false;
fallback?: string[];
preload?: boolean;
variable?: T;
declarations?: Array<{
prop: string;
value: string;
}>;
};
export default function localFont<T extends CssVariable | undefined = undefined>(options: LocalFont<T>): T extends undefined ? NextFont : NextFontWithVariable;
export {};

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function localFont(options) {
throw new Error();
}
exports.default = localFont;

View File

@@ -0,0 +1,3 @@
import type { FontLoader } from 'next/font';
declare const nextFontLocalFontLoader: FontLoader;
export default nextFontLocalFontLoader;

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// @ts-ignore
// eslint-disable-next-line import/no-extraneous-dependencies
let fontFromBuffer;
try {
const mod = require('../fontkit').default;
fontFromBuffer = mod.default || mod;
}
catch { }
const util_1 = require("util");
const pick_font_file_for_fallback_generation_1 = require("./pick-font-file-for-fallback-generation");
const get_fallback_metrics_from_font_file_1 = require("./get-fallback-metrics-from-font-file");
const validate_local_font_function_call_1 = require("./validate-local-font-function-call");
const nextFontLocalFontLoader = async ({ functionName, variableName, data, emitFontFile, resolve, loaderContext, }) => {
const { src, display, fallback, preload, variable, adjustFontFallback, declarations, weight: defaultWeight, style: defaultStyle, } = (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)(functionName, data[0]);
// Load all font files and emit them to the .next output directory
// Also generate a @font-face CSS for each font file
const fontFiles = await Promise.all(src.map(async ({ path, style, weight, ext, format }) => {
const resolved = await resolve(path);
const fileBuffer = await (0, util_1.promisify)(loaderContext.fs.readFile)(resolved);
const fontUrl = emitFontFile(fileBuffer, ext, preload, typeof adjustFontFallback === 'undefined' || !!adjustFontFallback);
// Try to load font metadata from the font file using fontkit.
// The data is used to calculate the fallback font override values.
let fontMetadata;
try {
fontMetadata = fontFromBuffer === null || fontFromBuffer === void 0 ? void 0 : fontFromBuffer(fileBuffer);
}
catch (e) {
console.error(`Failed to load font file: ${resolved}\n${e}`);
}
// Get all values that should be added to the @font-face declaration
const fontFaceProperties = [
...(declarations
? declarations.map(({ prop, value }) => [prop, value])
: []),
['font-family', variableName],
['src', `url(${fontUrl}) format('${format}')`],
['font-display', display],
...((weight !== null && weight !== void 0 ? weight : defaultWeight)
? [['font-weight', weight !== null && weight !== void 0 ? weight : defaultWeight]]
: []),
...((style !== null && style !== void 0 ? style : defaultStyle)
? [['font-style', style !== null && style !== void 0 ? style : defaultStyle]]
: []),
];
// Generate the @font-face CSS from the font-face properties
const css = `@font-face {\n${fontFaceProperties
.map(([property, value]) => `${property}: ${value};`)
.join('\n')}\n}\n`;
return {
css,
fontMetadata,
weight,
style,
};
}));
// Calculate the fallback font override values using the font file metadata
let adjustFontFallbackMetrics;
if (adjustFontFallback !== false) {
const fallbackFontFile = (0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)(fontFiles);
if (fallbackFontFile.fontMetadata) {
adjustFontFallbackMetrics = (0, get_fallback_metrics_from_font_file_1.getFallbackMetricsFromFontFile)(fallbackFontFile.fontMetadata, adjustFontFallback === 'Times New Roman' ? 'serif' : 'sans-serif');
}
}
return {
css: fontFiles.map(({ css }) => css).join('\n'),
fallbackFonts: fallback,
weight: src.length === 1 ? src[0].weight : undefined,
style: src.length === 1 ? src[0].style : undefined,
variable,
adjustFontFallback: adjustFontFallbackMetrics,
};
};
exports.default = nextFontLocalFontLoader;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,228 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const loader_1 = __importDefault(require("./loader"));
describe('next/font/local loader', () => {
describe('generated CSS', () => {
test('Default CSS', async () => {
const { css } = await (0, loader_1.default)({
functionName: '',
data: [{ src: './my-font.woff2' }],
emitFontFile: () => '/_next/static/media/my-font.woff2',
resolve: jest.fn(),
isDev: false,
isServer: true,
variableName: 'myFont',
loaderContext: {
fs: {
readFile: (_, cb) => cb(null, 'fontdata'),
},
},
});
expect(css).toMatchInlineSnapshot(`
"@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
}
"
`);
});
test('Weight and style', async () => {
const { css } = await (0, loader_1.default)({
functionName: '',
data: [{ src: './my-font.woff2', weight: '100 900', style: 'italic' }],
emitFontFile: () => '/_next/static/media/my-font.woff2',
resolve: jest.fn(),
isDev: false,
isServer: true,
variableName: 'myFont',
loaderContext: {
fs: {
readFile: (_, cb) => cb(null, 'fontdata'),
},
},
});
expect(css).toMatchInlineSnapshot(`
"@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
font-weight: 100 900;
font-style: italic;
}
"
`);
});
test('Other properties', async () => {
const { css } = await (0, loader_1.default)({
functionName: '',
data: [
{
src: './my-font.woff2',
declarations: [
{ prop: 'font-feature-settings', value: '"smcp" on' },
{ prop: 'ascent-override', value: '90%' },
],
},
],
emitFontFile: () => '/_next/static/media/my-font.woff2',
resolve: jest.fn(),
isDev: false,
isServer: true,
variableName: 'myFont',
loaderContext: {
fs: {
readFile: (_, cb) => cb(null, 'fontdata'),
},
},
});
expect(css).toMatchInlineSnapshot(`
"@font-face {
font-feature-settings: \\"smcp\\" on;
ascent-override: 90%;
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
}
"
`);
});
test('Multiple weights default style', async () => {
const { css } = await (0, loader_1.default)({
functionName: '',
data: [
{
style: 'italic',
src: [
{
path: './fonts/font1.woff2',
weight: '100',
},
{
path: './fonts/font2.woff2',
weight: '400',
},
{
path: './fonts/font3.woff2',
weight: '700',
},
{
path: './fonts/font2.woff2',
weight: '400',
style: 'normal',
},
],
adjustFontFallback: false,
},
],
emitFontFile: () => `/_next/static/media/my-font.woff2`,
resolve: jest.fn(),
isDev: false,
isServer: true,
variableName: 'myFont',
loaderContext: {
fs: {
readFile: (path, cb) => cb(null, path),
},
},
});
expect(css).toMatchInlineSnapshot(`
"@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
font-weight: 400;
font-style: normal;
}
"
`);
});
test('Multiple styles default weight', async () => {
const { css } = await (0, loader_1.default)({
functionName: '',
data: [
{
weight: '400',
src: [
{
path: './fonts/font1.woff2',
style: 'normal',
},
{
path: './fonts/font3.woff2',
style: 'italic',
},
{
path: './fonts/font2.woff2',
weight: '700',
},
],
adjustFontFallback: false,
},
],
emitFontFile: () => `/_next/static/media/my-font.woff2`,
resolve: jest.fn(),
isDev: false,
isServer: true,
variableName: 'myFont',
loaderContext: {
fs: {
readFile: (path, cb) => cb(null, path),
},
},
});
expect(css).toMatchInlineSnapshot(`
"@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: myFont;
src: url(/_next/static/media/my-font.woff2) format('woff2');
font-display: swap;
font-weight: 700;
}
"
`);
});
});
});

View File

@@ -0,0 +1,13 @@
/**
* If multiple font files are provided for a font family, we need to pick one to use for the automatic fallback generation.
* This function returns the font file that is most likely to be used for the bulk of the text on a page.
*
* There are some assumptions here about the text on a page when picking the font file:
* - Most of the text will have normal weight, use the one closest to 400
* - Most of the text will have normal style, prefer normal over italic
* - If two font files have the same distance from normal weight, the thinner one will most likely be the bulk of the text
*/
export declare function pickFontFileForFallbackGeneration<T extends {
style?: string;
weight?: string;
}>(fontFiles: T[]): T;

View File

@@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pickFontFileForFallbackGeneration = void 0;
const next_font_error_1 = require("../next-font-error");
const NORMAL_WEIGHT = 400;
const BOLD_WEIGHT = 700;
/**
* Convert the weight string to a number so it can be used for comparison.
* Weights can be defined as a number, 'normal' or 'bold'. https://developer.mozilla.org/docs/Web/CSS/@font-face/font-weight
*/
function getWeightNumber(weight) {
return weight === 'normal'
? NORMAL_WEIGHT
: weight === 'bold'
? BOLD_WEIGHT
: Number(weight);
}
/**
* Get the distance from normal (400) weight for the provided weight.
* If it's not a variable font we can just return the distance.
* If it's a variable font we need to compare its weight range to 400.
*/
function getDistanceFromNormalWeight(weight) {
if (!weight)
return 0;
// If it's a variable font the weight is defined with two numbers "100 900", rather than just one "400"
const [firstWeight, secondWeight] = weight
.trim()
.split(/ +/)
.map(getWeightNumber);
if (Number.isNaN(firstWeight) || Number.isNaN(secondWeight)) {
(0, next_font_error_1.nextFontError)(`Invalid weight value in src array: \`${weight}\`.\nExpected \`normal\`, \`bold\` or a number.`);
}
// If the weight doesn't have have a second value, it's not a variable font
// If that's the case, just return the distance from normal weight
if (!secondWeight) {
return firstWeight - NORMAL_WEIGHT;
}
// Normal weight is within variable font range
if (firstWeight <= NORMAL_WEIGHT && secondWeight >= NORMAL_WEIGHT) {
return 0;
}
// Normal weight is outside variable font range
// Return the distance of normal weight to the variable font range
const firstWeightDistance = firstWeight - NORMAL_WEIGHT;
const secondWeightDistance = secondWeight - NORMAL_WEIGHT;
if (Math.abs(firstWeightDistance) < Math.abs(secondWeightDistance)) {
return firstWeightDistance;
}
return secondWeightDistance;
}
/**
* If multiple font files are provided for a font family, we need to pick one to use for the automatic fallback generation.
* This function returns the font file that is most likely to be used for the bulk of the text on a page.
*
* There are some assumptions here about the text on a page when picking the font file:
* - Most of the text will have normal weight, use the one closest to 400
* - Most of the text will have normal style, prefer normal over italic
* - If two font files have the same distance from normal weight, the thinner one will most likely be the bulk of the text
*/
function pickFontFileForFallbackGeneration(fontFiles) {
return fontFiles.reduce((usedFontFile, currentFontFile) => {
if (!usedFontFile)
return currentFontFile;
const usedFontDistance = getDistanceFromNormalWeight(usedFontFile.weight);
const currentFontDistance = getDistanceFromNormalWeight(currentFontFile.weight);
// Prefer normal style if they have the same weight
if (usedFontDistance === currentFontDistance &&
(typeof currentFontFile.style === 'undefined' ||
currentFontFile.style === 'normal')) {
return currentFontFile;
}
const absUsedDistance = Math.abs(usedFontDistance);
const absCurrentDistance = Math.abs(currentFontDistance);
// Use closest absolute distance to normal weight
if (absCurrentDistance < absUsedDistance)
return currentFontFile;
// Prefer the thinner font if both have the same absolute distance from normal weight
if (absUsedDistance === absCurrentDistance &&
currentFontDistance < usedFontDistance) {
return currentFontFile;
}
return usedFontFile;
});
}
exports.pickFontFileForFallbackGeneration = pickFontFileForFallbackGeneration;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,113 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const pick_font_file_for_fallback_generation_1 = require("./pick-font-file-for-fallback-generation");
describe('pickFontFileForFallbackGeneration', () => {
it('should pick the weight closest to 400', () => {
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{
weight: '300',
},
{
weight: '600',
},
])).toEqual({
weight: '300',
});
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{ weight: '200' },
{
weight: '500',
},
])).toEqual({
weight: '500',
});
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{
weight: 'normal',
},
{
weight: '700',
},
])).toEqual({
weight: 'normal',
});
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{
weight: 'bold',
},
{
weight: '900',
},
])).toEqual({
weight: 'bold',
});
});
it('should pick the thinner weight if both have the same distance to 400', () => {
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{
weight: '300',
},
{
weight: '500',
},
])).toEqual({
weight: '300',
});
});
it('should pick variable range closest to 400', () => {
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{
weight: '100 300',
},
{
weight: '600 900',
},
])).toEqual({
weight: '100 300',
});
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{ weight: '100 200' },
{
weight: '500 800',
},
])).toEqual({
weight: '500 800',
});
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{ weight: '100 900' },
{
weight: '300 399',
},
])).toEqual({
weight: '100 900',
});
});
it('should prefer normal style over italic', () => {
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{ weight: '400', style: 'normal' },
{ weight: '400', style: 'italic' },
])).toEqual({ weight: '400', style: 'normal' });
});
it('should error on invalid weight in array', async () => {
expect(() => (0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{ path: './font1.woff2', weight: 'normal bold' },
{ path: './font2.woff2', weight: '400 bold' },
{ path: './font3.woff2', weight: 'normal 700' },
{ path: './font4.woff2', weight: '100 abc' },
])).toThrowErrorMatchingInlineSnapshot(`
"Invalid weight value in src array: \`100 abc\`.
Expected \`normal\`, \`bold\` or a number."
`);
});
test('Invalid variable weight in array', async () => {
expect(() => (0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
{ path: './font1.woff2', weight: 'normal bold' },
{ path: './font2.woff2', weight: '400 bold' },
{ path: './font3.woff2', weight: 'normal 700' },
{ path: './font4.woff2', weight: '100 abc' },
])).toThrowErrorMatchingInlineSnapshot(`
"Invalid weight value in src array: \`100 abc\`.
Expected \`normal\`, \`bold\` or a number."
`);
});
});

View File

@@ -0,0 +1,25 @@
type FontOptions = {
src: Array<{
path: string;
weight?: string;
style?: string;
ext: string;
format: string;
}>;
display: string;
weight?: string;
style?: string;
fallback?: string[];
preload: boolean;
variable?: string;
adjustFontFallback?: string | false;
declarations?: Array<{
prop: string;
value: string;
}>;
};
/**
* Validate the data recieved from next-swc next-transform-font on next/font/local calls
*/
export declare function validateLocalFontFunctionCall(functionName: string, fontData: any): FontOptions;
export {};

View File

@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateLocalFontFunctionCall = void 0;
const constants_1 = require("../constants");
const format_available_values_1 = require("../format-available-values");
const next_font_error_1 = require("../next-font-error");
const extToFormat = {
woff: 'woff',
woff2: 'woff2',
ttf: 'truetype',
otf: 'opentype',
eot: 'embedded-opentype',
};
/**
* Validate the data recieved from next-swc next-transform-font on next/font/local calls
*/
function validateLocalFontFunctionCall(functionName, fontData) {
if (functionName) {
(0, next_font_error_1.nextFontError)(`next/font/local has no named exports`);
}
let { src, display = 'swap', weight, style, fallback, preload = true, variable, adjustFontFallback, declarations, } = fontData || {};
if (!constants_1.allowedDisplayValues.includes(display)) {
(0, next_font_error_1.nextFontError)(`Invalid display value \`${display}\`.\nAvailable display values: ${(0, format_available_values_1.formatAvailableValues)(constants_1.allowedDisplayValues)}`);
}
if (!src) {
(0, next_font_error_1.nextFontError)('Missing required `src` property');
}
if (!Array.isArray(src)) {
src = [{ path: src, weight, style }];
}
else {
if (src.length === 0) {
(0, next_font_error_1.nextFontError)('Unexpected empty `src` array.');
}
}
src = src.map((fontFile) => {
var _a;
const ext = (_a = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile.path)) === null || _a === void 0 ? void 0 : _a[1];
if (!ext) {
(0, next_font_error_1.nextFontError)(`Unexpected file \`${fontFile.path}\``);
}
return {
...fontFile,
ext,
format: extToFormat[ext],
};
});
if (Array.isArray(declarations)) {
declarations.forEach((declaration) => {
if ([
'font-family',
'src',
'font-display',
'font-weight',
'font-style',
].includes(declaration === null || declaration === void 0 ? void 0 : declaration.prop)) {
(0, next_font_error_1.nextFontError)(`Invalid declaration prop: \`${declaration.prop}\``);
}
});
}
return {
src,
display,
weight,
style,
fallback,
preload,
variable,
adjustFontFallback,
declarations,
};
}
exports.validateLocalFontFunctionCall = validateLocalFontFunctionCall;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const validate_local_font_function_call_1 = require("./validate-local-font-function-call");
describe('validateLocalFontFunctionCall', () => {
test('Not using default export', async () => {
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('Named', {})).toThrowErrorMatchingInlineSnapshot(`"next/font/local has no named exports"`);
});
test('Missing src', async () => {
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', {})).toThrowErrorMatchingInlineSnapshot(`"Missing required \`src\` property"`);
});
test('Invalid file extension', async () => {
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', { src: './font/font-file.abc' })).toThrowErrorMatchingInlineSnapshot(`"Unexpected file \`./font/font-file.abc\`"`);
});
test('Invalid display value', async () => {
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', {
src: './font-file.woff2',
display: 'invalid',
})).toThrowErrorMatchingInlineSnapshot(`
"Invalid display value \`invalid\`.
Available display values: \`auto\`, \`block\`, \`swap\`, \`fallback\`, \`optional\`"
`);
});
test('Invalid declaration', async () => {
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', {
src: './font-file.woff2',
declarations: [{ prop: 'src', value: '/hello.woff2' }],
})).toThrowErrorMatchingInlineSnapshot(`"Invalid declaration prop: \`src\`"`);
});
test('Empty src array', async () => {
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', {
src: [],
})).toThrowErrorMatchingInlineSnapshot(`"Unexpected empty \`src\` array."`);
});
});

View File

@@ -0,0 +1,4 @@
/**
* Throw NextFontError error. Used by the WellKnownErrorsPlugin to format errors thrown by next/font.
*/
export declare function nextFontError(message: string): never;

View File

@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.nextFontError = void 0;
/**
* Throw NextFontError error. Used by the WellKnownErrorsPlugin to format errors thrown by next/font.
*/
function nextFontError(message) {
const err = new Error(message);
err.name = 'NextFontError';
throw err;
}
exports.nextFontError = nextFontError;

View File

@@ -0,0 +1,13 @@
export type CssVariable = `--${string}`;
export type Display = 'auto' | 'block' | 'swap' | 'fallback' | 'optional';
export type NextFont = {
className: string;
style: {
fontFamily: string;
fontWeight?: number;
fontStyle?: string;
};
};
export type NextFontWithVariable = NextFont & {
variable: string;
};

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1 @@
export * from '../dist/google'

View File

@@ -0,0 +1,15 @@
// Validate next version
const semver = require('next/dist/compiled/semver')
if (semver.lt(require('next/package.json').version, '13.0.0')) {
throw new Error('`@next/font` is only available in Next.js 13 and newer.')
}
let message = '@next/font/google failed to run or is incorrectly configured.'
if (process.env.NODE_ENV === 'development') {
message +=
'\nIf you just installed `@next/font`, please try restarting `next dev` and resaving your file.'
}
message += `\n\nRead more: https://nextjs.org/docs/basic-features/font-optimization`
throw new Error(message)

View File

@@ -0,0 +1 @@
export { default } from '../dist/google/loader'

View File

@@ -0,0 +1 @@
module.exports = require('../dist/google/loader')

View File

@@ -0,0 +1 @@
export { default } from '../dist/local/index'

View File

@@ -0,0 +1,15 @@
// Validate next version
const semver = require('next/dist/compiled/semver')
if (semver.lt(require('next/package.json').version, '13.0.0')) {
throw new Error('`@next/font` is only available in Next.js 13 and newer.')
}
let message = '@next/font/local failed to run or is incorrectly configured.'
if (process.env.NODE_ENV === 'development') {
message +=
'\nIf you just installed `@next/font`, please try restarting `next dev` and resaving your file.'
}
message += `\n\nRead more: https://nextjs.org/docs/basic-features/font-optimization`
throw new Error(message)

View File

@@ -0,0 +1 @@
export { default } from '../dist/local/loader'

View File

@@ -0,0 +1 @@
module.exports = require('../dist/local/loader')

View File

@@ -0,0 +1 @@
{"name":"@next/font","license":"MIT","types":"dist/types.d.ts"}