fontaine 0.2.0 → 0.2.3
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/README.md +20 -0
- package/dist/index.cjs +37 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +38 -22
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -86,11 +86,31 @@ function fontainePlugin(_context, _options) {
|
|
|
86
86
|
},
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
// Gatsby config - gatsby-node.js
|
|
91
|
+
const { FontaineTransform } = require('fontaine')
|
|
92
|
+
|
|
93
|
+
exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
|
|
94
|
+
const config = getConfig()
|
|
95
|
+
config.plugins.push(FontaineTransform.webpack(options))
|
|
96
|
+
actions.replaceWebpackConfig(config)
|
|
97
|
+
}
|
|
89
98
|
```
|
|
90
99
|
|
|
91
100
|
> **Note**
|
|
92
101
|
> If you are using Nuxt, check out [nuxt-font-metrics](https://github.com/danielroe/nuxt-font-metrics) which uses `fontaine` under the hood.
|
|
93
102
|
|
|
103
|
+
If your custom font is used through the mechanism of CSS variables, you'll need to make a tweak to your CSS variables to give fontaine a helping hand. Docusaurus is an example of this, it uses the `--ifm-font-family-base` variable to reference a custom font. In order that fontaine can connect the variable with the font, we need to add a `{Name of Font} override` suffix to that variable. What does this look like? Well imagine we were using the custom font Poppins which is referenced from the `--ifm-font-family-base` variable, we'd make the following adjustment:
|
|
104
|
+
|
|
105
|
+
```diff
|
|
106
|
+
:root {
|
|
107
|
+
/* ... */
|
|
108
|
+
- --ifm-font-family-base: 'Poppins';
|
|
109
|
+
+ --ifm-font-family-base: 'Poppins', 'Poppins override';
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Behind the scenes, there is a 'Poppins override' `@font-face` rule that has been created by fontaine. By manually adding this override font family to our CSS variable, we make our site use the fallback `@font-face` rule with the correct font metrics that fontaine generates.
|
|
113
|
+
|
|
94
114
|
## How it works
|
|
95
115
|
|
|
96
116
|
`fontaine` will scan your `@font-face` rules and generate fallback rules with the correct metrics. For example:
|
package/dist/index.cjs
CHANGED
|
@@ -9,15 +9,22 @@ const ufo = require('ufo');
|
|
|
9
9
|
const scule = require('scule');
|
|
10
10
|
const pathe = require('pathe');
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
function* parseFontFace(css) {
|
|
13
13
|
const fontFamily = css.match(FAMILY_RE)?.groups.fontFamily;
|
|
14
|
-
const src = css.match(SOURCE_RE)?.groups.src;
|
|
15
14
|
const family = withoutQuotes(fontFamily?.split(",")[0] || "");
|
|
16
|
-
const
|
|
17
|
-
src?.split(",")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
for (const match of css.matchAll(SOURCE_RE)) {
|
|
16
|
+
const sources = match.groups.src?.split(",") || [];
|
|
17
|
+
for (const entry of sources) {
|
|
18
|
+
for (const url of entry.matchAll(URL_RE)) {
|
|
19
|
+
const source = withoutQuotes(url.groups?.url || "");
|
|
20
|
+
if (source) {
|
|
21
|
+
yield { family, source };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
yield { family: "", source: "" };
|
|
27
|
+
}
|
|
21
28
|
const generateOverrideName = (name) => {
|
|
22
29
|
const firstFamily = withoutQuotes(name.split(",").shift());
|
|
23
30
|
return `${firstFamily} override`;
|
|
@@ -58,10 +65,12 @@ const FAMILY_RE = magicRegexp.createRegExp(
|
|
|
58
65
|
magicRegexp.exactly("font-family:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("fontFamily"))
|
|
59
66
|
);
|
|
60
67
|
const SOURCE_RE = magicRegexp.createRegExp(
|
|
61
|
-
magicRegexp.exactly("src:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("src"))
|
|
68
|
+
magicRegexp.exactly("src:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("src")),
|
|
69
|
+
["g"]
|
|
62
70
|
);
|
|
63
71
|
const URL_RE = magicRegexp.createRegExp(
|
|
64
|
-
magicRegexp.exactly("url(").and(magicRegexp.charNotIn(")").times.any().as("url")).and(")")
|
|
72
|
+
magicRegexp.exactly("url(").and(magicRegexp.charNotIn(")").times.any().as("url")).and(")"),
|
|
73
|
+
["g"]
|
|
65
74
|
);
|
|
66
75
|
|
|
67
76
|
const metricCache = {};
|
|
@@ -94,6 +103,7 @@ async function readMetrics(_source) {
|
|
|
94
103
|
return metrics;
|
|
95
104
|
}
|
|
96
105
|
|
|
106
|
+
const supportedExtensions = ["woff2", "woff", "ttf"];
|
|
97
107
|
const FontaineTransform = unplugin.createUnplugin(
|
|
98
108
|
(options) => {
|
|
99
109
|
const cssContext = options.css = options.css || {};
|
|
@@ -109,7 +119,7 @@ const FontaineTransform = unplugin.createUnplugin(
|
|
|
109
119
|
enforce: "pre",
|
|
110
120
|
transformInclude(id) {
|
|
111
121
|
const { pathname } = ufo.parseURL(id);
|
|
112
|
-
return
|
|
122
|
+
return CSS_RE.test(pathname) || CSS_RE.test(id);
|
|
113
123
|
},
|
|
114
124
|
async transform(code, id) {
|
|
115
125
|
const s = new MagicString(code);
|
|
@@ -119,17 +129,20 @@ const FontaineTransform = unplugin.createUnplugin(
|
|
|
119
129
|
if (match.index === void 0 || !matchContent)
|
|
120
130
|
continue;
|
|
121
131
|
faceRanges.push([match.index, match.index + matchContent.length]);
|
|
122
|
-
const { family, source }
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
for (const { family, source } of parseFontFace(matchContent)) {
|
|
133
|
+
if (!family)
|
|
134
|
+
continue;
|
|
135
|
+
if (!supportedExtensions.some((e) => source?.endsWith(e)))
|
|
136
|
+
continue;
|
|
137
|
+
const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
|
|
138
|
+
if (metrics) {
|
|
139
|
+
const fontFace = generateFontFace(metrics, {
|
|
140
|
+
name: overrideName(family),
|
|
141
|
+
fallbacks: options.fallbacks
|
|
142
|
+
});
|
|
143
|
+
cssContext.value += fontFace;
|
|
144
|
+
s.appendLeft(match.index, fontFace);
|
|
145
|
+
}
|
|
133
146
|
}
|
|
134
147
|
}
|
|
135
148
|
for (const match of code.matchAll(FONT_FAMILY_RE)) {
|
|
@@ -169,6 +182,9 @@ const FONT_FAMILY_RE = magicRegexp.createRegExp(
|
|
|
169
182
|
magicRegexp.charNotIn(";}").times.any().as("family").after(magicRegexp.exactly("font-family:").and(magicRegexp.whitespace.times.any())).before(magicRegexp.charIn(";}").or(magicRegexp.exactly("").at.lineEnd())),
|
|
170
183
|
["g"]
|
|
171
184
|
);
|
|
185
|
+
const CSS_RE = magicRegexp.createRegExp(
|
|
186
|
+
magicRegexp.exactly(".").and(magicRegexp.anyOf("sass", "css", "scss")).at.lineEnd()
|
|
187
|
+
);
|
|
172
188
|
|
|
173
189
|
exports.FontaineTransform = FontaineTransform;
|
|
174
190
|
exports.generateFontFace = generateFontFace;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ interface FontaineTransformOptions {
|
|
|
11
11
|
overrideName?: (name: string) => string;
|
|
12
12
|
sourcemap?: boolean;
|
|
13
13
|
}
|
|
14
|
-
declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions>;
|
|
14
|
+
declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions, false>;
|
|
15
15
|
|
|
16
16
|
declare const generateOverrideName: (name: string) => string;
|
|
17
17
|
interface GenerateOptions {
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createUnplugin } from 'unplugin';
|
|
2
|
-
import { createRegExp, charIn, exactly, whitespace, charNotIn } from 'magic-regexp';
|
|
2
|
+
import { createRegExp, charIn, exactly, whitespace, charNotIn, anyOf } from 'magic-regexp';
|
|
3
3
|
import MagicString from 'magic-string';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { fromFile, fromUrl } from '@capsizecss/unpack';
|
|
@@ -7,15 +7,22 @@ import { parseURL } from 'ufo';
|
|
|
7
7
|
import { camelCase } from 'scule';
|
|
8
8
|
import { isAbsolute, join } from 'pathe';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
function* parseFontFace(css) {
|
|
11
11
|
const fontFamily = css.match(FAMILY_RE)?.groups.fontFamily;
|
|
12
|
-
const src = css.match(SOURCE_RE)?.groups.src;
|
|
13
12
|
const family = withoutQuotes(fontFamily?.split(",")[0] || "");
|
|
14
|
-
const
|
|
15
|
-
src?.split(",")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
for (const match of css.matchAll(SOURCE_RE)) {
|
|
14
|
+
const sources = match.groups.src?.split(",") || [];
|
|
15
|
+
for (const entry of sources) {
|
|
16
|
+
for (const url of entry.matchAll(URL_RE)) {
|
|
17
|
+
const source = withoutQuotes(url.groups?.url || "");
|
|
18
|
+
if (source) {
|
|
19
|
+
yield { family, source };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
yield { family: "", source: "" };
|
|
25
|
+
}
|
|
19
26
|
const generateOverrideName = (name) => {
|
|
20
27
|
const firstFamily = withoutQuotes(name.split(",").shift());
|
|
21
28
|
return `${firstFamily} override`;
|
|
@@ -56,10 +63,12 @@ const FAMILY_RE = createRegExp(
|
|
|
56
63
|
exactly("font-family:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("fontFamily"))
|
|
57
64
|
);
|
|
58
65
|
const SOURCE_RE = createRegExp(
|
|
59
|
-
exactly("src:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("src"))
|
|
66
|
+
exactly("src:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("src")),
|
|
67
|
+
["g"]
|
|
60
68
|
);
|
|
61
69
|
const URL_RE = createRegExp(
|
|
62
|
-
exactly("url(").and(charNotIn(")").times.any().as("url")).and(")")
|
|
70
|
+
exactly("url(").and(charNotIn(")").times.any().as("url")).and(")"),
|
|
71
|
+
["g"]
|
|
63
72
|
);
|
|
64
73
|
|
|
65
74
|
const metricCache = {};
|
|
@@ -92,6 +101,7 @@ async function readMetrics(_source) {
|
|
|
92
101
|
return metrics;
|
|
93
102
|
}
|
|
94
103
|
|
|
104
|
+
const supportedExtensions = ["woff2", "woff", "ttf"];
|
|
95
105
|
const FontaineTransform = createUnplugin(
|
|
96
106
|
(options) => {
|
|
97
107
|
const cssContext = options.css = options.css || {};
|
|
@@ -107,7 +117,7 @@ const FontaineTransform = createUnplugin(
|
|
|
107
117
|
enforce: "pre",
|
|
108
118
|
transformInclude(id) {
|
|
109
119
|
const { pathname } = parseURL(id);
|
|
110
|
-
return
|
|
120
|
+
return CSS_RE.test(pathname) || CSS_RE.test(id);
|
|
111
121
|
},
|
|
112
122
|
async transform(code, id) {
|
|
113
123
|
const s = new MagicString(code);
|
|
@@ -117,17 +127,20 @@ const FontaineTransform = createUnplugin(
|
|
|
117
127
|
if (match.index === void 0 || !matchContent)
|
|
118
128
|
continue;
|
|
119
129
|
faceRanges.push([match.index, match.index + matchContent.length]);
|
|
120
|
-
const { family, source }
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
for (const { family, source } of parseFontFace(matchContent)) {
|
|
131
|
+
if (!family)
|
|
132
|
+
continue;
|
|
133
|
+
if (!supportedExtensions.some((e) => source?.endsWith(e)))
|
|
134
|
+
continue;
|
|
135
|
+
const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
|
|
136
|
+
if (metrics) {
|
|
137
|
+
const fontFace = generateFontFace(metrics, {
|
|
138
|
+
name: overrideName(family),
|
|
139
|
+
fallbacks: options.fallbacks
|
|
140
|
+
});
|
|
141
|
+
cssContext.value += fontFace;
|
|
142
|
+
s.appendLeft(match.index, fontFace);
|
|
143
|
+
}
|
|
131
144
|
}
|
|
132
145
|
}
|
|
133
146
|
for (const match of code.matchAll(FONT_FAMILY_RE)) {
|
|
@@ -167,5 +180,8 @@ const FONT_FAMILY_RE = createRegExp(
|
|
|
167
180
|
charNotIn(";}").times.any().as("family").after(exactly("font-family:").and(whitespace.times.any())).before(charIn(";}").or(exactly("").at.lineEnd())),
|
|
168
181
|
["g"]
|
|
169
182
|
);
|
|
183
|
+
const CSS_RE = createRegExp(
|
|
184
|
+
exactly(".").and(anyOf("sass", "css", "scss")).at.lineEnd()
|
|
185
|
+
);
|
|
170
186
|
|
|
171
187
|
export { FontaineTransform, generateFontFace, generateOverrideName, getMetricsForFamily, readMetrics };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontaine",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Automatic font fallback based on font metrics",
|
|
5
5
|
"repository": "unjs/fontaine",
|
|
6
6
|
"keywords": [
|
|
@@ -48,23 +48,23 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@capsizecss/metrics": "^0.3.0",
|
|
50
50
|
"@capsizecss/unpack": "^0.1.0",
|
|
51
|
-
"magic-regexp": "^0.
|
|
51
|
+
"magic-regexp": "^0.6.0",
|
|
52
52
|
"magic-string": "^0.26.7",
|
|
53
53
|
"pathe": "^0.3.9",
|
|
54
54
|
"scule": "^0.3.2",
|
|
55
55
|
"ufo": "^0.8.6",
|
|
56
|
-
"unplugin": "^0.
|
|
56
|
+
"unplugin": "^0.10.2"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@nuxtjs/eslint-config-typescript": "latest",
|
|
60
60
|
"@release-it/conventional-changelog": "latest",
|
|
61
|
-
"@types/node": "
|
|
61
|
+
"@types/node": "^18.11.8",
|
|
62
62
|
"@types/serve-handler": "^6.1.1",
|
|
63
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
64
|
-
"@typescript-eslint/parser": "^5.
|
|
65
|
-
"@vitest/coverage-c8": "^0.24.
|
|
63
|
+
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
|
64
|
+
"@typescript-eslint/parser": "^5.41.0",
|
|
65
|
+
"@vitest/coverage-c8": "^0.24.4",
|
|
66
66
|
"conventional-changelog-conventionalcommits": "latest",
|
|
67
|
-
"eslint": "
|
|
67
|
+
"eslint": "^8.26.0",
|
|
68
68
|
"eslint-config-prettier": "latest",
|
|
69
69
|
"eslint-plugin-prettier": "latest",
|
|
70
70
|
"execa": "^6.1.0",
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
"serve-handler": "^6.1.3",
|
|
78
78
|
"typescript": "latest",
|
|
79
79
|
"unbuild": "latest",
|
|
80
|
-
"vite": "
|
|
81
|
-
"vitest": "
|
|
80
|
+
"vite": "^3.2.2",
|
|
81
|
+
"vitest": "^0.24.4"
|
|
82
82
|
},
|
|
83
83
|
"resolutions": {
|
|
84
84
|
"fontaine": "link:."
|
|
85
85
|
},
|
|
86
|
-
"packageManager": "pnpm@7.
|
|
86
|
+
"packageManager": "pnpm@7.14.1"
|
|
87
87
|
}
|