@nebutra/fonts 2.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/NOTICE-FONTS.md +31 -11
- package/README.md +92 -81
- package/dist/chunk-A4PPXTH5.js +38 -0
- package/dist/chunk-A4PPXTH5.js.map +1 -0
- package/dist/chunk-FUPJK5RT.js +20 -0
- package/dist/chunk-FUPJK5RT.js.map +1 -0
- package/dist/generated/index.d.ts +23 -23
- package/dist/generated/index.js +12 -16
- package/dist/generated/index.js.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/next-cjk.d.ts +40 -38
- package/dist/next-cjk.js +10 -3
- package/dist/next.d.ts +2 -1
- package/dist/next.js +206 -57
- package/dist/next.js.map +1 -1
- package/generated/index.ts +18 -15
- package/generated/subset-manifest.json +63 -5
- package/package.json +31 -5
- package/src/cjk-font-face.tsx +53 -0
- package/src/index.ts +6 -5
- package/src/next-cjk.test.ts +37 -30
- package/src/next-cjk.ts +36 -52
- package/src/next.test.ts +68 -0
- package/src/next.ts +238 -72
- package/vendor/{noto-sans-sc → dm-sans}/OFL.txt +93 -92
- package/vendor/misans/LICENSE.txt +13 -0
- package/dist/chunk-GRE7GS6W.js +0 -27
- package/dist/chunk-GRE7GS6W.js.map +0 -1
- package/generated/noto-sans-sc.css +0 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/fonts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Self-hosted OSS font registry — build-time next/font faces + a name→CSS-var resolver so theme / DESIGN.md fonts render with zero runtime external requests",
|
|
6
6
|
"private": false,
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
27
|
"src",
|
|
28
|
-
"generated/noto-sans-sc.css",
|
|
29
28
|
"generated/subset-manifest.json",
|
|
30
29
|
"generated/index.ts",
|
|
31
|
-
"vendor/
|
|
30
|
+
"vendor/misans/LICENSE.txt",
|
|
31
|
+
"vendor/dm-sans/OFL.txt",
|
|
32
32
|
"NOTICE-FONTS.md",
|
|
33
33
|
"README.md",
|
|
34
34
|
"LICENSE",
|
|
@@ -57,12 +57,38 @@
|
|
|
57
57
|
"import": "./src/next-cjk.ts",
|
|
58
58
|
"default": "./src/next-cjk.ts"
|
|
59
59
|
},
|
|
60
|
+
"./font-face": {
|
|
61
|
+
"types": "./src/cjk-font-face.tsx",
|
|
62
|
+
"import": "./src/cjk-font-face.tsx",
|
|
63
|
+
"default": "./src/cjk-font-face.tsx"
|
|
64
|
+
},
|
|
60
65
|
"./cjk": {
|
|
61
66
|
"types": "./dist/generated/index.d.ts",
|
|
62
67
|
"import": "./dist/generated/index.js",
|
|
63
68
|
"default": "./dist/generated/index.js"
|
|
64
|
-
}
|
|
65
|
-
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"@fontsource-variable/dm-sans": "5.3.0",
|
|
73
|
+
"@fontsource-variable/figtree": "5.3.0",
|
|
74
|
+
"@fontsource-variable/fira-code": "5.3.0",
|
|
75
|
+
"@fontsource-variable/fraunces": "5.3.0",
|
|
76
|
+
"@fontsource-variable/inter": "5.3.0",
|
|
77
|
+
"@fontsource-variable/inter-tight": "5.3.0",
|
|
78
|
+
"@fontsource-variable/jetbrains-mono": "5.3.0",
|
|
79
|
+
"@fontsource-variable/lexend": "5.3.0",
|
|
80
|
+
"@fontsource-variable/manrope": "5.3.0",
|
|
81
|
+
"@fontsource-variable/montserrat": "5.3.0",
|
|
82
|
+
"@fontsource-variable/outfit": "5.3.0",
|
|
83
|
+
"@fontsource-variable/playfair-display": "5.3.0",
|
|
84
|
+
"@fontsource-variable/plus-jakarta-sans": "5.3.0",
|
|
85
|
+
"@fontsource-variable/roboto-mono": "5.3.0",
|
|
86
|
+
"@fontsource-variable/sora": "5.3.0",
|
|
87
|
+
"@fontsource-variable/source-code-pro": "5.3.0",
|
|
88
|
+
"@fontsource-variable/source-serif-4": "5.3.0",
|
|
89
|
+
"@fontsource-variable/space-grotesk": "5.3.0",
|
|
90
|
+
"@fontsource-variable/work-sans": "5.3.0",
|
|
91
|
+
"@nebutra/brand": "4.0.0"
|
|
66
92
|
},
|
|
67
93
|
"devDependencies": {
|
|
68
94
|
"@types/react": "^19.2.14",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MiSans @font-face, rendered at request time rather than shipped as CSS.
|
|
3
|
+
*
|
|
4
|
+
* The MiSans licence forbids distributing the font on its own and this
|
|
5
|
+
* repository is public, so the subsets live in the deployment's public asset
|
|
6
|
+
* bucket and only their keys are committed (../generated/index.ts). A static
|
|
7
|
+
* stylesheet would have to spell out a host, and the host differs per
|
|
8
|
+
* deployment: Nebutra's CDN for Nebutra, the scaffold's own for a template
|
|
9
|
+
* user. publicAssetUrl() resolves it the way it resolves every other public
|
|
10
|
+
* asset — NEXT_PUBLIC_R2_PUBLIC_URL, then R2_PUBLIC_URL, then the brand's cdn
|
|
11
|
+
* origin.
|
|
12
|
+
*
|
|
13
|
+
* Before the subsets have been uploaded (a fresh scaffold, `pnpm subset:cjk
|
|
14
|
+
* --upload` not yet run) the requests 404 and the token stacks fall through to
|
|
15
|
+
* PingFang / YaHei. That is the intended degraded state, not an error.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { publicAssetUrl } from "@nebutra/brand/metadata-helpers";
|
|
19
|
+
import { MISANS_FAMILY, MISANS_FILES, MISANS_UNICODE_RANGE } from "../generated/index";
|
|
20
|
+
|
|
21
|
+
/** One @font-face per weight, CJK-only unicode-range, `swap` so text is never invisible. */
|
|
22
|
+
export function misansFontFaceCss(origin?: string): string {
|
|
23
|
+
return MISANS_FILES.map(
|
|
24
|
+
(face) =>
|
|
25
|
+
`@font-face{font-family:"${MISANS_FAMILY}";font-style:normal;font-weight:${face.weight};` +
|
|
26
|
+
`font-display:swap;src:url("${publicAssetUrl(face.key, origin)}") format("woff2");` +
|
|
27
|
+
`unicode-range:${MISANS_UNICODE_RANGE}}`,
|
|
28
|
+
).join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CjkFontFaceProps {
|
|
32
|
+
/** Asset origin override; defaults to the publicAssetUrl() resolution. */
|
|
33
|
+
origin?: string;
|
|
34
|
+
/**
|
|
35
|
+
* CSP nonce, for apps whose style-src allows inline styles only by nonce
|
|
36
|
+
* (apps/web). Without it the whole @font-face block is refused. The page's
|
|
37
|
+
* font-src must also allow publicAssetOrigin().
|
|
38
|
+
*/
|
|
39
|
+
nonce?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Render once in each root layout, next to `cjkFontClassName` on <html>.
|
|
44
|
+
* React 19 hoists a `<style>` carrying `href` + `precedence` into <head> and
|
|
45
|
+
* dedupes it by `href`, so rendering it twice costs nothing.
|
|
46
|
+
*/
|
|
47
|
+
export function CjkFontFace({ origin, nonce }: CjkFontFaceProps) {
|
|
48
|
+
return (
|
|
49
|
+
<style href="nebutra-misans" precedence="default" nonce={nonce || undefined}>
|
|
50
|
+
{misansFontFaceCss(origin)}
|
|
51
|
+
</style>
|
|
52
|
+
);
|
|
53
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -23,11 +23,12 @@ export const FONT_REGISTRY: Record<string, string> = {
|
|
|
23
23
|
geist: "--font-geist-sans",
|
|
24
24
|
"geist sans": "--font-geist-sans",
|
|
25
25
|
"geist mono": "--font-geist-mono",
|
|
26
|
-
// Self-hosted via next/font/local
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// Self-hosted via next/font/local (see ./next-cjk). MiSans is the
|
|
27
|
+
// Simplified-Chinese face — CJK only via unicode-range, so it cannot take
|
|
28
|
+
// Latin away from Geist. DM Sans is the Latin display/heading face.
|
|
29
|
+
misans: "--font-misans",
|
|
30
|
+
"dm sans display": "--font-dm-sans",
|
|
31
|
+
// Self-hosted via next/font/local over @fontsource-variable/* (see ./next)
|
|
31
32
|
inter: "--font-inter",
|
|
32
33
|
"inter tight": "--font-reg-inter-tight",
|
|
33
34
|
"space grotesk": "--font-space-grotesk",
|
package/src/next-cjk.test.ts
CHANGED
|
@@ -1,49 +1,56 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
NOTO_SANS_SC_UNICODE_RANGE,
|
|
7
|
-
NOTO_SANS_SC_VARIABLE,
|
|
8
|
-
} from "../generated/index";
|
|
4
|
+
import { MISANS_FILES, MISANS_UNICODE_RANGE } from "../generated/index";
|
|
5
|
+
import { misansFontFaceCss } from "./cjk-font-face";
|
|
9
6
|
import { FONT_REGISTRY } from "./index";
|
|
10
7
|
|
|
11
8
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* next/font/local
|
|
16
|
-
* literals still agree with what the subsetter actually emitted.
|
|
9
|
+
* MiSans is CDN-hosted (its licence forbids distributing the font on its own,
|
|
10
|
+
* and this repo is public). Only content-hashed keys are committed; the host is
|
|
11
|
+
* resolved per deployment by publicAssetUrl(). DM Sans is self-hosted through
|
|
12
|
+
* next/font/local in ./next-cjk.ts, a compile-time transform — read as TEXT.
|
|
17
13
|
*/
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
14
|
+
const read = (rel: string) => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), "utf8");
|
|
15
|
+
const NEXT_CJK_SOURCE = read("./next-cjk.ts");
|
|
16
|
+
const GENERATED_INDEX = read("../generated/index.ts");
|
|
22
17
|
|
|
23
|
-
describe("
|
|
24
|
-
it("
|
|
25
|
-
for (const {
|
|
26
|
-
|
|
27
|
-
expect(NEXT_CJK_SOURCE).toContain(`"../generated/${file}", weight: "${weight}"`);
|
|
18
|
+
describe("MiSans wiring", () => {
|
|
19
|
+
it("commits keys, never a host", () => {
|
|
20
|
+
for (const { key } of MISANS_FILES) {
|
|
21
|
+
expect(key).toMatch(/^fonts\/misans\/misans-\d{3}\.[0-9a-f]{10}\.woff2$/);
|
|
28
22
|
}
|
|
23
|
+
expect(GENERATED_INDEX).not.toMatch(/https?:\/\//);
|
|
29
24
|
});
|
|
30
25
|
|
|
31
|
-
it("declares
|
|
32
|
-
const
|
|
33
|
-
|
|
26
|
+
it("declares one @font-face per weight against the given origin", () => {
|
|
27
|
+
const css = misansFontFaceCss("https://assets.example.test/");
|
|
28
|
+
for (const { key, weight } of MISANS_FILES) {
|
|
29
|
+
expect(css).toContain(`font-weight:${weight};`);
|
|
30
|
+
expect(css).toContain(`src:url("https://assets.example.test/${key}") format("woff2")`);
|
|
31
|
+
}
|
|
32
|
+
expect(css.match(/@font-face/g)).toHaveLength(MISANS_FILES.length);
|
|
33
|
+
expect(css).toContain("font-display:swap");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("carries a CJK-only unicode-range, so Latin never downloads a CJK file", () => {
|
|
37
|
+
expect(misansFontFaceCss("https://a.test")).toContain(`unicode-range:${MISANS_UNICODE_RANGE}`);
|
|
38
|
+
expect(MISANS_UNICODE_RANGE).not.toMatch(/U\+00[0-7]/i);
|
|
34
39
|
});
|
|
35
40
|
|
|
36
|
-
it("
|
|
37
|
-
expect(NEXT_CJK_SOURCE).
|
|
38
|
-
expect(NOTO_SANS_SC_UNICODE_RANGE).not.toMatch(/U\+00[0-7]/i);
|
|
41
|
+
it("never loads a MiSans binary through next/font", () => {
|
|
42
|
+
expect(NEXT_CJK_SOURCE).not.toMatch(/misans-\d{3}\.woff2/);
|
|
39
43
|
});
|
|
40
44
|
|
|
41
|
-
it("
|
|
42
|
-
expect(
|
|
43
|
-
expect(FONT_REGISTRY["noto sans sc"]).toBe(NOTO_SANS_SC_VARIABLE);
|
|
45
|
+
it("registers the variable the token stacks reference", () => {
|
|
46
|
+
expect(FONT_REGISTRY.misans).toBe("--font-misans");
|
|
44
47
|
});
|
|
48
|
+
});
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
describe("DM Sans wiring", () => {
|
|
51
|
+
it("self-hosts the variable Latin subset under the heading variable", () => {
|
|
52
|
+
expect(NEXT_CJK_SOURCE).toContain('path: "../generated/dm-sans.woff2"');
|
|
53
|
+
expect(NEXT_CJK_SOURCE).toContain('variable: "--font-dm-sans"');
|
|
54
|
+
expect(FONT_REGISTRY["dm sans display"]).toBe("--font-dm-sans");
|
|
48
55
|
});
|
|
49
56
|
});
|
package/src/next-cjk.ts
CHANGED
|
@@ -1,70 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nebutra/fonts/next/cjk — the self-hosted
|
|
2
|
+
* @nebutra/fonts/next/cjk — the self-hosted brand faces (server-only).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* would drag those build-time Google downloads into every app — and this repo has
|
|
7
|
-
* a known trap where `next/font/google` fails outright in a network-sandboxed dev
|
|
8
|
-
* server. This file imports `next/font/local` ONLY: the woff2 files live in the
|
|
9
|
-
* workspace `generated/` directory (not the npm tarball), so first-party apps
|
|
10
|
-
* work offline, in CI, and in the sandbox. `./next` re-exports it,
|
|
11
|
-
* so an app already applying `fontRegistryClassName` still only needs one import.
|
|
4
|
+
* The Latin brand face lives here through `next/font/local` so first-party apps work
|
|
5
|
+
* offline, in CI and in the network sandbox (the registry in `./next` is local too):
|
|
12
6
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the OS.
|
|
7
|
+
* - MiSans — the Simplified-Chinese face — is CDN-hosted; see <CjkFontFace /> below.
|
|
8
|
+
* - DM Sans — the Latin display/heading face (SIL OFL). Chosen the same day
|
|
9
|
+
* from the same measurement (DeepSeek, Databricks). Body/UI Latin stays
|
|
10
|
+
* Geist: its tabular figures are what dense dashboard tables need.
|
|
18
11
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
12
|
+
* WHY A SEPARATE ENTRY FROM `./next`: that module declares 19
|
|
13
|
+
* faces for the theme / DESIGN.md registry; importing it for the brand faces
|
|
14
|
+
* would put all of them into every app's CSS. `./next`
|
|
15
|
+
* re-exports this file, so an app already applying `fontRegistryClassName`
|
|
16
|
+
* still needs one import.
|
|
17
|
+
*
|
|
18
|
+
* next/font is a compile-time transform: SWC statically analyses the call, so
|
|
19
|
+
* the options object is spelled out as a literal.
|
|
26
20
|
*/
|
|
27
21
|
|
|
28
22
|
import localFont from "next/font/local";
|
|
29
23
|
|
|
30
24
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* -
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
* MiSans is NOT loaded here: its licence forbids distributing the font on its
|
|
26
|
+
* own and this repository is public (vivo Sans was removed for the same reason,
|
|
27
|
+
* b5e73db35). The subsets are served from the deployment's asset CDN and
|
|
28
|
+
* declared by <CjkFontFace />, re-exported below — render it in the root
|
|
29
|
+
* layout next to this class name. The token stacks reference
|
|
30
|
+
* var(--font-misans, "MiSans"): with no variable set, the literal family name
|
|
31
|
+
* resolves to that @font-face.
|
|
32
|
+
*/
|
|
33
|
+
export { CjkFontFace, misansFontFaceCss } from "./cjk-font-face";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* DM Sans — one variable file (opsz 9–40, wght 100–1000), Latin subset, 67KB.
|
|
37
|
+
* Preloaded: headings render above the fold on most pages.
|
|
41
38
|
*/
|
|
42
|
-
export const
|
|
43
|
-
src: [
|
|
44
|
-
{ path: "../generated/noto-sans-sc-400.woff2", weight: "400", style: "normal" },
|
|
45
|
-
{ path: "../generated/noto-sans-sc-500.woff2", weight: "500", style: "normal" },
|
|
46
|
-
{ path: "../generated/noto-sans-sc-600.woff2", weight: "600", style: "normal" },
|
|
47
|
-
{ path: "../generated/noto-sans-sc-700.woff2", weight: "700", style: "normal" },
|
|
48
|
-
],
|
|
49
|
-
declarations: [
|
|
50
|
-
{
|
|
51
|
-
prop: "unicode-range",
|
|
52
|
-
value: "U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF",
|
|
53
|
-
},
|
|
54
|
-
],
|
|
39
|
+
export const dmSans = localFont({
|
|
40
|
+
src: [{ path: "../generated/dm-sans.woff2", weight: "100 1000", style: "normal" }],
|
|
55
41
|
display: "swap",
|
|
56
|
-
|
|
57
|
-
adjustFontFallback: false,
|
|
58
|
-
variable: "--font-noto-sans-sc",
|
|
42
|
+
variable: "--font-dm-sans",
|
|
59
43
|
});
|
|
60
44
|
|
|
61
45
|
/**
|
|
62
|
-
* Apply to <html> next to the Geist loaders so `--font-
|
|
46
|
+
* Apply to <html> next to the Geist loaders so `--font-dm-sans` exists:
|
|
63
47
|
*
|
|
64
48
|
* className={`${GeistSans.variable} ${GeistMono.variable} ${cjkFontClassName}`}
|
|
65
49
|
*
|
|
66
|
-
* The
|
|
67
|
-
*
|
|
68
|
-
* the numerals and only CJK falls through to this face.
|
|
50
|
+
* The name predates DM Sans; it now carries both brand faces so every app that
|
|
51
|
+
* already applies it picks them up without a layout change.
|
|
69
52
|
*/
|
|
70
|
-
export const
|
|
53
|
+
export const brandFontClassName = dmSans.variable;
|
|
54
|
+
export const cjkFontClassName = brandFontClassName;
|
package/src/next.test.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { FONT_REGISTRY } from "./index";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ./next.ts is a next/font compile-time transform, so it is read as TEXT: the
|
|
8
|
+
* point is what the build will do, and the build must not need the network.
|
|
9
|
+
*/
|
|
10
|
+
const here = (rel: string) => fileURLToPath(new URL(rel, import.meta.url));
|
|
11
|
+
const SOURCE = readFileSync(here("./next.ts"), "utf8");
|
|
12
|
+
const faces = [
|
|
13
|
+
...SOURCE.matchAll(
|
|
14
|
+
/const (\w+) = localFont\(\{\s*src: \[\s*\{\s*path: "([^"]+)",\s*weight: "(\d+ \d+)",\s*style: "normal",\s*\},\s*\],\s*display: "swap",\s*(?:adjustFontFallback: "Times New Roman",\s*)?declarations: \[\{ prop: "font-family", value: "'([^']+)'" \}\],\s*(?:adjustFontFallback: "Times New Roman",\s*)?variable: "(--font-[\w-]+)",/g,
|
|
15
|
+
),
|
|
16
|
+
].map(([, name = "", path = "", weight = "", family = "", variable = ""]) => ({
|
|
17
|
+
name,
|
|
18
|
+
path,
|
|
19
|
+
weight,
|
|
20
|
+
family,
|
|
21
|
+
variable,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe("registry faces (@nebutra/fonts/next)", () => {
|
|
25
|
+
it("never fetches from Google at build or dev time", () => {
|
|
26
|
+
expect(SOURCE).not.toMatch(/from\s+["']next\/font\/google["']/);
|
|
27
|
+
expect(SOURCE).toContain('import localFont from "next/font/local";');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("declares every face the registry list exports", () => {
|
|
31
|
+
const listed = /FONT_REGISTRY_FACES = \[([^\]]+)\]/.exec(SOURCE)?.[1] ?? "";
|
|
32
|
+
const names = listed
|
|
33
|
+
.split(",")
|
|
34
|
+
.map((s) => s.trim())
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
expect(names).toHaveLength(19);
|
|
37
|
+
expect(faces.map((f) => f.name).sort()).toEqual([...names].sort());
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("loads each face from an installed @fontsource-variable package (Latin, upright, wght)", () => {
|
|
41
|
+
for (const { path } of faces) {
|
|
42
|
+
expect(path).toMatch(
|
|
43
|
+
/^\.\.\/node_modules\/@fontsource-variable\/([a-z0-9-]+)\/files\/\1-latin-wght-normal\.woff2$/,
|
|
44
|
+
);
|
|
45
|
+
expect(existsSync(here(path)), path).toBe(true);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("depends on every package it loads from, pinned", () => {
|
|
50
|
+
const pkg = JSON.parse(readFileSync(here("../package.json"), "utf8")) as {
|
|
51
|
+
dependencies: Record<string, string>;
|
|
52
|
+
};
|
|
53
|
+
for (const { path } of faces) {
|
|
54
|
+
const name = /@fontsource-variable\/[a-z0-9-]+/.exec(path)?.[0] ?? "";
|
|
55
|
+
expect(pkg.dependencies[name], name).toMatch(/^\d+\.\d+\.\d+$/);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("keeps the variables FONT_REGISTRY resolves to, and distinct family names", () => {
|
|
60
|
+
const registryVars = new Set(Object.values(FONT_REGISTRY));
|
|
61
|
+
for (const { variable } of faces) expect(registryVars.has(variable), variable).toBe(true);
|
|
62
|
+
const families = faces.map((f) => f.family);
|
|
63
|
+
expect(new Set(families).size).toBe(families.length);
|
|
64
|
+
// The brand heading face in ./next-cjk is family "dmSans"; the registry's
|
|
65
|
+
// must not share it, or one @font-face family serves two files.
|
|
66
|
+
expect(families).not.toContain("dmSans");
|
|
67
|
+
});
|
|
68
|
+
});
|