fontaine 0.5.0 → 0.6.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/README.md CHANGED
@@ -187,7 +187,7 @@ Published under [MIT License](./LICENCE).
187
187
  [npm-version-href]: https://npmjs.com/package/fontaine
188
188
  [npm-downloads-src]: https://img.shields.io/npm/dm/fontaine?style=flat-square
189
189
  [npm-downloads-href]: https://npmjs.com/package/fontaine
190
- [github-actions-src]: https://img.shields.io/github/workflow/status/unjs/fontaine/ci/main?style=flat-square
191
- [github-actions-href]: https://github.com/unjs/fontaine/actions?query=workflow%3Aci
190
+ [github-actions-src]: https://img.shields.io/github/actions/workflow/status/unjs/fontaine/ci.yml?branch=main&style=flat-square
191
+ [github-actions-href]: https://github.com/unjs/fontaine/actions/workflows/ci.yml
192
192
  [codecov-src]: https://img.shields.io/codecov/c/gh/unjs/fontaine/main?style=flat-square
193
193
  [codecov-href]: https://codecov.io/gh/unjs/fontaine
package/dist/index.cjs CHANGED
@@ -1,13 +1,14 @@
1
1
  'use strict';
2
2
 
3
- const unplugin = require('unplugin');
3
+ const cssTree = require('css-tree');
4
4
  const magicRegexp = require('magic-regexp');
5
- const MagicString = require('magic-string');
6
- const ufo = require('ufo');
7
- const pathe = require('pathe');
8
5
  const node_url = require('node:url');
9
- const unpack = require('@capsizecss/unpack');
10
6
  const metrics = require('@capsizecss/metrics');
7
+ const unpack = require('@capsizecss/unpack');
8
+ const ufo = require('ufo');
9
+ const MagicString = require('magic-string');
10
+ const pathe = require('pathe');
11
+ const unplugin = require('unplugin');
11
12
 
12
13
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
13
14
 
@@ -24,44 +25,91 @@ const QUOTES_RE = magicRegexp.createRegExp(
24
25
  magicRegexp.charIn(`"'`).at.lineStart().or(magicRegexp.charIn(`"'`).at.lineEnd()),
25
26
  ["g"]
26
27
  );
