@flightdev/fonts 0.0.2
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/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/adapters/google.d.ts +33 -0
- package/dist/adapters/google.d.ts.map +1 -0
- package/dist/adapters/google.js +96 -0
- package/dist/adapters/google.js.map +1 -0
- package/dist/adapters/local.d.ts +28 -0
- package/dist/adapters/local.d.ts.map +1 -0
- package/dist/adapters/local.js +144 -0
- package/dist/adapters/local.js.map +1 -0
- package/dist/components/react.d.ts +103 -0
- package/dist/components/react.d.ts.map +1 -0
- package/dist/components/react.js +108 -0
- package/dist/components/react.js.map +1 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/vite.d.ts +98 -0
- package/dist/plugins/vite.d.ts.map +1 -0
- package/dist/plugins/vite.js +240 -0
- package/dist/plugins/vite.js.map +1 -0
- package/dist/subset.d.ts +113 -0
- package/dist/subset.d.ts.map +1 -0
- package/dist/subset.js +315 -0
- package/dist/subset.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flightdev/fonts - Vite Plugin for Font Optimization
|
|
3
|
+
*
|
|
4
|
+
* Build-time font optimization for Flight Framework.
|
|
5
|
+
* Features:
|
|
6
|
+
* - Automatic character extraction from source files
|
|
7
|
+
* - Unicode-range CSS generation for optimal subsetting
|
|
8
|
+
* - Self-hosting with cache busting
|
|
9
|
+
* - Development mode passthrough for fast iteration
|
|
10
|
+
*
|
|
11
|
+
* This is an OPTIONAL plugin. Developers can choose to use it or not.
|
|
12
|
+
*/
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { extractCharsFromSource, charsToCodePoints, codePointsToUnicodeRange, getNamedSubsetRanges, detectRequiredSubsets, generateFontFaceCSS, } from '../subset';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Vite Plugin
|
|
17
|
+
// ============================================================================
|
|
18
|
+
/**
|
|
19
|
+
* Vite plugin for font optimization.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // vite.config.ts
|
|
24
|
+
* import { fontOptimization } from '@flightdev/fonts/vite';
|
|
25
|
+
*
|
|
26
|
+
* export default defineConfig({
|
|
27
|
+
* plugins: [
|
|
28
|
+
* fontOptimization({
|
|
29
|
+
* strategy: 'named-subsets',
|
|
30
|
+
* subsets: ['latin', 'latin-ext'],
|
|
31
|
+
* selfHost: true,
|
|
32
|
+
* }),
|
|
33
|
+
* ],
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function fontOptimization(options = {}) {
|
|
38
|
+
const { strategy = 'named-subsets', subsets = ['latin'], selfHost = true, outputDir = 'public/_fonts', scanDirs = ['src'], scanExtensions = ['.tsx', '.jsx', '.vue', '.svelte', '.astro', '.html'], excludePatterns = ['node_modules', 'dist', '.git'], generatePreloads = true, injectToHtml = true, verbose = true, } = options;
|
|
39
|
+
let config;
|
|
40
|
+
let isProduction = false;
|
|
41
|
+
let enabled = options.enabled;
|
|
42
|
+
let projectRoot = '';
|
|
43
|
+
let extractedChars = new Set();
|
|
44
|
+
let processedFonts = [];
|
|
45
|
+
// Track registered fonts from Flight's font loader
|
|
46
|
+
const registeredFonts = new Map();
|
|
47
|
+
return {
|
|
48
|
+
name: 'flight-font-optimization',
|
|
49
|
+
enforce: 'pre',
|
|
50
|
+
configResolved(resolvedConfig) {
|
|
51
|
+
config = resolvedConfig;
|
|
52
|
+
isProduction = resolvedConfig.command === 'build';
|
|
53
|
+
projectRoot = resolvedConfig.root;
|
|
54
|
+
// Enable by default only in production
|
|
55
|
+
if (enabled === undefined) {
|
|
56
|
+
enabled = isProduction;
|
|
57
|
+
}
|
|
58
|
+
if (verbose && enabled) {
|
|
59
|
+
console.log('[flight-fonts] Font optimization enabled');
|
|
60
|
+
console.log(`[flight-fonts] Strategy: ${strategy}`);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
buildStart() {
|
|
64
|
+
if (!enabled)
|
|
65
|
+
return;
|
|
66
|
+
// Scan source files for used characters if using 'used-chars' strategy
|
|
67
|
+
if (strategy === 'used-chars') {
|
|
68
|
+
extractedChars = scanSourceFiles(projectRoot, scanDirs, scanExtensions, excludePatterns);
|
|
69
|
+
if (verbose) {
|
|
70
|
+
console.log(`[flight-fonts] Extracted ${extractedChars.size} unique characters`);
|
|
71
|
+
const detectedSubsets = detectRequiredSubsets(extractedChars);
|
|
72
|
+
console.log(`[flight-fonts] Detected subsets: ${detectedSubsets.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
transform(code, id) {
|
|
77
|
+
if (!enabled)
|
|
78
|
+
return null;
|
|
79
|
+
// Intercept font loader calls to track registered fonts
|
|
80
|
+
if (id.includes('@flightdev/fonts') || id.includes('fonts/src')) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
// Look for createFontLoader or fonts.load calls
|
|
84
|
+
if (code.includes('fonts.load(') || code.includes('.load(')) {
|
|
85
|
+
// This is a simple detection - in production, use AST parsing
|
|
86
|
+
const fontMatches = code.matchAll(/\.load\s*\(\s*['"]([^'"]+)['"]/g);
|
|
87
|
+
for (const match of fontMatches) {
|
|
88
|
+
const fontFamily = match[1];
|
|
89
|
+
if (!registeredFonts.has(fontFamily)) {
|
|
90
|
+
registeredFonts.set(fontFamily, {
|
|
91
|
+
family: fontFamily,
|
|
92
|
+
weights: ['400'],
|
|
93
|
+
styles: ['normal'],
|
|
94
|
+
subsets: strategy === 'named-subsets' ? subsets : ['latin'],
|
|
95
|
+
display: 'swap',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
},
|
|
102
|
+
generateBundle() {
|
|
103
|
+
if (!enabled)
|
|
104
|
+
return;
|
|
105
|
+
// Generate optimized font CSS
|
|
106
|
+
for (const [family, fontDef] of registeredFonts) {
|
|
107
|
+
let unicodeRange;
|
|
108
|
+
switch (strategy) {
|
|
109
|
+
case 'used-chars':
|
|
110
|
+
const codePoints = charsToCodePoints(extractedChars);
|
|
111
|
+
unicodeRange = codePointsToUnicodeRange(codePoints);
|
|
112
|
+
break;
|
|
113
|
+
case 'named-subsets':
|
|
114
|
+
unicodeRange = getNamedSubsetRanges(fontDef.subsets);
|
|
115
|
+
break;
|
|
116
|
+
case 'none':
|
|
117
|
+
default:
|
|
118
|
+
unicodeRange = undefined;
|
|
119
|
+
}
|
|
120
|
+
// Generate CSS for each weight
|
|
121
|
+
const cssRules = [];
|
|
122
|
+
for (const weight of fontDef.weights) {
|
|
123
|
+
for (const style of fontDef.styles) {
|
|
124
|
+
const fontUrl = selfHost
|
|
125
|
+
? `/_fonts/${family.toLowerCase().replace(/\s/g, '-')}-${weight}.woff2`
|
|
126
|
+
: `https://fonts.gstatic.com/s/${family.toLowerCase().replace(/\s/g, '')}/*/${weight}.woff2`;
|
|
127
|
+
cssRules.push(generateFontFaceCSS(family, fontUrl, {
|
|
128
|
+
weight,
|
|
129
|
+
style,
|
|
130
|
+
display: fontDef.display,
|
|
131
|
+
unicodeRange,
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
processedFonts.push({
|
|
136
|
+
family,
|
|
137
|
+
css: cssRules.join('\n'),
|
|
138
|
+
unicodeRange,
|
|
139
|
+
preloadLinks: generatePreloads ? generatePreloadLinks(family, fontDef.weights) : [],
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (verbose && processedFonts.length > 0) {
|
|
143
|
+
console.log(`[flight-fonts] Processed ${processedFonts.length} font families`);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
transformIndexHtml(html) {
|
|
147
|
+
if (!enabled || !injectToHtml || processedFonts.length === 0) {
|
|
148
|
+
return html;
|
|
149
|
+
}
|
|
150
|
+
// Inject preload links and font CSS
|
|
151
|
+
const preloadTags = processedFonts
|
|
152
|
+
.flatMap(f => f.preloadLinks)
|
|
153
|
+
.join('\n');
|
|
154
|
+
const fontCSS = processedFonts
|
|
155
|
+
.map(f => f.css)
|
|
156
|
+
.join('\n');
|
|
157
|
+
// Insert before </head>
|
|
158
|
+
const headCloseIndex = html.indexOf('</head>');
|
|
159
|
+
if (headCloseIndex === -1)
|
|
160
|
+
return html;
|
|
161
|
+
const injection = `
|
|
162
|
+
<!-- Flight Font Optimization -->
|
|
163
|
+
${preloadTags}
|
|
164
|
+
<style id="flight-fonts">
|
|
165
|
+
${fontCSS}
|
|
166
|
+
</style>
|
|
167
|
+
`;
|
|
168
|
+
return html.slice(0, headCloseIndex) + injection + html.slice(headCloseIndex);
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Helper Functions
|
|
174
|
+
// ============================================================================
|
|
175
|
+
/**
|
|
176
|
+
* Scan source files for used characters.
|
|
177
|
+
*/
|
|
178
|
+
function scanSourceFiles(projectRoot, scanDirs, extensions, excludePatterns) {
|
|
179
|
+
const chars = new Set();
|
|
180
|
+
// This is a simplified implementation
|
|
181
|
+
// In production, use glob or fast-glob for better performance
|
|
182
|
+
const scanDir = (dir) => {
|
|
183
|
+
try {
|
|
184
|
+
const fs = require('node:fs');
|
|
185
|
+
const path = require('node:path');
|
|
186
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
187
|
+
for (const entry of entries) {
|
|
188
|
+
const fullPath = path.join(dir, entry.name);
|
|
189
|
+
// Skip excluded patterns
|
|
190
|
+
if (excludePatterns.some(pattern => fullPath.includes(pattern))) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (entry.isDirectory()) {
|
|
194
|
+
scanDir(fullPath);
|
|
195
|
+
}
|
|
196
|
+
else if (entry.isFile()) {
|
|
197
|
+
const ext = path.extname(entry.name);
|
|
198
|
+
if (extensions.includes(ext)) {
|
|
199
|
+
try {
|
|
200
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
201
|
+
const fileType = ext.slice(1); // Remove the dot
|
|
202
|
+
const fileChars = extractCharsFromSource(content, fileType);
|
|
203
|
+
for (const char of fileChars) {
|
|
204
|
+
chars.add(char);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Skip files that can't be read
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Directory doesn't exist or can't be read
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
for (const scanDirPath of scanDirs) {
|
|
219
|
+
const fullScanDir = join(projectRoot, scanDirPath);
|
|
220
|
+
scanDir(fullScanDir);
|
|
221
|
+
}
|
|
222
|
+
return chars;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Generate preload link tags for fonts.
|
|
226
|
+
*/
|
|
227
|
+
function generatePreloadLinks(family, weights) {
|
|
228
|
+
const links = [];
|
|
229
|
+
for (const weight of weights) {
|
|
230
|
+
const fontPath = `/_fonts/${family.toLowerCase().replace(/\s/g, '-')}-${weight}.woff2`;
|
|
231
|
+
links.push(`<link rel="preload" href="${fontPath}" as="font" type="font/woff2" crossorigin="anonymous">`);
|
|
232
|
+
}
|
|
233
|
+
return links;
|
|
234
|
+
}
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// Exports
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// Types are exported at declaration above
|
|
239
|
+
export default fontOptimization;
|
|
240
|
+
//# sourceMappingURL=vite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/plugins/vite.ts"],"names":[],"mappings":"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"}
|
package/dist/subset.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flightdev/fonts - Font Subsetting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Zero-dependency font subsetting for optimal Core Web Vitals.
|
|
5
|
+
* Extracts used characters and generates unicode-range CSS.
|
|
6
|
+
*
|
|
7
|
+
* For full font file subsetting, install fontkit as a peer dependency.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Unicode range entry for CSS @font-face
|
|
11
|
+
*/
|
|
12
|
+
export interface UnicodeRange {
|
|
13
|
+
/** Range name for identification */
|
|
14
|
+
name: string;
|
|
15
|
+
/** CSS unicode-range value (e.g., 'U+0000-00FF') */
|
|
16
|
+
range: string;
|
|
17
|
+
/** Character set description */
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Font subset configuration
|
|
22
|
+
*/
|
|
23
|
+
export interface FontSubsetConfig {
|
|
24
|
+
/** Strategy for determining which characters to include */
|
|
25
|
+
strategy: 'used-chars' | 'named-subsets' | 'custom' | 'none';
|
|
26
|
+
/** Named subsets to include (e.g., 'latin', 'latin-ext') */
|
|
27
|
+
subsets?: string[];
|
|
28
|
+
/** Custom characters to include */
|
|
29
|
+
customChars?: string;
|
|
30
|
+
/** Directories to scan for used characters */
|
|
31
|
+
scanDirs?: string[];
|
|
32
|
+
/** File extensions to scan */
|
|
33
|
+
scanExtensions?: string[];
|
|
34
|
+
/** Exclude patterns (glob) */
|
|
35
|
+
excludePatterns?: string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Result of character extraction
|
|
39
|
+
*/
|
|
40
|
+
export interface ExtractedChars {
|
|
41
|
+
/** Set of unique characters found */
|
|
42
|
+
chars: Set<string>;
|
|
43
|
+
/** Unicode code points */
|
|
44
|
+
codePoints: number[];
|
|
45
|
+
/** Generated unicode-range CSS value */
|
|
46
|
+
unicodeRange: string;
|
|
47
|
+
/** Files that were scanned */
|
|
48
|
+
scannedFiles: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Standard unicode ranges matching Google Fonts subsets.
|
|
52
|
+
* These are the same ranges used by major font services.
|
|
53
|
+
*/
|
|
54
|
+
export declare const UNICODE_RANGES: Record<string, UnicodeRange>;
|
|
55
|
+
/**
|
|
56
|
+
* Extract unique characters from text content.
|
|
57
|
+
*
|
|
58
|
+
* @param text - Text to extract characters from
|
|
59
|
+
* @returns Set of unique characters
|
|
60
|
+
*/
|
|
61
|
+
export declare function extractCharsFromText(text: string): Set<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Convert a set of characters to sorted code points.
|
|
64
|
+
*/
|
|
65
|
+
export declare function charsToCodePoints(chars: Set<string>): number[];
|
|
66
|
+
/**
|
|
67
|
+
* Generate CSS unicode-range from code points.
|
|
68
|
+
*
|
|
69
|
+
* Groups consecutive code points into ranges for smaller CSS.
|
|
70
|
+
*
|
|
71
|
+
* @param codePoints - Sorted array of code points
|
|
72
|
+
* @returns CSS unicode-range value
|
|
73
|
+
*/
|
|
74
|
+
export declare function codePointsToUnicodeRange(codePoints: number[]): string;
|
|
75
|
+
/**
|
|
76
|
+
* Get named unicode ranges for specified subsets.
|
|
77
|
+
*
|
|
78
|
+
* @param subsetNames - Array of subset names (e.g., ['latin', 'latin-ext'])
|
|
79
|
+
* @returns Combined unicode-range CSS value
|
|
80
|
+
*/
|
|
81
|
+
export declare function getNamedSubsetRanges(subsetNames: string[]): string;
|
|
82
|
+
/**
|
|
83
|
+
* Detect which subsets are needed based on characters.
|
|
84
|
+
*
|
|
85
|
+
* @param chars - Set of characters to analyze
|
|
86
|
+
* @returns Array of subset names that cover the characters
|
|
87
|
+
*/
|
|
88
|
+
export declare function detectRequiredSubsets(chars: Set<string>): string[];
|
|
89
|
+
/**
|
|
90
|
+
* Extract text content from source code.
|
|
91
|
+
* Used by the Vite plugin to find used characters.
|
|
92
|
+
*
|
|
93
|
+
* @param sourceCode - Source code content
|
|
94
|
+
* @param fileType - File extension (tsx, vue, svelte, etc.)
|
|
95
|
+
* @returns Set of unique characters
|
|
96
|
+
*/
|
|
97
|
+
export declare function extractCharsFromSource(sourceCode: string, fileType: string): Set<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Generate @font-face CSS with unicode-range.
|
|
100
|
+
*
|
|
101
|
+
* @param fontFamily - Font family name
|
|
102
|
+
* @param fontUrl - URL to font file
|
|
103
|
+
* @param options - Font face options
|
|
104
|
+
* @returns CSS @font-face rule
|
|
105
|
+
*/
|
|
106
|
+
export declare function generateFontFaceCSS(fontFamily: string, fontUrl: string, options?: {
|
|
107
|
+
weight?: string | number;
|
|
108
|
+
style?: string;
|
|
109
|
+
display?: string;
|
|
110
|
+
unicodeRange?: string;
|
|
111
|
+
format?: string;
|
|
112
|
+
}): string;
|
|
113
|
+
//# sourceMappingURL=subset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subset.d.ts","sourceRoot":"","sources":["../src/subset.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,2DAA2D;IAC3D,QAAQ,EAAE,YAAY,GAAG,eAAe,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC7D,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,8BAA8B;IAC9B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,8BAA8B;IAC9B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,qCAAqC;IACrC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,0BAA0B;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,8BAA8B;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAC;CAC1B;AAMD;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAuEvD,CAAC;AAMF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAa9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAW9D;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAkCrE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,CAWlE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CA+BlE;AA2BD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACjB,GAAG,CAAC,MAAM,CAAC,CA+Cb;AAMD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACL,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACd,GACP,MAAM,CAuBR"}
|
package/dist/subset.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flightdev/fonts - Font Subsetting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Zero-dependency font subsetting for optimal Core Web Vitals.
|
|
5
|
+
* Extracts used characters and generates unicode-range CSS.
|
|
6
|
+
*
|
|
7
|
+
* For full font file subsetting, install fontkit as a peer dependency.
|
|
8
|
+
*/
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Predefined Unicode Ranges (Google Fonts subsets)
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Standard unicode ranges matching Google Fonts subsets.
|
|
14
|
+
* These are the same ranges used by major font services.
|
|
15
|
+
*/
|
|
16
|
+
export const UNICODE_RANGES = {
|
|
17
|
+
latin: {
|
|
18
|
+
name: 'latin',
|
|
19
|
+
range: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
|
|
20
|
+
description: 'Basic Latin characters',
|
|
21
|
+
},
|
|
22
|
+
'latin-ext': {
|
|
23
|
+
name: 'latin-ext',
|
|
24
|
+
range: 'U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF',
|
|
25
|
+
description: 'Extended Latin characters',
|
|
26
|
+
},
|
|
27
|
+
cyrillic: {
|
|
28
|
+
name: 'cyrillic',
|
|
29
|
+
range: 'U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116',
|
|
30
|
+
description: 'Cyrillic characters',
|
|
31
|
+
},
|
|
32
|
+
'cyrillic-ext': {
|
|
33
|
+
name: 'cyrillic-ext',
|
|
34
|
+
range: 'U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F',
|
|
35
|
+
description: 'Extended Cyrillic characters',
|
|
36
|
+
},
|
|
37
|
+
greek: {
|
|
38
|
+
name: 'greek',
|
|
39
|
+
range: 'U+0370-03FF',
|
|
40
|
+
description: 'Greek characters',
|
|
41
|
+
},
|
|
42
|
+
'greek-ext': {
|
|
43
|
+
name: 'greek-ext',
|
|
44
|
+
range: 'U+1F00-1FFF',
|
|
45
|
+
description: 'Extended Greek characters',
|
|
46
|
+
},
|
|
47
|
+
vietnamese: {
|
|
48
|
+
name: 'vietnamese',
|
|
49
|
+
range: 'U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB',
|
|
50
|
+
description: 'Vietnamese characters',
|
|
51
|
+
},
|
|
52
|
+
arabic: {
|
|
53
|
+
name: 'arabic',
|
|
54
|
+
range: 'U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF',
|
|
55
|
+
description: 'Arabic characters',
|
|
56
|
+
},
|
|
57
|
+
hebrew: {
|
|
58
|
+
name: 'hebrew',
|
|
59
|
+
range: 'U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F',
|
|
60
|
+
description: 'Hebrew characters',
|
|
61
|
+
},
|
|
62
|
+
devanagari: {
|
|
63
|
+
name: 'devanagari',
|
|
64
|
+
range: 'U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB',
|
|
65
|
+
description: 'Devanagari characters',
|
|
66
|
+
},
|
|
67
|
+
thai: {
|
|
68
|
+
name: 'thai',
|
|
69
|
+
range: 'U+0E01-0E5B, U+200C-200D, U+25CC',
|
|
70
|
+
description: 'Thai characters',
|
|
71
|
+
},
|
|
72
|
+
japanese: {
|
|
73
|
+
name: 'japanese',
|
|
74
|
+
range: 'U+3000-303F, U+3040-309F, U+30A0-30FF, U+FF00-FFEF, U+4E00-9FAF',
|
|
75
|
+
description: 'Japanese characters (Hiragana, Katakana, common Kanji)',
|
|
76
|
+
},
|
|
77
|
+
korean: {
|
|
78
|
+
name: 'korean',
|
|
79
|
+
range: 'U+AC00-D7AF, U+1100-11FF, U+3130-318F, U+A960-A97F, U+D7B0-D7FF',
|
|
80
|
+
description: 'Korean Hangul characters',
|
|
81
|
+
},
|
|
82
|
+
'chinese-simplified': {
|
|
83
|
+
name: 'chinese-simplified',
|
|
84
|
+
range: 'U+4E00-9FFF, U+3400-4DBF, U+20000-2A6DF',
|
|
85
|
+
description: 'Simplified Chinese characters',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Character Extraction
|
|
90
|
+
// ============================================================================
|
|
91
|
+
/**
|
|
92
|
+
* Extract unique characters from text content.
|
|
93
|
+
*
|
|
94
|
+
* @param text - Text to extract characters from
|
|
95
|
+
* @returns Set of unique characters
|
|
96
|
+
*/
|
|
97
|
+
export function extractCharsFromText(text) {
|
|
98
|
+
const chars = new Set();
|
|
99
|
+
// Use Array.from to properly handle Unicode surrogate pairs
|
|
100
|
+
for (const char of text) {
|
|
101
|
+
// Skip whitespace and common punctuation for more aggressive subsetting
|
|
102
|
+
const codePoint = char.codePointAt(0);
|
|
103
|
+
if (codePoint !== undefined && codePoint > 0x1F) {
|
|
104
|
+
chars.add(char);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return chars;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Convert a set of characters to sorted code points.
|
|
111
|
+
*/
|
|
112
|
+
export function charsToCodePoints(chars) {
|
|
113
|
+
const codePoints = [];
|
|
114
|
+
for (const char of chars) {
|
|
115
|
+
const cp = char.codePointAt(0);
|
|
116
|
+
if (cp !== undefined) {
|
|
117
|
+
codePoints.push(cp);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return codePoints.sort((a, b) => a - b);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Generate CSS unicode-range from code points.
|
|
124
|
+
*
|
|
125
|
+
* Groups consecutive code points into ranges for smaller CSS.
|
|
126
|
+
*
|
|
127
|
+
* @param codePoints - Sorted array of code points
|
|
128
|
+
* @returns CSS unicode-range value
|
|
129
|
+
*/
|
|
130
|
+
export function codePointsToUnicodeRange(codePoints) {
|
|
131
|
+
if (codePoints.length === 0) {
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
const ranges = [];
|
|
135
|
+
let rangeStart = codePoints[0];
|
|
136
|
+
let rangeEnd = codePoints[0];
|
|
137
|
+
for (let i = 1; i <= codePoints.length; i++) {
|
|
138
|
+
const current = codePoints[i];
|
|
139
|
+
// Check if current is consecutive with previous
|
|
140
|
+
if (current === rangeEnd + 1) {
|
|
141
|
+
rangeEnd = current;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Output the completed range
|
|
145
|
+
if (rangeStart === rangeEnd) {
|
|
146
|
+
ranges.push(`U+${rangeStart.toString(16).toUpperCase().padStart(4, '0')}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
ranges.push(`U+${rangeStart.toString(16).toUpperCase().padStart(4, '0')}-${rangeEnd.toString(16).toUpperCase().padStart(4, '0')}`);
|
|
150
|
+
}
|
|
151
|
+
// Start new range
|
|
152
|
+
if (current !== undefined) {
|
|
153
|
+
rangeStart = current;
|
|
154
|
+
rangeEnd = current;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return ranges.join(', ');
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get named unicode ranges for specified subsets.
|
|
162
|
+
*
|
|
163
|
+
* @param subsetNames - Array of subset names (e.g., ['latin', 'latin-ext'])
|
|
164
|
+
* @returns Combined unicode-range CSS value
|
|
165
|
+
*/
|
|
166
|
+
export function getNamedSubsetRanges(subsetNames) {
|
|
167
|
+
const ranges = [];
|
|
168
|
+
for (const name of subsetNames) {
|
|
169
|
+
const subset = UNICODE_RANGES[name];
|
|
170
|
+
if (subset) {
|
|
171
|
+
ranges.push(subset.range);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return ranges.join(', ');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Detect which subsets are needed based on characters.
|
|
178
|
+
*
|
|
179
|
+
* @param chars - Set of characters to analyze
|
|
180
|
+
* @returns Array of subset names that cover the characters
|
|
181
|
+
*/
|
|
182
|
+
export function detectRequiredSubsets(chars) {
|
|
183
|
+
const needed = new Set();
|
|
184
|
+
const codePoints = charsToCodePoints(chars);
|
|
185
|
+
// Helper to check if code point is in range
|
|
186
|
+
const inRange = (cp, rangeStr) => {
|
|
187
|
+
const ranges = rangeStr.split(',').map(r => r.trim());
|
|
188
|
+
for (const range of ranges) {
|
|
189
|
+
const match = range.match(/U\+([0-9A-F]+)(?:-([0-9A-F]+))?/i);
|
|
190
|
+
if (match) {
|
|
191
|
+
const start = parseInt(match[1], 16);
|
|
192
|
+
const end = match[2] ? parseInt(match[2], 16) : start;
|
|
193
|
+
if (cp >= start && cp <= end) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
};
|
|
200
|
+
// Check each code point against all subsets
|
|
201
|
+
for (const cp of codePoints) {
|
|
202
|
+
for (const [name, subset] of Object.entries(UNICODE_RANGES)) {
|
|
203
|
+
if (inRange(cp, subset.range)) {
|
|
204
|
+
needed.add(name);
|
|
205
|
+
break; // Found a match, move to next code point
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return Array.from(needed);
|
|
210
|
+
}
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// File Scanning (Node.js only, used by Vite plugin)
|
|
213
|
+
// ============================================================================
|
|
214
|
+
/**
|
|
215
|
+
* Patterns to match text content in source files.
|
|
216
|
+
* Excludes code, imports, comments.
|
|
217
|
+
*/
|
|
218
|
+
const TEXT_EXTRACTION_PATTERNS = {
|
|
219
|
+
// JSX/TSX text content
|
|
220
|
+
jsxText: />\s*([^<>{]+?)\s*</g,
|
|
221
|
+
// String literals (double quotes)
|
|
222
|
+
doubleQuoteString: /"([^"\\]|\\.)*"/g,
|
|
223
|
+
// String literals (single quotes)
|
|
224
|
+
singleQuoteString: /'([^'\\]|\\.)*'/g,
|
|
225
|
+
// Template literals (backticks)
|
|
226
|
+
templateLiteral: /`([^`\\]|\\.)*`/g,
|
|
227
|
+
// HTML text content
|
|
228
|
+
htmlText: />\s*([^<>]+?)\s*</g,
|
|
229
|
+
// Vue template text
|
|
230
|
+
vueText: />\s*([^<>{]+?)\s*</g,
|
|
231
|
+
// Svelte text
|
|
232
|
+
svelteText: />\s*([^<>{]+?)\s*</g,
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* Extract text content from source code.
|
|
236
|
+
* Used by the Vite plugin to find used characters.
|
|
237
|
+
*
|
|
238
|
+
* @param sourceCode - Source code content
|
|
239
|
+
* @param fileType - File extension (tsx, vue, svelte, etc.)
|
|
240
|
+
* @returns Set of unique characters
|
|
241
|
+
*/
|
|
242
|
+
export function extractCharsFromSource(sourceCode, fileType) {
|
|
243
|
+
const chars = new Set();
|
|
244
|
+
const patterns = [];
|
|
245
|
+
// Select patterns based on file type
|
|
246
|
+
switch (fileType) {
|
|
247
|
+
case 'tsx':
|
|
248
|
+
case 'jsx':
|
|
249
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.jsxText);
|
|
250
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.doubleQuoteString);
|
|
251
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.singleQuoteString);
|
|
252
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.templateLiteral);
|
|
253
|
+
break;
|
|
254
|
+
case 'vue':
|
|
255
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.vueText);
|
|
256
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.doubleQuoteString);
|
|
257
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.singleQuoteString);
|
|
258
|
+
break;
|
|
259
|
+
case 'svelte':
|
|
260
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.svelteText);
|
|
261
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.doubleQuoteString);
|
|
262
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.singleQuoteString);
|
|
263
|
+
break;
|
|
264
|
+
case 'html':
|
|
265
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.htmlText);
|
|
266
|
+
break;
|
|
267
|
+
default:
|
|
268
|
+
// Generic: extract all string-like content
|
|
269
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.doubleQuoteString);
|
|
270
|
+
patterns.push(TEXT_EXTRACTION_PATTERNS.singleQuoteString);
|
|
271
|
+
}
|
|
272
|
+
// Extract text using all applicable patterns
|
|
273
|
+
for (const pattern of patterns) {
|
|
274
|
+
// Reset lastIndex for global regexes
|
|
275
|
+
pattern.lastIndex = 0;
|
|
276
|
+
let match;
|
|
277
|
+
while ((match = pattern.exec(sourceCode)) !== null) {
|
|
278
|
+
const text = match[1] || match[0];
|
|
279
|
+
for (const char of extractCharsFromText(text)) {
|
|
280
|
+
chars.add(char);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return chars;
|
|
285
|
+
}
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// CSS Generation
|
|
288
|
+
// ============================================================================
|
|
289
|
+
/**
|
|
290
|
+
* Generate @font-face CSS with unicode-range.
|
|
291
|
+
*
|
|
292
|
+
* @param fontFamily - Font family name
|
|
293
|
+
* @param fontUrl - URL to font file
|
|
294
|
+
* @param options - Font face options
|
|
295
|
+
* @returns CSS @font-face rule
|
|
296
|
+
*/
|
|
297
|
+
export function generateFontFaceCSS(fontFamily, fontUrl, options = {}) {
|
|
298
|
+
const { weight = '400', style = 'normal', display = 'swap', unicodeRange, format = 'woff2', } = options;
|
|
299
|
+
let css = `@font-face {\n`;
|
|
300
|
+
css += ` font-family: '${fontFamily}';\n`;
|
|
301
|
+
css += ` font-style: ${style};\n`;
|
|
302
|
+
css += ` font-weight: ${weight};\n`;
|
|
303
|
+
css += ` font-display: ${display};\n`;
|
|
304
|
+
css += ` src: url('${fontUrl}') format('${format}');\n`;
|
|
305
|
+
if (unicodeRange) {
|
|
306
|
+
css += ` unicode-range: ${unicodeRange};\n`;
|
|
307
|
+
}
|
|
308
|
+
css += `}\n`;
|
|
309
|
+
return css;
|
|
310
|
+
}
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Exports
|
|
313
|
+
// ============================================================================
|
|
314
|
+
// Types are exported at declaration above
|
|
315
|
+
//# sourceMappingURL=subset.js.map
|