fontaine 0.0.1 → 0.1.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/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Daniel Roe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # fontaine
2
+
3
+ [![npm version][npm-version-src]][npm-version-href]
4
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
+ [![Github Actions][github-actions-src]][github-actions-href]
6
+ [![Codecov][codecov-src]][codecov-href]
7
+
8
+ > Automatic font fallback based on font metrics
9
+
10
+ - [✨  Changelog](https://github.com/danielroe/fontaine/blob/main/CHANGELOG.md)
11
+ - [▶️  Online playground](https://stackblitz.com/github/danielroe/fontaine/tree/main/playground)
12
+
13
+ ## Features
14
+
15
+ **⚠️ `fontaine` is under active development. ⚠️**
16
+
17
+ - 💪 Reduces CLS by using local font fallbacks with crafted font metrics.
18
+ - ✨ Generates font metrics and overrides automatically.
19
+ - ⚡️ Pure CSS, zero runtime overhead.
20
+
21
+ On the playground project, enabling/disabling `fontaine` makes the following difference rendering `/`, with no customisation required:
22
+
23
+ | | Before | After |
24
+ | ----------- | ------ | ------- |
25
+ | CLS | `0.24` | `0.054` |
26
+ | Performance | `92` | `100` |
27
+
28
+ ## Installation
29
+
30
+ With `pnpm`
31
+
32
+ ```bash
33
+ pnpm add -D fontaine
34
+ ```
35
+
36
+ Or, with `npm`
37
+
38
+ ```bash
39
+ npm install -D fontaine
40
+ ```
41
+
42
+ Or, with `yarn`
43
+
44
+ ```bash
45
+ yarn add -D fontaine
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ```js
51
+ import { FontaineTransform } from 'fontaine'
52
+
53
+ const options = {
54
+ fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans'],
55
+ // You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
56
+ resolvePath: (id) => 'file:///path/to/public/dir' + id,
57
+ }
58
+
59
+ // Vite
60
+ export default {
61
+ plugins: [FontaineTransform.vite(options)]
62
+ }
63
+
64
+ // Next.js
65
+ export default {
66
+ webpack(config) {
67
+ config.plugins = config.plugins || []
68
+ config.plugins.push(FontaineTransform.webpack(options))
69
+ return config
70
+ },
71
+ }
72
+ ```
73
+
74
+ > **Note**
75
+ > If you are using Nuxt, check out [nuxt-font-metrics](https://github.com/danielroe/nuxt-font-metrics) which uses `fontaine` under the hood.
76
+
77
+ ## How it works
78
+
79
+ `fontaine` will scan your `@font-face` rules and generate fallback rules with the correct metrics. For example:
80
+
81
+ ```css
82
+ @font-face {
83
+ font-family: 'Roboto';
84
+ font-display: swap;
85
+ src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff')
86
+ format('woff');
87
+ font-weight: 700;
88
+ }
89
+ /* This additional font-face declaration will be added to your CSS. */
90
+ @font-face {
91
+ font-family: 'Roboto override';
92
+ src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local(
93
+ 'Helvetica Neue'
94
+ ), local('Arial'), local('Noto Sans');
95
+ ascent-override: 92.7734375%;
96
+ descent-override: 24.4140625%;
97
+ line-gap-override: 0%;
98
+ }
99
+ ```
100
+
101
+ Then, whenever you use `font-family: 'Roboto'`, `fontaine` will add the override to the font-family:
102
+
103
+ ```css
104
+ :root {
105
+ font-family: 'Roboto';
106
+ /* This becomes */
107
+ font-family: 'Roboto', 'Roboto override';
108
+ }
109
+ ```
110
+
111
+ ## 💻 Development
112
+
113
+ - Clone this repository
114
+ - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` (use `npm i -g corepack` for Node.js < 16.10)
115
+ - Install dependencies using `pnpm install`
116
+ - Run interactive tests using `pnpm dev`; launch a vite server using source code with `pnpm demo:dev`
117
+
118
+ ## Credits
119
+
120
+ This would not have been possible without:
121
+
122
+ - amazing tooling and generated metrics from [capsizecss](https://seek-oss.github.io/capsize/)
123
+ - suggestion and algorithm from [Katie Hempenius](https://katiehempenius.com/) & [Kara Erickson](https://github.com/kara) on the Google Aurora team - see [notes on calculating font metric overrides](https://docs.google.com/document/d/e/2PACX-1vRsazeNirATC7lIj2aErSHpK26hZ6dA9GsQ069GEbq5fyzXEhXbvByoftSfhG82aJXmrQ_sJCPBqcx_/pub).
124
+
125
+ ## License
126
+
127
+ Made with ❤️
128
+
129
+ Published under [MIT License](./LICENCE).
130
+
131
+ <!-- Badges -->
132
+
133
+ [npm-version-src]: https://img.shields.io/npm/v/fontaine?style=flat-square
134
+ [npm-version-href]: https://npmjs.com/package/fontaine
135
+ [npm-downloads-src]: https://img.shields.io/npm/dm/fontaine?style=flat-square
136
+ [npm-downloads-href]: https://npmjs.com/package/fontaine
137
+ [github-actions-src]: https://img.shields.io/github/workflow/status/danielroe/fontaine/ci/main?style=flat-square
138
+ [github-actions-href]: https://github.com/danielroe/fontaine/actions?query=workflow%3Aci
139
+ [codecov-src]: https://img.shields.io/codecov/c/gh/danielroe/fontaine/main?style=flat-square
140
+ [codecov-href]: https://codecov.io/gh/danielroe/fontaine
package/dist/index.cjs ADDED
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const unplugin = require('unplugin');
6
+ const magicRegexp = require('magic-regexp');
7
+ const MagicString = require('magic-string');
8
+ const node_url = require('node:url');
9
+ const unpack = require('@capsizecss/unpack');
10
+ const ufo = require('ufo');
11
+ const scule = require('scule');
12
+
13
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
14
+
15
+ const MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);
16
+
17
+ const parseFontFace = (css) => {
18
+ const fontFamily = css.match(FAMILY_RE)?.groups.fontFamily;
19
+ const src = css.match(SOURCE_RE)?.groups.src;
20
+ const family = withoutQuotes(fontFamily?.split(",")[0] || "");
21
+ const source = withoutQuotes(
22
+ src?.split(",").map((source2) => source2.match(URL_RE)?.groups.url).filter(Boolean)[0] || ""
23
+ );
24
+ return { family, source };
25
+ };
26
+ const generateOverrideName = (name) => {
27
+ const firstFamily = withoutQuotes(name.split(",").shift());
28
+ return `${firstFamily} override`;
29
+ };
30
+ const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
31
+ const generateFontFace = (metrics, options) => {
32
+ const { name, fallbacks, ...properties } = options;
33
+ const sizeAdjust = 1;
34
+ const declaration = {
35
+ "font-family": JSON.stringify(name),
36
+ src: fallbacks.map((f) => `local(${JSON.stringify(f)})`),
37
+ "ascent-override": toPercentage(
38
+ metrics.ascent / (metrics.unitsPerEm * sizeAdjust)
39
+ ),
40
+ "descent-override": toPercentage(
41
+ Math.abs(metrics.descent / (metrics.unitsPerEm * sizeAdjust))
42
+ ),
43
+ "line-gap-override": toPercentage(
44
+ metrics.lineGap / (metrics.unitsPerEm * sizeAdjust)
45
+ ),
46
+ ...properties
47
+ };
48
+ return `@font-face {
49
+ ${toCSS(declaration)}
50
+ }
51
+ `;
52
+ };
53
+ const toPercentage = (value, fractionDigits = 8) => {
54
+ const percentage = value * 100;
55
+ return (percentage % 1 ? percentage.toFixed(fractionDigits).replace(/0+$/, "") : percentage) + "%";
56
+ };
57
+ const toCSS = (properties, indent = 2) => Object.entries(properties).map(([key, value]) => " ".repeat(indent) + `${key}: ${value};`).join("\n");
58
+ const QUOTES_RE = magicRegexp.createRegExp(
59
+ magicRegexp.charIn(`"'`).at.lineStart().or(magicRegexp.charIn(`"'`).at.lineEnd()),
60
+ ["g"]
61
+ );
62
+ const FAMILY_RE = magicRegexp.createRegExp(
63
+ magicRegexp.exactly("font-family:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("fontFamily"))
64
+ );
65
+ const SOURCE_RE = magicRegexp.createRegExp(
66
+ magicRegexp.exactly("src:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("src"))
67
+ );
68
+ const URL_RE = magicRegexp.createRegExp(
69
+ magicRegexp.exactly("url(").and(magicRegexp.charNotIn(")").times.any().as("url")).and(")")
70
+ );
71
+
72
+ const metricCache = {};
73
+ async function getMetricsForFamily(family) {
74
+ family = withoutQuotes(family);
75
+ if (family in metricCache)
76
+ return metricCache[family];
77
+ try {
78
+ const name = scule.camelCase(family).replace(/ /, "");
79
+ const metrics = await import(`@capsizecss/metrics/${name}.js`).then(
80
+ (r) => r.default || r
81
+ );
82
+ metricCache[family] = metrics;
83
+ return metrics;
84
+ } catch {
85
+ metricCache[family] = null;
86
+ return null;
87
+ }
88
+ }
89
+ async function readMetrics(_source) {
90
+ const source = typeof _source !== "string" && "href" in _source ? _source.href : _source;
91
+ if (source in metricCache) {
92
+ return Promise.resolve(metricCache[source]);
93
+ }
94
+ const { protocol } = ufo.parseURL(source);
95
+ if (!protocol)
96
+ return null;
97
+ const metrics = protocol === "file:" ? await unpack.fromFile(node_url.fileURLToPath(source)) : await unpack.fromUrl(source);
98
+ metricCache[source] = metrics;
99
+ return metrics;
100
+ }
101
+
102
+ const FontaineTransform = unplugin.createUnplugin(
103
+ (options) => {
104
+ const cssContext = options.css = options.css || {};
105
+ cssContext.value = "";
106
+ return {
107
+ name: "fontaine-transform",
108
+ enforce: "pre",
109
+ transformInclude(id) {
110
+ const { pathname } = ufo.parseURL(id);
111
+ return pathname.endsWith(".css");
112
+ },
113
+ async transform(code, id) {
114
+ const s = new MagicString__default(code);
115
+ const faceRanges = [];
116
+ for (const match of code.matchAll(FONT_FACE_RE)) {
117
+ const matchContent = match[0];
118
+ if (match.index === void 0 || !matchContent)
119
+ continue;
120
+ faceRanges.push([match.index, match.index + matchContent.length]);
121
+ const { family, source } = parseFontFace(matchContent);
122
+ if (!family)
123
+ continue;
124
+ const metrics = await getMetricsForFamily(family) || (source ? await readMetrics(
125
+ options.resolvePath ? options.resolvePath(source) : source
126
+ ).catch(() => null) : null);
127
+ if (metrics) {
128
+ const fontFace = generateFontFace(metrics, {
129
+ name: generateOverrideName(family),
130
+ fallbacks: options.fallbacks
131
+ });
132
+ cssContext.value += fontFace;
133
+ s.appendLeft(match.index, fontFace);
134
+ }
135
+ }
136
+ for (const match of code.matchAll(FONT_FAMILY_RE)) {
137
+ const { index, 0: matchContent } = match;
138
+ if (index === void 0 || !matchContent)
139
+ continue;
140
+ if (faceRanges.some(([start, end]) => index > start && index < end))
141
+ continue;
142
+ const families = matchContent.split(",").map((f) => f.trim());
143
+ s.overwrite(
144
+ index,
145
+ index + matchContent.length,
146
+ " " + [
147
+ families[0],
148
+ `"${generateOverrideName(families[0])}"`,
149
+ ...families.slice(1)
150
+ ].join(", ")
151
+ );
152
+ }
153
+ if (s.hasChanged()) {
154
+ return {
155
+ code: s.toString(),
156
+ map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
157
+ };
158
+ }
159
+ }
160
+ };
161
+ }
162
+ );
163
+ const FONT_FACE_RE = magicRegexp.createRegExp(
164
+ magicRegexp.exactly("@font-face").and(magicRegexp.whitespace.times.any()).and("{").and(magicRegexp.charNotIn("}").times.any()).and("}"),
165
+ ["g"]
166
+ );
167
+ const FONT_FAMILY_RE = magicRegexp.createRegExp(
168
+ magicRegexp.charNotIn(";}").times.any().as("family").after(magicRegexp.exactly("font-family:").and(magicRegexp.whitespace.times.any())).before(magicRegexp.charIn(";}").or(magicRegexp.exactly("").at.lineEnd())),
169
+ ["g"]
170
+ );
171
+
172
+ exports.FontaineTransform = FontaineTransform;
173
+ exports.generateFontFace = generateFontFace;
174
+ exports.generateOverrideName = generateOverrideName;
175
+ exports.getMetricsForFamily = getMetricsForFamily;
176
+ exports.readMetrics = readMetrics;
@@ -0,0 +1,47 @@
1
+ import * as unplugin from 'unplugin';
2
+ import { Font } from '@capsizecss/unpack';
3
+
4
+ interface FontaineTransformOptions {
5
+ css?: {
6
+ value?: string;
7
+ };
8
+ fallbacks: string[];
9
+ resolvePath?: (path: string) => string | URL;
10
+ sourcemap?: boolean;
11
+ }
12
+ declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions>;
13
+
14
+ declare const generateOverrideName: (name: string) => string;
15
+ interface GenerateOptions {
16
+ name: string;
17
+ fallbacks: string[];
18
+ [key: string]: any;
19
+ }
20
+ declare const generateFontFace: (metrics: Font, options: GenerateOptions) => string;
21
+
22
+ declare function getMetricsForFamily(family: string): Promise<{
23
+ familyName: string;
24
+ fullName: string;
25
+ postscriptName: string;
26
+ subfamilyName: string;
27
+ capHeight: number;
28
+ ascent: number;
29
+ descent: number;
30
+ lineGap: number;
31
+ unitsPerEm: number;
32
+ xHeight: number;
33
+ } | null>;
34
+ declare function readMetrics(_source: URL | string): Promise<{
35
+ familyName: string;
36
+ fullName: string;
37
+ postscriptName: string;
38
+ subfamilyName: string;
39
+ capHeight: number;
40
+ ascent: number;
41
+ descent: number;
42
+ lineGap: number;
43
+ unitsPerEm: number;
44
+ xHeight: number;
45
+ } | null>;
46
+
47
+ export { FontaineTransform, generateFontFace, generateOverrideName, getMetricsForFamily, readMetrics };
package/dist/index.mjs ADDED
@@ -0,0 +1,164 @@
1
+ import { createUnplugin } from 'unplugin';
2
+ import { createRegExp, charIn, exactly, whitespace, charNotIn } from 'magic-regexp';
3
+ import MagicString from 'magic-string';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { fromFile, fromUrl } from '@capsizecss/unpack';
6
+ import { parseURL } from 'ufo';
7
+ import { camelCase } from 'scule';
8
+
9
+ const parseFontFace = (css) => {
10
+ const fontFamily = css.match(FAMILY_RE)?.groups.fontFamily;
11
+ const src = css.match(SOURCE_RE)?.groups.src;
12
+ const family = withoutQuotes(fontFamily?.split(",")[0] || "");
13
+ const source = withoutQuotes(
14
+ src?.split(",").map((source2) => source2.match(URL_RE)?.groups.url).filter(Boolean)[0] || ""
15
+ );
16
+ return { family, source };
17
+ };
18
+ const generateOverrideName = (name) => {
19
+ const firstFamily = withoutQuotes(name.split(",").shift());
20
+ return `${firstFamily} override`;
21
+ };
22
+ const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
23
+ const generateFontFace = (metrics, options) => {
24
+ const { name, fallbacks, ...properties } = options;
25
+ const sizeAdjust = 1;
26
+ const declaration = {
27
+ "font-family": JSON.stringify(name),
28
+ src: fallbacks.map((f) => `local(${JSON.stringify(f)})`),
29
+ "ascent-override": toPercentage(
30
+ metrics.ascent / (metrics.unitsPerEm * sizeAdjust)
31
+ ),
32
+ "descent-override": toPercentage(
33
+ Math.abs(metrics.descent / (metrics.unitsPerEm * sizeAdjust))
34
+ ),
35
+ "line-gap-override": toPercentage(
36
+ metrics.lineGap / (metrics.unitsPerEm * sizeAdjust)
37
+ ),
38
+ ...properties
39
+ };
40
+ return `@font-face {
41
+ ${toCSS(declaration)}
42
+ }
43
+ `;
44
+ };
45
+ const toPercentage = (value, fractionDigits = 8) => {
46
+ const percentage = value * 100;
47
+ return (percentage % 1 ? percentage.toFixed(fractionDigits).replace(/0+$/, "") : percentage) + "%";
48
+ };
49
+ const toCSS = (properties, indent = 2) => Object.entries(properties).map(([key, value]) => " ".repeat(indent) + `${key}: ${value};`).join("\n");
50
+ const QUOTES_RE = createRegExp(
51
+ charIn(`"'`).at.lineStart().or(charIn(`"'`).at.lineEnd()),
52
+ ["g"]
53
+ );
54
+ const FAMILY_RE = createRegExp(
55
+ exactly("font-family:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("fontFamily"))
56
+ );
57
+ const SOURCE_RE = createRegExp(
58
+ exactly("src:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("src"))
59
+ );
60
+ const URL_RE = createRegExp(
61
+ exactly("url(").and(charNotIn(")").times.any().as("url")).and(")")
62
+ );
63
+
64
+ const metricCache = {};
65
+ async function getMetricsForFamily(family) {
66
+ family = withoutQuotes(family);
67
+ if (family in metricCache)
68
+ return metricCache[family];
69
+ try {
70
+ const name = camelCase(family).replace(/ /, "");
71
+ const metrics = await import(`@capsizecss/metrics/${name}.js`).then(
72
+ (r) => r.default || r
73
+ );
74
+ metricCache[family] = metrics;
75
+ return metrics;
76
+ } catch {
77
+ metricCache[family] = null;
78
+ return null;
79
+ }
80
+ }
81
+ async function readMetrics(_source) {
82
+ const source = typeof _source !== "string" && "href" in _source ? _source.href : _source;
83
+ if (source in metricCache) {
84
+ return Promise.resolve(metricCache[source]);
85
+ }
86
+ const { protocol } = parseURL(source);
87
+ if (!protocol)
88
+ return null;
89
+ const metrics = protocol === "file:" ? await fromFile(fileURLToPath(source)) : await fromUrl(source);
90
+ metricCache[source] = metrics;
91
+ return metrics;
92
+ }
93
+
94
+ const FontaineTransform = createUnplugin(
95
+ (options) => {
96
+ const cssContext = options.css = options.css || {};
97
+ cssContext.value = "";
98
+ return {
99
+ name: "fontaine-transform",
100
+ enforce: "pre",
101
+ transformInclude(id) {
102
+ const { pathname } = parseURL(id);
103
+ return pathname.endsWith(".css");
104
+ },
105
+ async transform(code, id) {
106
+ const s = new MagicString(code);
107
+ const faceRanges = [];
108
+ for (const match of code.matchAll(FONT_FACE_RE)) {
109
+ const matchContent = match[0];
110
+ if (match.index === void 0 || !matchContent)
111
+ continue;
112
+ faceRanges.push([match.index, match.index + matchContent.length]);
113
+ const { family, source } = parseFontFace(matchContent);
114
+ if (!family)
115
+ continue;
116
+ const metrics = await getMetricsForFamily(family) || (source ? await readMetrics(
117
+ options.resolvePath ? options.resolvePath(source) : source
118
+ ).catch(() => null) : null);
119
+ if (metrics) {
120
+ const fontFace = generateFontFace(metrics, {
121
+ name: generateOverrideName(family),
122
+ fallbacks: options.fallbacks
123
+ });
124
+ cssContext.value += fontFace;
125
+ s.appendLeft(match.index, fontFace);
126
+ }
127
+ }
128
+ for (const match of code.matchAll(FONT_FAMILY_RE)) {
129
+ const { index, 0: matchContent } = match;
130
+ if (index === void 0 || !matchContent)
131
+ continue;
132
+ if (faceRanges.some(([start, end]) => index > start && index < end))
133
+ continue;
134
+ const families = matchContent.split(",").map((f) => f.trim());
135
+ s.overwrite(
136
+ index,
137
+ index + matchContent.length,
138
+ " " + [
139
+ families[0],
140
+ `"${generateOverrideName(families[0])}"`,
141
+ ...families.slice(1)
142
+ ].join(", ")
143
+ );
144
+ }
145
+ if (s.hasChanged()) {
146
+ return {
147
+ code: s.toString(),
148
+ map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
149
+ };
150
+ }
151
+ }
152
+ };
153
+ }
154
+ );
155
+ const FONT_FACE_RE = createRegExp(
156
+ exactly("@font-face").and(whitespace.times.any()).and("{").and(charNotIn("}").times.any()).and("}"),
157
+ ["g"]
158
+ );
159
+ const FONT_FAMILY_RE = createRegExp(
160
+ charNotIn(";}").times.any().as("family").after(exactly("font-family:").and(whitespace.times.any())).before(charIn(";}").or(exactly("").at.lineEnd())),
161
+ ["g"]
162
+ );
163
+
164
+ export { FontaineTransform, generateFontFace, generateOverrideName, getMetricsForFamily, readMetrics };
package/package.json CHANGED
@@ -1,14 +1,87 @@
1
1
  {
2
2
  "name": "fontaine",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
+ "description": "Automatic font fallback based on font metrics",
5
+ "repository": "danielroe/fontaine",
6
+ "keywords": [
7
+ "fonts",
8
+ "cls",
9
+ "web-vitals",
10
+ "performance"
11
+ ],
12
+ "author": {
13
+ "name": "Daniel Roe <daniel@roe.dev>",
14
+ "url": "https://github.com/danielroe"
15
+ },
16
+ "license": "MIT",
17
+ "sideEffects": false,
4
18
  "type": "module",
5
- "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.cjs"
23
+ }
7
24
  },
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.mjs",
27
+ "types": "./dist/index.d.ts",
8
28
  "files": [
9
29
  "dist"
10
30
  ],
11
- "keywords": [],
12
- "author": "Daniel Roe <daniel@roe.dev>",
13
- "license": "MIT"
31
+ "scripts": {
32
+ "build": "unbuild",
33
+ "dev": "vitest",
34
+ "demo": "vite dev playground",
35
+ "demo:dev": "pnpm demo --config test/vite.config.mjs",
36
+ "lint": "pnpm lint:all:eslint && pnpm lint:all:prettier",
37
+ "lint:all:eslint": "pnpm lint:eslint --ext .ts,.js,.mjs,.cjs .",
38
+ "lint:all:prettier": "pnpm lint:prettier \"{src,test}/**/*.{js,json,ts}\"",
39
+ "lint:eslint": "eslint --fix",
40
+ "lint:prettier": "prettier --write --loglevel warn",
41
+ "prepare": "husky install && pnpm build",
42
+ "prepublishOnly": "pnpm lint && pnpm test && pinst --disable",
43
+ "release": "release-it",
44
+ "test": "vitest run",
45
+ "_postinstall": "husky install",
46
+ "postpublish": "pinst --enable"
47
+ },
48
+ "dependencies": {
49
+ "@capsizecss/metrics": "^0.3.0",
50
+ "@capsizecss/unpack": "^0.1.0",
51
+ "magic-regexp": "^0.5.0",
52
+ "magic-string": "^0.26.4",
53
+ "pathe": "^0.3.8",
54
+ "scule": "^0.3.2",
55
+ "ufo": "^0.8.5",
56
+ "unplugin": "^0.9.6"
57
+ },
58
+ "devDependencies": {
59
+ "@nuxtjs/eslint-config-typescript": "latest",
60
+ "@release-it/conventional-changelog": "latest",
61
+ "@types/node": "latest",
62
+ "@types/serve-handler": "^6.1.1",
63
+ "@vitest/coverage-c8": "^0.23.4",
64
+ "c8": "latest",
65
+ "conventional-changelog-conventionalcommits": "latest",
66
+ "eslint": "latest",
67
+ "eslint-config-prettier": "latest",
68
+ "eslint-plugin-prettier": "latest",
69
+ "execa": "^6.1.0",
70
+ "expect-type": "latest",
71
+ "get-port-please": "^2.6.1",
72
+ "husky": "latest",
73
+ "lint-staged": "latest",
74
+ "pinst": "latest",
75
+ "prettier": "latest",
76
+ "release-it": "latest",
77
+ "serve-handler": "^6.1.3",
78
+ "typescript": "latest",
79
+ "unbuild": "latest",
80
+ "vite": "latest",
81
+ "vitest": "latest"
82
+ },
83
+ "resolutions": {
84
+ "fontaine": "link:."
85
+ },
86
+ "packageManager": "pnpm@7.12.2"
14
87
  }