27
- const FAMILY_RE = magicRegexp.createRegExp(
28
- magicRegexp.exactly("font-family:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("fontFamily"))
29
- );
30
- const SOURCE_RE = magicRegexp.createRegExp(
31
- magicRegexp.exactly("src:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("src")),
32
- ["g"]
33
- );
34
- const URL_RE = magicRegexp.createRegExp(
35
- magicRegexp.exactly("url(").and(magicRegexp.charNotIn(")").times.any().as("url")).and(")"),
36
- ["g"]
37
- );
38
28
  const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
39
- function* parseFontFace(css) {
40
- const fontFamily = css.match(FAMILY_RE)?.groups.fontFamily;
41
- const family = withoutQuotes(fontFamily?.split(",")[0] || "");
42
- for (const match of css.matchAll(SOURCE_RE)) {
43
- const sources = match.groups.src?.split(",");
44
- for (const entry of sources || []) {
45
- for (const url of entry.matchAll(URL_RE)) {
46
- const source = withoutQuotes(url.groups?.url || "");
47
- if (source)
48
- yield { family, source };
29
+ const genericCSSFamilies = /* @__PURE__ */ new Set([
30
+ "serif",
31
+ "sans-serif",
32
+ "monospace",
33
+ "cursive",
34
+ "fantasy",
35
+ "system-ui",
36
+ "ui-serif",
37
+ "ui-sans-serif",
38
+ "ui-monospace",
39
+ "ui-rounded",
40
+ "emoji",
41
+ "math",
42
+ "fangsong"
43
+ ]);
44
+ const fontProperties = /* @__PURE__ */ new Set(["font-weight", "font-style", "font-stretch"]);
45
+ function parseFontFace(css) {
46
+ const families = [];
47
+ const ast = typeof css === "string" ? cssTree.parse(css, { positions: true }) : css;
48
+ cssTree.walk(ast, {
49
+ visit: "Atrule",
50
+ enter(node) {
51
+ if (node.name !== "font-face")
52
+ return;
53
+ let family;
54
+ const sources = [];
55
+ const properties = {};
56
+ if (node.block) {
57
+ cssTree.walk(node.block, {
58
+ visit: "Declaration",
59
+ enter(declaration) {
60
+ if (declaration.property === "font-family" && declaration.value.type === "Value") {
61
+ for (const child of declaration.value.children) {
62
+ if (child.type === "String") {
63
+ family = withoutQuotes(child.value);
64
+ break;
65
+ }
66
+ if (child.type === "Identifier" && !genericCSSFamilies.has(child.name)) {
67
+ family = child.name;
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ if (fontProperties.has(declaration.property)) {
73
+ if (declaration.value.type === "Value") {
74
+ for (const child of declaration.value.children) {
75
+ const hasValue = !!properties[declaration.property];
76
+ properties[declaration.property] ||= "";
77
+ properties[declaration.property] += (hasValue ? " " : "") + cssTree.generate(child);
78
+ }
79
+ }
80
+ }
81
+ if (declaration.property === "src") {
82
+ cssTree.walk(declaration.value, {
83
+ visit: "Url",
84
+ enter(urlNode) {
85
+ const source = withoutQuotes(urlNode.value);
86
+ if (source) {
87
+ sources.push(source);
88
+ }
89
+ }
90
+ });
91
+ }
92
+ }
93
+ });
94
+ }
95
+ if (family) {
96
+ for (const source of sources) {
97
+ families.push({ index: node.loc.start.offset, family, source, properties });
98
+ }
99
+ if (!sources.length) {
100
+ families.push({ index: node.loc.start.offset, family, properties });
101
+ }
49
102
  }
50
103
  }
51
- }
52
- yield { family: "", source: "" };
104
+ });
105
+ return families;
53
106
  }
54
107
  function generateFallbackName(name) {
55
108
  const firstFamily = withoutQuotes(name.split(",").shift());
56
109
  return `${firstFamily} fallback`;
57
110
  }
58
111
  function generateFontFace(metrics, fallback) {
59
- const {
60
- name: fallbackName,
61
- font: fallbackFontName,
62
- metrics: fallbackMetrics,
63
- ...properties
64
- } = fallback;
112
+ const { name: fallbackName, font: fallbackFontName, metrics: fallbackMetrics, ...properties } = fallback;
65
113
  const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
66
114
  const fallbackFontXAvgRatio = fallbackMetrics ? fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm : 1;
67
115
  const sizeAdjust = fallbackMetrics && preferredFontXAvgRatio && fallbackFontXAvgRatio ? preferredFontXAvgRatio / fallbackFontXAvgRatio : 1;
@@ -85,13 +133,7 @@ ${toCSS(declaration)}
85
133
  }
86
134
 
87
135
  const metricCache = {};
88
- function filterRequiredMetrics({
89
- ascent,
90
- descent,
91
- lineGap,
92
- unitsPerEm,
93
- xWidthAvg
94
- }) {
136
+ function filterRequiredMetrics({ ascent, descent, lineGap, unitsPerEm, xWidthAvg }) {
95
137
  return {
96
138
  ascent,
97
139
  descent,
@@ -108,6 +150,10 @@ async function getMetricsForFamily(family) {
108
150
  const name = metrics.fontFamilyToCamelCase(family);
109
151
  const { entireMetricsCollection } = await import('@capsizecss/metrics/entireMetricsCollection');
110
152
  const metrics$1 = entireMetricsCollection[name];
153
+ if (!("descent" in metrics$1)) {
154
+ metricCache[family] = null;
155
+ return null;
156
+ }
111
157
  const filteredMetrics = filterRequiredMetrics(metrics$1);
112
158
  metricCache[family] = filteredMetrics;
113
159
  return filteredMetrics;
@@ -116,111 +162,114 @@ async function getMetricsForFamily(family) {
116
162
  return null;
117
163
  }
118
164
  }
165
+ const urlRequestCache = /* @__PURE__ */ new Map();
119
166
  async function readMetrics(_source) {
120
167
  const source = typeof _source !== "string" && "href" in _source ? _source.href : _source;
121
168
  if (source in metricCache)
122
- return Promise.resolve(metricCache[source]);
169
+ return metricCache[source];
123
170
  const { protocol } = ufo.parseURL(source);
124
171
  if (!protocol)
125
172
  return null;
126
- const metrics = protocol === "file:" ? await unpack.fromFile(node_url.fileURLToPath(source)) : await unpack.fromUrl(source);
173
+ let metrics;
174
+ if (protocol === "file:") {
175
+ metrics = await unpack.fromFile(node_url.fileURLToPath(source));
176
+ } else {
177
+ if (urlRequestCache.has(source)) {
178
+ metrics = await urlRequestCache.get(source);
179
+ } else {
180
+ const requestPromise = unpack.fromUrl(source);
181
+ urlRequestCache.set(source, requestPromise);
182
+ metrics = await requestPromise;
183
+ }
184
+ }
127
185
  const filteredMetrics = filterRequiredMetrics(metrics);
128
186
  metricCache[source] = filteredMetrics;
129
187
  return filteredMetrics;
130
188
  }
131
189
 
132
190
  const supportedExtensions = ["woff2", "woff", "ttf"];
133
- const FONT_FACE_RE = magicRegexp.createRegExp(
134
- magicRegexp.exactly("@font-face").and(magicRegexp.whitespace.times.any()).and("{").and(magicRegexp.charNotIn("}").times.any()).and("}"),
135
- ["g"]
136
- );
137
- const FONT_FAMILY_RE = magicRegexp.createRegExp(
138
- magicRegexp.charNotIn(";}").times.any().as("family").after(magicRegexp.exactly("font-family:").and(magicRegexp.whitespace.times.any())).before(magicRegexp.charIn(";}").or(magicRegexp.exactly("").at.lineEnd())),
139
- ["g"]
140
- );
141
191
  const CSS_RE = magicRegexp.createRegExp(
142
192
  magicRegexp.exactly(".").and(magicRegexp.anyOf("sass", "css", "scss")).at.lineEnd()
143
193
  );
144
- const FontaineTransform = unplugin.createUnplugin(
145
- (options) => {
146
- const cssContext = options.css = options.css || {};
147
- cssContext.value = "";
148
- const resolvePath = options.resolvePath || ((id) => id);
149
- const fallbackName = options.fallbackName || options.overrideName || generateFallbackName;
150
- const skipFontFaceGeneration = options.skipFontFaceGeneration || (() => false);
151
- function readMetricsFromId(path, importer) {
152
- const resolvedPath = pathe.isAbsolute(importer) && path.startsWith(".") ? pathe.join(importer, path) : resolvePath(path);
153
- return readMetrics(resolvedPath);
154
- }
155
- return {
156
- name: "fontaine-transform",
157
- enforce: "pre",
158
- transformInclude(id) {
159
- const { pathname } = ufo.parseURL(id);
160
- return CSS_RE.test(pathname) || CSS_RE.test(id);
161
- },
162
- async transform(code, id) {
163
- const s = new MagicString__default(code);
164
- const faceRanges = [];
165
- for (const match of code.matchAll(FONT_FACE_RE)) {
166
- const matchContent = match[0];
167
- if (match.index === void 0 || !matchContent)
194
+ const RELATIVE_RE = magicRegexp.createRegExp(
195
+ magicRegexp.exactly(".").or("..").and(magicRegexp.anyOf("/", "\\")).at.lineStart()
196
+ );
197
+ const FontaineTransform = unplugin.createUnplugin((options) => {
198
+ const cssContext = options.css = options.css || {};
199
+ cssContext.value = "";
200
+ const resolvePath = options.resolvePath || ((id) => id);
201
+ const fallbackName = options.fallbackName || options.overrideName || generateFallbackName;
202
+ const skipFontFaceGeneration = options.skipFontFaceGeneration || (() => false);
203
+ function readMetricsFromId(path, importer) {
204
+ const resolvedPath = pathe.isAbsolute(importer) && RELATIVE_RE.test(path) ? new URL(path, node_url.pathToFileURL(importer)) : resolvePath(path);
205
+ return readMetrics(resolvedPath);
206
+ }
207
+ return {
208
+ name: "fontaine-transform",
209
+ enforce: "pre",
210
+ transformInclude(id) {
211
+ const { pathname } = ufo.parseURL(id);
212
+ return CSS_RE.test(pathname) || CSS_RE.test(id);
213
+ },
214
+ async transform(code, id) {
215
+ const s = new MagicString__default(code);
216
+ const ast = cssTree.parse(code, { positions: true });
217
+ for (const { family, source, index, properties } of parseFontFace(ast)) {
218
+ if (!supportedExtensions.some((e) => source?.endsWith(e)))
219
+ continue;
220
+ if (skipFontFaceGeneration(fallbackName(family)))
221
+ continue;
222
+ const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
223
+ if (!metrics)
224
+ continue;
225
+ for (let i = options.fallbacks.length - 1; i >= 0; i--) {
226
+ const fallback = options.fallbacks[i];
227
+ const fallbackMetrics = await getMetricsForFamily(fallback);
228
+ if (!fallbackMetrics)
168
229
  continue;
169
- faceRanges.push([match.index, match.index + matchContent.length]);
170
- for (const { family, source } of parseFontFace(matchContent)) {
230
+ const fontFace = generateFontFace(metrics, {
231
+ name: fallbackName(family),
232
+ font: fallback,
233
+ metrics: fallbackMetrics,
234
+ ...properties
235
+ });
236
+ cssContext.value += fontFace;
237
+ s.appendLeft(index, fontFace);
238
+ }
239
+ }
240
+ cssTree.walk(ast, {
241
+ visit: "Declaration",
242
+ enter(node) {
243
+ if (node.property !== "font-family")
244
+ return;
245
+ if (this.atrule && this.atrule.name === "font-face")
246
+ return;
247
+ if (node.value.type !== "Value")
248
+ return;
249
+ for (const child of node.value.children) {
250
+ let family;
251
+ if (child.type === "String") {
252
+ family = withoutQuotes(child.value);
253
+ } else if (child.type === "Identifier" && child.name !== "inherit") {
254
+ family = child.name;
255
+ }
171
256
  if (!family)
172
257
  continue;
173
- if (!supportedExtensions.some((e) => source?.endsWith(e)))
174
- continue;
175
- if (skipFontFaceGeneration(fallbackName(family)))
176
- continue;
177
- const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
178
- if (!metrics)
179
- continue;
180
- for (let i = options.fallbacks.length - 1; i >= 0; i--) {
181
- const fallback = options.fallbacks[i];
182
- const fallbackMetrics = await getMetricsForFamily(fallback);
183
- if (!fallbackMetrics)
184
- continue;
185
- const fontFace = generateFontFace(metrics, {
186
- name: fallbackName(family),
187
- font: fallback,
188
- metrics: fallbackMetrics
189
- });
190
- cssContext.value += fontFace;
191
- s.appendLeft(match.index, fontFace);
192
- }
258
+ s.appendRight(child.loc.end.offset, `, "${fallbackName(family)}"`);
259
+ return;
193
260
  }
194
261
  }
195
- for (const match of code.matchAll(FONT_FAMILY_RE)) {
196
- const { index, 0: matchContent } = match;
197
- if (index === void 0 || !matchContent)
198
- continue;
199
- if (faceRanges.some(([start, end]) => index > start && index < end))
200
- continue;
201
- const families = matchContent.split(",").map((f) => f.trim()).filter((f) => !f.startsWith("var("));
202
- if (!families.length || families[0] === "inherit")
203
- continue;
204
- s.overwrite(
205
- index,
206
- index + matchContent.length,
207
- ` ${[
208
- families[0],
209
- `"${generateFallbackName(families[0])}"`,
210
- ...families.slice(1)
211
- ].join(", ")}`
212
- );
213
- }
214
- if (s.hasChanged()) {
215
- return {
216
- code: s.toString(),
217
- map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
218
- };
219
- }
262
+ });
263
+ if (s.hasChanged()) {
264
+ return {
265
+ code: s.toString(),
266
+ /* v8 ignore next 3 */
267
+ map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
268
+ };
220
269
  }
221
- };
222
- }
223
- );
270
+ }
271
+ };
272
+ });
224
273
 
225
274
  exports.FontaineTransform = FontaineTransform;
226
275
  exports.generateFallbackName = generateFallbackName;
package/dist/index.d.cts CHANGED
@@ -1,32 +1,104 @@
1
- import * as unplugin from 'unplugin';
2
1
  import { Font } from '@capsizecss/unpack';
2
+ import * as unplugin from 'unplugin';
3
+
4
+ /**
5
+ * Generates a fallback name based on the first font family specified in the input string.
6
+ * @param {string} name - The full font family string.
7
+ * @returns {string} - The fallback font name.
8
+ */
9
+ declare function generateFallbackName(name: string): string;
10
+ interface FallbackOptions {
11
+ /**
12
+ * The name of the fallback font.
13
+ */
14
+ name: string;
15
+ /**
16
+ * The fallback font family name.
17
+ */
18
+ font: string;
19
+ /**
20
+ * Metrics for fallback face calculations.
21
+ * @optional
22
+ */
23
+ metrics?: FontFaceMetrics;
24
+ /**
25
+ * Additional properties that may be included dynamically
26
+ */
27
+ [key: string]: any;
28
+ }
29
+ type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
30
+ /**
31
+ * Generates a CSS `@font-face' declaration for a font, taking fallback and resizing into account.
32
+ * @param {FontFaceMetrics} metrics - The metrics of the preferred font. See {@link FontFaceMetrics}.
33
+ * @param {FallbackOptions} fallback - The fallback options, including name, font and optional metrics. See {@link FallbackOptions}.
34
+ * @returns {string} - The full `@font-face` CSS declaration.
35
+ */
36
+ declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
37
+
38
+ /**
39
+ * Retrieves the font metrics for a given font family from the metrics collection. Uses caching to avoid redundant calculations.
40
+ * @param {string} family - The name of the font family for which metrics are requested.
41
+ * @returns {Promise<FontFaceMetrics | null>} - A promise that resolves with the filtered font metrics or null if not found. See {@link FontFaceMetrics}.
42
+ * @async
43
+ */
44
+ declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
45
+ /**
46
+ * Reads font metrics from a specified source URL or file path. This function supports both local files and remote URLs.
47
+ * It caches the results to optimise subsequent requests for the same source.
48
+ * @param {URL | string} _source - The source URL or local file path from which to read the font metrics.
49
+ * @returns {Promise<FontFaceMetrics | null>} - A promise that resolves to the filtered font metrics or null if the source cannot be processed.
50
+ * @async
51
+ */
52
+ declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
3
53
 
4
54
  interface FontaineTransformOptions {
55
+ /**
56
+ * Configuration options for the CSS transformation.
57
+ * @optional
58
+ */
5
59
  css?: {
60
+ /**
61
+ * Holds the current value of the CSS being transformed.
62
+ * @optional
63
+ */
6
64
  value?: string;
7
65
  };
66
+ /**
67
+ * An array of fallback font family names to use.
68
+ */
8
69
  fallbacks: string[];
70
+ /**
71
+ * Function to resolve a given path to a valid URL or local path.
72
+ * This is typically used to resolve font file paths.
73
+ * @optional
74
+ */
9
75
  resolvePath?: (path: string) => string | URL;
76
+ /**
77
+ * A function to determine whether to skip font face generation for a given fallback name.
78
+ * @optional
79
+ */
10
80
  skipFontFaceGeneration?: (fallbackName: string) => boolean;
11
- /** this should produce an unquoted font family name */
81
+ /**
82
+ * Function to generate an unquoted font family name to use as a fallback.
83
+ * This should return a valid CSS font family name and should not include quotes.
84
+ * @optional
85
+ */
12
86
  fallbackName?: (name: string) => string;
13
87
  /** @deprecated use fallbackName */
14
88
  overrideName?: (name: string) => string;
89
+ /**
90
+ * Specifies whether to create a source map for the transformation.
91
+ * @optional
92
+ */
15
93
  sourcemap?: boolean;
16
94
  }
95
+ /**
96
+ * Transforms CSS files to include font fallbacks.
97
+ *
98
+ * @param options - The transformation options. See {@link FontaineTransformOptions}.
99
+ * @returns The unplugin instance.
100
+ */
17
101
  declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions, boolean>;
18
102
 
19
- declare function generateFallbackName(name: string): string;
20
- interface FallbackOptions {
21
- name: string;
22
- font: string;
23
- metrics?: FontFaceMetrics;
24
- [key: string]: any;
25
- }
26
- type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
27
- declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
28
-
29
- declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
30
- declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
31
-
32
- export { FontaineTransform, type FontaineTransformOptions, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
103
+ export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
104
+ export type { FontaineTransformOptions };
package/dist/index.d.mts CHANGED
@@ -1,32 +1,104 @@
1
- import * as unplugin from 'unplugin';
2
1
  import { Font } from '@capsizecss/unpack';
2
+ import * as unplugin from 'unplugin';
3
+
4
+ /**
5
+ * Generates a fallback name based on the first font family specified in the input string.
6
+ * @param {string} name - The full font family string.
7
+ * @returns {string} - The fallback font name.
8
+ */
9
+ declare function generateFallbackName(name: string): string;
10
+ interface FallbackOptions {
11
+ /**
12
+ * The name of the fallback font.
13
+ */
14
+ name: string;
15
+ /**
16
+ * The fallback font family name.
17
+ */
18
+ font: string;
19
+ /**
20
+ * Metrics for fallback face calculations.
21
+ * @optional
22
+ */
23
+ metrics?: FontFaceMetrics;
24
+ /**
25
+ * Additional properties that may be included dynamically
26
+ */
27
+ [key: string]: any;
28
+ }
29
+ type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
30
+ /**
31
+ * Generates a CSS `@font-face' declaration for a font, taking fallback and resizing into account.
32
+ * @param {FontFaceMetrics} metrics - The metrics of the preferred font. See {@link FontFaceMetrics}.
33
+ * @param {FallbackOptions} fallback - The fallback options, including name, font and optional metrics. See {@link FallbackOptions}.
34
+ * @returns {string} - The full `@font-face` CSS declaration.
35
+ */
36
+ declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
37
+
38
+ /**
39
+ * Retrieves the font metrics for a given font family from the metrics collection. Uses caching to avoid redundant calculations.
40
+ * @param {string} family - The name of the font family for which metrics are requested.
41
+ * @returns {Promise<FontFaceMetrics | null>} - A promise that resolves with the filtered font metrics or null if not found. See {@link FontFaceMetrics}.
42
+ * @async
43
+ */
44
+ declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
45
+ /**
46
+ * Reads font metrics from a specified source URL or file path. This function supports both local files and remote URLs.
47
+ * It caches the results to optimise subsequent requests for the same source.
48
+ * @param {URL | string} _source - The source URL or local file path from which to read the font metrics.
49
+ * @returns {Promise<FontFaceMetrics | null>} - A promise that resolves to the filtered font metrics or null if the source cannot be processed.
50
+ * @async
51
+ */
52
+ declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
3
53
 
4
54
  interface FontaineTransformOptions {
55
+ /**
56
+ * Configuration options for the CSS transformation.
57
+ * @optional
58
+ */
5
59
  css?: {
60
+ /**
61
+ * Holds the current value of the CSS being transformed.
62
+ * @optional
63
+ */
6
64
  value?: string;
7
65
  };
66
+ /**
67
+ * An array of fallback font family names to use.
68
+ */
8
69
  fallbacks: string[];
70
+ /**
71
+ * Function to resolve a given path to a valid URL or local path.
72
+ * This is typically used to resolve font file paths.
73
+ * @optional
74
+ */
9
75
  resolvePath?: (path: string) => string | URL;
76
+ /**
77
+ * A function to determine whether to skip font face generation for a given fallback name.
78
+ * @optional
79
+ */
10
80
  skipFontFaceGeneration?: (fallbackName: string) => boolean;
11
- /** this should produce an unquoted font family name */
81
+ /**
82
+ * Function to generate an unquoted font family name to use as a fallback.
83
+ * This should return a valid CSS font family name and should not include quotes.
84
+ * @optional
85
+ */
12
86
  fallbackName?: (name: string) => string;
13
87
  /** @deprecated use fallbackName */
14
88
  overrideName?: (name: string) => string;
89
+ /**
90
+ * Specifies whether to create a source map for the transformation.
91
+ * @optional
92
+ */
15
93
  sourcemap?: boolean;
16
94
  }
95
+ /**
96
+ * Transforms CSS files to include font fallbacks.
97
+ *
98
+ * @param options - The transformation options. See {@link FontaineTransformOptions}.
99
+ * @returns The unplugin instance.
100
+ */
17
101
  declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions, boolean>;
18
102
 
19
- declare function generateFallbackName(name: string): string;
20
- interface FallbackOptions {
21
- name: string;
22
- font: string;
23
- metrics?: FontFaceMetrics;
24
- [key: string]: any;
25
- }
26
- type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
27
- declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
28
-
29
- declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
30
- declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
31
-
32
- export { FontaineTransform, type FontaineTransformOptions, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
103
+ export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
104
+ export type { FontaineTransformOptions };
package/dist/index.d.ts CHANGED
@@ -1,32 +1,104 @@
1
- import * as unplugin from 'unplugin';
2
1
  import { Font } from '@capsizecss/unpack';
2
+ import * as unplugin from 'unplugin';
3
+
4
+ /**
5
+ * Generates a fallback name based on the first font family specified in the input string.
6
+ * @param {string} name - The full font family string.
7
+ * @returns {string} - The fallback font name.
8
+ */
9
+ declare function generateFallbackName(name: string): string;
10
+ interface FallbackOptions {
11
+ /**
12
+ * The name of the fallback font.
13
+ */
14
+ name: string;
15
+ /**
16
+ * The fallback font family name.
17
+ */
18
+ font: string;
19
+ /**
20
+ * Metrics for fallback face calculations.
21
+ * @optional
22
+ */
23
+ metrics?: FontFaceMetrics;
24
+ /**
25
+ * Additional properties that may be included dynamically
26
+ */
27
+ [key: string]: any;
28
+ }
29
+ type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
30
+ /**
31
+ * Generates a CSS `@font-face' declaration for a font, taking fallback and resizing into account.
32
+ * @param {FontFaceMetrics} metrics - The metrics of the preferred font. See {@link FontFaceMetrics}.
33
+ * @param {FallbackOptions} fallback - The fallback options, including name, font and optional metrics. See {@link FallbackOptions}.
34
+ * @returns {string} - The full `@font-face` CSS declaration.
35
+ */
36
+ declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
37
+
38
+ /**
39
+ * Retrieves the font metrics for a given font family from the metrics collection. Uses caching to avoid redundant calculations.
40
+ * @param {string} family - The name of the font family for which metrics are requested.
41
+ * @returns {Promise<FontFaceMetrics | null>} - A promise that resolves with the filtered font metrics or null if not found. See {@link FontFaceMetrics}.
42
+ * @async
43
+ */
44
+ declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
45
+ /**
46
+ * Reads font metrics from a specified source URL or file path. This function supports both local files and remote URLs.
47
+ * It caches the results to optimise subsequent requests for the same source.
48
+ * @param {URL | string} _source - The source URL or local file path from which to read the font metrics.
49
+ * @returns {Promise<FontFaceMetrics | null>} - A promise that resolves to the filtered font metrics or null if the source cannot be processed.
50
+ * @async
51
+ */
52
+ declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
3
53
 
4
54
  interface FontaineTransformOptions {
55
+ /**
56
+ * Configuration options for the CSS transformation.
57
+ * @optional
58
+ */
5
59
  css?: {
60
+ /**
61
+ * Holds the current value of the CSS being transformed.
62
+ * @optional
63
+ */
6
64
  value?: string;
7
65
  };
66
+ /**
67
+ * An array of fallback font family names to use.
68
+ */
8
69
  fallbacks: string[];
70
+ /**
71
+ * Function to resolve a given path to a valid URL or local path.
72
+ * This is typically used to resolve font file paths.
73
+ * @optional
74
+ */
9
75
  resolvePath?: (path: string) => string | URL;
76
+ /**
77
+ * A function to determine whether to skip font face generation for a given fallback name.
78
+ * @optional
79
+ */
10
80
  skipFontFaceGeneration?: (fallbackName: string) => boolean;
11
- /** this should produce an unquoted font family name */
81
+ /**
82
+ * Function to generate an unquoted font family name to use as a fallback.
83
+ * This should return a valid CSS font family name and should not include quotes.
84
+ * @optional
85
+ */
12
86
  fallbackName?: (name: string) => string;
13
87
  /** @deprecated use fallbackName */
14
88
  overrideName?: (name: string) => string;
89
+ /**
90
+ * Specifies whether to create a source map for the transformation.
91
+ * @optional
92
+ */
15
93
  sourcemap?: boolean;
16
94
  }
95
+ /**
96
+ * Transforms CSS files to include font fallbacks.
97
+ *
98
+ * @param options - The transformation options. See {@link FontaineTransformOptions}.
99
+ * @returns The unplugin instance.
100
+ */
17
101
  declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions, boolean>;
18
102
 
19
- declare function generateFallbackName(name: string): string;
20
- interface FallbackOptions {
21
- name: string;
22
- font: string;
23
- metrics?: FontFaceMetrics;
24
- [key: string]: any;
25
- }
26
- type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
27
- declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
28
-
29
- declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
30
- declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
31
-
32
- export { FontaineTransform, type FontaineTransformOptions, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
103
+ export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
104
+ export type { FontaineTransformOptions };
package/dist/index.mjs CHANGED
@@ -1,11 +1,12 @@
1
- import { createUnplugin } from 'unplugin';
2
- import { createRegExp, charIn, exactly, whitespace, charNotIn, anyOf } from 'magic-regexp';
3
- import MagicString from 'magic-string';
4
- import { parseURL } from 'ufo';
5
- import { isAbsolute, join } from 'pathe';
6
- import { fileURLToPath } from 'node:url';
7
- import { fromFile, fromUrl } from '@capsizecss/unpack';
1
+ import { parse, walk, generate } from 'css-tree';
2
+ import { createRegExp, charIn, exactly, anyOf } from 'magic-regexp';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
4
  import { fontFamilyToCamelCase } from '@capsizecss/metrics';
5
+ import { fromFile, fromUrl } from '@capsizecss/unpack';
6
+ import { parseURL } from 'ufo';
7
+ import MagicString from 'magic-string';
8
+ import { isAbsolute } from 'pathe';
9
+ import { createUnplugin } from 'unplugin';
9
10
 
10
11
  function toPercentage(value, fractionDigits = 4) {
11
12
  const percentage = value * 100;
@@ -18,44 +19,91 @@ const QUOTES_RE = createRegExp(
18
19
  charIn(`"'`).at.lineStart().or(charIn(`"'`).at.lineEnd()),
19
20
  ["g"]
20
21
  );
21
- const FAMILY_RE = createRegExp(
22
- exactly("font-family:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("fontFamily"))
23
- );
24
- const SOURCE_RE = createRegExp(
25
- exactly("src:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("src")),
26
- ["g"]
27
- );
28
- const URL_RE = createRegExp(
29
- exactly("url(").and(charNotIn(")").times.any().as("url")).and(")"),
30
- ["g"]
31
- );
32
22
  const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
33
- function* parseFontFace(css) {
34
- const fontFamily = css.match(FAMILY_RE)?.groups.fontFamily;
35
- const family = withoutQuotes(fontFamily?.split(",")[0] || "");
36
- for (const match of css.matchAll(SOURCE_RE)) {
37
- const sources = match.groups.src?.split(",");
38
- for (const entry of sources || []) {
39
- for (const url of entry.matchAll(URL_RE)) {
40
- const source = withoutQuotes(url.groups?.url || "");
41
- if (source)
42
- yield { family, source };
23
+ const genericCSSFamilies = /* @__PURE__ */ new Set([
24
+ "serif",
25
+ "sans-serif",
26
+ "monospace",
27
+ "cursive",
28
+ "fantasy",
29
+ "system-ui",
30
+ "ui-serif",
31
+ "ui-sans-serif",
32
+ "ui-monospace",
33
+ "ui-rounded",
34
+ "emoji",
35
+ "math",
36
+ "fangsong"
37
+ ]);
38
+ const fontProperties = /* @__PURE__ */ new Set(["font-weight", "font-style", "font-stretch"]);
39
+ function parseFontFace(css) {
40
+ const families = [];
41
+ const ast = typeof css === "string" ? parse(css, { positions: true }) : css;
42
+ walk(ast, {
43
+ visit: "Atrule",
44
+ enter(node) {
45
+ if (node.name !== "font-face")
46
+ return;
47
+ let family;
48
+ const sources = [];
49
+ const properties = {};
50
+ if (node.block) {
51
+ walk(node.block, {
52
+ visit: "Declaration",
53
+ enter(declaration) {
54
+ if (declaration.property === "font-family" && declaration.value.type === "Value") {
55
+ for (const child of declaration.value.children) {
56
+ if (child.type === "String") {
57
+ family = withoutQuotes(child.value);
58
+ break;
59
+ }
60
+ if (child.type === "Identifier" && !genericCSSFamilies.has(child.name)) {
61
+ family = child.name;
62
+ break;
63
+ }
64
+ }
65
+ }
66
+ if (fontProperties.has(declaration.property)) {
67
+ if (declaration.value.type === "Value") {
68
+ for (const child of declaration.value.children) {
69
+ const hasValue = !!properties[declaration.property];
70
+ properties[declaration.property] ||= "";
71
+ properties[declaration.property] += (hasValue ? " " : "") + generate(child);
72
+ }
73
+ }
74
+ }
75
+ if (declaration.property === "src") {
76
+ walk(declaration.value, {
77
+ visit: "Url",
78
+ enter(urlNode) {
79
+ const source = withoutQuotes(urlNode.value);
80
+ if (source) {
81
+ sources.push(source);
82
+ }
83
+ }
84
+ });
85
+ }
86
+ }
87
+ });
88
+ }
89
+ if (family) {
90
+ for (const source of sources) {
91
+ families.push({ index: node.loc.start.offset, family, source, properties });
92
+ }
93
+ if (!sources.length) {
94
+ families.push({ index: node.loc.start.offset, family, properties });
95
+ }
43
96
  }
44
97
  }
45
- }
46
- yield { family: "", source: "" };
98
+ });
99
+ return families;
47
100
  }
48
101
  function generateFallbackName(name) {
49
102
  const firstFamily = withoutQuotes(name.split(",").shift());
50
103
  return `${firstFamily} fallback`;
51
104
  }
52
105
  function generateFontFace(metrics, fallback) {
53
- const {
54
- name: fallbackName,
55
- font: fallbackFontName,
56
- metrics: fallbackMetrics,
57
- ...properties
58
- } = fallback;
106
+ const { name: fallbackName, font: fallbackFontName, metrics: fallbackMetrics, ...properties } = fallback;
59
107
  const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
60
108
  const fallbackFontXAvgRatio = fallbackMetrics ? fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm : 1;
61
109
  const sizeAdjust = fallbackMetrics && preferredFontXAvgRatio && fallbackFontXAvgRatio ? preferredFontXAvgRatio / fallbackFontXAvgRatio : 1;
@@ -79,13 +127,7 @@ ${toCSS(declaration)}
79
127
  }
80
128
 
81
129
  const metricCache = {};
82
- function filterRequiredMetrics({
83
- ascent,
84
- descent,
85
- lineGap,
86
- unitsPerEm,
87
- xWidthAvg
88
- }) {
130
+ function filterRequiredMetrics({ ascent, descent, lineGap, unitsPerEm, xWidthAvg }) {
89
131
  return {
90
132
  ascent,
91
133
  descent,
@@ -102,6 +144,10 @@ async function getMetricsForFamily(family) {
102
144
  const name = fontFamilyToCamelCase(family);
103
145
  const { entireMetricsCollection } = await import('@capsizecss/metrics/entireMetricsCollection');
104
146
  const metrics = entireMetricsCollection[name];
147
+ if (!("descent" in metrics)) {
148
+ metricCache[family] = null;
149
+ return null;
150
+ }
105
151
  const filteredMetrics = filterRequiredMetrics(metrics);
106
152
  metricCache[family] = filteredMetrics;
107
153
  return filteredMetrics;
@@ -110,110 +156,113 @@ async function getMetricsForFamily(family) {
110
156
  return null;
111
157
  }
112
158
  }
159
+ const urlRequestCache = /* @__PURE__ */ new Map();
113
160
  async function readMetrics(_source) {
114
161
  const source = typeof _source !== "string" && "href" in _source ? _source.href : _source;
115
162
  if (source in metricCache)
116
- return Promise.resolve(metricCache[source]);
163
+ return metricCache[source];
117
164
  const { protocol } = parseURL(source);
118
165
  if (!protocol)
119
166
  return null;
120
- const metrics = protocol === "file:" ? await fromFile(fileURLToPath(source)) : await fromUrl(source);
167
+ let metrics;
168
+ if (protocol === "file:") {
169
+ metrics = await fromFile(fileURLToPath(source));
170
+ } else {
171
+ if (urlRequestCache.has(source)) {
172
+ metrics = await urlRequestCache.get(source);
173
+ } else {
174
+ const requestPromise = fromUrl(source);
175
+ urlRequestCache.set(source, requestPromise);
176
+ metrics = await requestPromise;
177
+ }
178
+ }
121
179
  const filteredMetrics = filterRequiredMetrics(metrics);
122
180
  metricCache[source] = filteredMetrics;
123
181
  return filteredMetrics;
124
182
  }
125
183
 
126
184
  const supportedExtensions = ["woff2", "woff", "ttf"];
127
- const FONT_FACE_RE = createRegExp(
128
- exactly("@font-face").and(whitespace.times.any()).and("{").and(charNotIn("}").times.any()).and("}"),
129
- ["g"]
130
- );
131
- const FONT_FAMILY_RE = createRegExp(
132
- charNotIn(";}").times.any().as("family").after(exactly("font-family:").and(whitespace.times.any())).before(charIn(";}").or(exactly("").at.lineEnd())),
133
- ["g"]
134
- );
135
185
  const CSS_RE = createRegExp(
136
186
  exactly(".").and(anyOf("sass", "css", "scss")).at.lineEnd()
137
187
  );
138
- const FontaineTransform = createUnplugin(
139
- (options) => {
140
- const cssContext = options.css = options.css || {};
141
- cssContext.value = "";
142
- const resolvePath = options.resolvePath || ((id) => id);
143
- const fallbackName = options.fallbackName || options.overrideName || generateFallbackName;
144
- const skipFontFaceGeneration = options.skipFontFaceGeneration || (() => false);
145
- function readMetricsFromId(path, importer) {
146
- const resolvedPath = isAbsolute(importer) && path.startsWith(".") ? join(importer, path) : resolvePath(path);
147
- return readMetrics(resolvedPath);
148
- }
149
- return {
150
- name: "fontaine-transform",
151
- enforce: "pre",
152
- transformInclude(id) {
153
- const { pathname } = parseURL(id);
154
- return CSS_RE.test(pathname) || CSS_RE.test(id);
155
- },
156
- async transform(code, id) {
157
- const s = new MagicString(code);
158
- const faceRanges = [];
159
- for (const match of code.matchAll(FONT_FACE_RE)) {
160
- const matchContent = match[0];
161
- if (match.index === void 0 || !matchContent)
188
+ const RELATIVE_RE = createRegExp(
189
+ exactly(".").or("..").and(anyOf("/", "\\")).at.lineStart()
190
+ );
191
+ const FontaineTransform = createUnplugin((options) => {
192
+ const cssContext = options.css = options.css || {};
193
+ cssContext.value = "";
194
+ const resolvePath = options.resolvePath || ((id) => id);
195
+ const fallbackName = options.fallbackName || options.overrideName || generateFallbackName;
196
+ const skipFontFaceGeneration = options.skipFontFaceGeneration || (() => false);
197
+ function readMetricsFromId(path, importer) {
198
+ const resolvedPath = isAbsolute(importer) && RELATIVE_RE.test(path) ? new URL(path, pathToFileURL(importer)) : resolvePath(path);
199
+ return readMetrics(resolvedPath);
200
+ }
201
+ return {
202
+ name: "fontaine-transform",
203
+ enforce: "pre",
204
+ transformInclude(id) {
205
+ const { pathname } = parseURL(id);
206
+ return CSS_RE.test(pathname) || CSS_RE.test(id);
207
+ },
208
+ async transform(code, id) {
209
+ const s = new MagicString(code);
210
+ const ast = parse(code, { positions: true });
211
+ for (const { family, source, index, properties } of parseFontFace(ast)) {
212
+ if (!supportedExtensions.some((e) => source?.endsWith(e)))
213
+ continue;
214
+ if (skipFontFaceGeneration(fallbackName(family)))
215
+ continue;
216
+ const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
217
+ if (!metrics)
218
+ continue;
219
+ for (let i = options.fallbacks.length - 1; i >= 0; i--) {
220
+ const fallback = options.fallbacks[i];
221
+ const fallbackMetrics = await getMetricsForFamily(fallback);
222
+ if (!fallbackMetrics)
162
223
  continue;
163
- faceRanges.push([match.index, match.index + matchContent.length]);
164
- for (const { family, source } of parseFontFace(matchContent)) {
224
+ const fontFace = generateFontFace(metrics, {
225
+ name: fallbackName(family),
226
+ font: fallback,
227
+ metrics: fallbackMetrics,
228
+ ...properties
229
+ });
230
+ cssContext.value += fontFace;
231
+ s.appendLeft(index, fontFace);
232
+ }
233
+ }
234
+ walk(ast, {
235
+ visit: "Declaration",
236
+ enter(node) {
237
+ if (node.property !== "font-family")
238
+ return;
239
+ if (this.atrule && this.atrule.name === "font-face")
240
+ return;
241
+ if (node.value.type !== "Value")
242
+ return;
243
+ for (const child of node.value.children) {
244
+ let family;
245
+ if (child.type === "String") {
246
+ family = withoutQuotes(child.value);
247
+ } else if (child.type === "Identifier" && child.name !== "inherit") {
248
+ family = child.name;
249
+ }
165
250
  if (!family)
166
251
  continue;
167
- if (!supportedExtensions.some((e) => source?.endsWith(e)))
168
- continue;
169
- if (skipFontFaceGeneration(fallbackName(family)))
170
- continue;
171
- const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
172
- if (!metrics)
173
- continue;
174
- for (let i = options.fallbacks.length - 1; i >= 0; i--) {
175
- const fallback = options.fallbacks[i];
176
- const fallbackMetrics = await getMetricsForFamily(fallback);
177
- if (!fallbackMetrics)
178
- continue;
179
- const fontFace = generateFontFace(metrics, {
180
- name: fallbackName(family),
181
- font: fallback,
182
- metrics: fallbackMetrics
183
- });
184
- cssContext.value += fontFace;
185
- s.appendLeft(match.index, fontFace);
186
- }
252
+ s.appendRight(child.loc.end.offset, `, "${fallbackName(family)}"`);
253
+ return;
187
254
  }
188
255
  }
189
- for (const match of code.matchAll(FONT_FAMILY_RE)) {
190
- const { index, 0: matchContent } = match;
191
- if (index === void 0 || !matchContent)
192
- continue;
193
- if (faceRanges.some(([start, end]) => index > start && index < end))
194
- continue;
195
- const families = matchContent.split(",").map((f) => f.trim()).filter((f) => !f.startsWith("var("));
196
- if (!families.length || families[0] === "inherit")
197
- continue;
198
- s.overwrite(
199
- index,
200
- index + matchContent.length,
201
- ` ${[
202
- families[0],
203
- `"${generateFallbackName(families[0])}"`,
204
- ...families.slice(1)
205
- ].join(", ")}`
206
- );
207
- }
208
- if (s.hasChanged()) {
209
- return {
210
- code: s.toString(),
211
- map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
212
- };
213
- }
256
+ });
257
+ if (s.hasChanged()) {
258
+ return {
259
+ code: s.toString(),
260
+ /* v8 ignore next 3 */
261
+ map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
262
+ };
214
263
  }
215
- };
216
- }
217
- );
264
+ }
265
+ };
266
+ });
218
267
 
219
268
  export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
package/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "fontaine",
3
3
  "type": "module",
4
- "version": "0.5.0",
5
- "packageManager": "pnpm@8.15.4",
4
+ "version": "0.6.0",
5
+ "packageManager": "pnpm@10.9.0",
6
6
  "description": "Automatic font fallback based on font metrics",
7
7
  "author": {
8
- "name": "Daniel Roe <daniel@roe.dev>",
8
+ "name": "Daniel Roe",
9
+ "email": "daniel@roe.dev",
9
10
  "url": "https://github.com/danielroe"
10
11
  },
11
12
  "license": "MIT",
12
- "repository": "unjs/fontaine",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/unjs/fontaine.git"
16
+ },
13
17
  "keywords": [
14
18
  "fonts",
15
19
  "cls",
@@ -34,43 +38,42 @@
34
38
  "dev": "vitest",
35
39
  "demo": "vite dev playground",
36
40
  "demo:dev": "pnpm demo --config test/vite.config.mjs",
37
- "lint": "eslint --fix .",
41
+ "lint": "eslint .",
38
42
  "prepare": "simple-git-hooks install && pnpm build",
39
43
  "prepublishOnly": "pnpm lint && pnpm test",
40
44
  "release": "pnpm test && bumpp && npm publish",
41
45
  "test": "vitest run"
42
46
  },
43
47
  "dependencies": {
44
- "@capsizecss/metrics": "^2.1.1",
45
- "@capsizecss/unpack": "^2.0.1",
46
- "magic-regexp": "^0.8.0",
47
- "magic-string": "^0.30.8",
48
- "pathe": "^1.1.2",
49
- "ufo": "^1.4.0",
50
- "unplugin": "^1.8.3"
48
+ "@capsizecss/metrics": "^3.5.0",
49
+ "@capsizecss/unpack": "^2.4.0",
50
+ "css-tree": "^3.1.0",
51
+ "magic-regexp": "^0.10.0",
52
+ "magic-string": "^0.30.17",
53
+ "pathe": "^2.0.3",
54
+ "ufo": "^1.6.1",
55
+ "unplugin": "^2.3.2"
51
56
  },
52
57
  "devDependencies": {
53
- "@antfu/eslint-config": "^2.8.0",
58
+ "@antfu/eslint-config": "4.12.0",
54
59
  "@nuxtjs/eslint-config-typescript": "latest",
55
- "@types/node": "20.11.25",
60
+ "@types/css-tree": "^2.3.10",
61
+ "@types/node": "22.14.1",
56
62
  "@types/serve-handler": "6.1.4",
57
- "@typescript-eslint/eslint-plugin": "7.1.1",
58
- "@typescript-eslint/parser": "7.1.1",
59
- "@vitest/coverage-v8": "1.3.1",
60
- "bumpp": "9.4.0",
61
- "eslint": "8.57.0",
62
- "eslint-config-prettier": "9.1.0",
63
- "eslint-plugin-prettier": "latest",
64
- "execa": "8.0.1",
63
+ "@typescript-eslint/eslint-plugin": "8.31.0",
64
+ "@typescript-eslint/parser": "8.31.0",
65
+ "@vitest/coverage-v8": "3.1.2",
66
+ "bumpp": "10.1.0",
67
+ "eslint": "9.25.1",
68
+ "execa": "9.5.2",
65
69
  "get-port-please": "3.1.2",
66
- "lint-staged": "15.2.2",
67
- "prettier": "latest",
68
- "serve-handler": "6.1.5",
69
- "simple-git-hooks": "^2.10.0",
70
- "typescript": "5.4.2",
70
+ "lint-staged": "15.5.1",
71
+ "serve-handler": "6.1.6",
72
+ "simple-git-hooks": "2.12.1",
73
+ "typescript": "5.8.3",
71
74
  "unbuild": "latest",
72
- "vite": "5.1.5",
73
- "vitest": "1.3.1"
75
+ "vite": "6.3.2",
76
+ "vitest": "3.1.2"
74
77
  },
75
78
  "resolutions": {
76
79
  "fontaine": "link:."
@@ -80,7 +83,7 @@
80
83
  },
81
84
  "lint-staged": {
82
85
  "*.{js,ts,mjs,cjs,json,.*rc}": [
83
- "pnpm eslint --fix"
86
+ "npx eslint --fix"
84
87
  ]
85
88
  }
86
89
  }