vanillaforge 1.9.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/package.json +1 -1
- package/src/components/base-component.js +26 -16
- package/src/framework.js +1 -1
- package/src/plugins/fonts/font-manifests.js +15 -10
- package/src/plugins/fonts/fonts-plugin.js +17 -11
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.9.1] - 2026-06-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
#### Font data now loads lazily (`src/plugins/fonts/`)
|
|
15
|
+
- `font-manifests.js` converted static imports of `inter.js` and `jetbrains-mono.js`
|
|
16
|
+
(~244 KB of base64 font data) to dynamic `import()` inside each `dataUri()` function.
|
|
17
|
+
The data is only fetched the first time `fontsPlugin` is actually installed.
|
|
18
|
+
Previously, every `import { createApp } from 'vanillaforge'` — including projects that
|
|
19
|
+
never use fonts — paid the 244 KB parsing cost up front, which broke the demo and
|
|
20
|
+
generated apps in VS Code's built-in browser (module-load timeout / stuck loading spinner).
|
|
21
|
+
- `FontsService._src()`, `_buildCSS()`, and `_reinject()` are now `async` to handle the
|
|
22
|
+
lazy data loading. A `_ready` Promise is exposed so callers can `await svc._ready`
|
|
23
|
+
after installation.
|
|
24
|
+
- `ESLint` config updated: `AbortController` added to browser globals.
|
|
25
|
+
|
|
26
|
+
#### `autoLoadCSS` no longer hangs in file:// environments (`src/components/base-component.js`)
|
|
27
|
+
- Added `AbortController` with a 500 ms timeout to the `fetch HEAD` call in `autoLoadCSS`.
|
|
28
|
+
In environments where `fetch` never rejects (VS Code webview, `file://` protocol),
|
|
29
|
+
the old code left `init()` suspended indefinitely. The abort ensures the method always
|
|
30
|
+
returns within half a second even when the network is unavailable.
|
|
31
|
+
|
|
32
|
+
#### Demo CSS always loads in VS Code's built-in browser (`index.html`)
|
|
33
|
+
- Added explicit `<link>` tags for `home-component.css` and `not-found-component.css`.
|
|
34
|
+
All visual styling for the demo lives in those files; relying solely on `autoLoadCSS`
|
|
35
|
+
meant a blank/unstyled page whenever `fetch` was unavailable.
|
|
36
|
+
|
|
10
37
|
## [1.9.0] - 2026-06-23
|
|
11
38
|
|
|
12
39
|
This release completes the public API surface and prepares the package for npm
|
package/package.json
CHANGED
|
@@ -549,31 +549,41 @@ export class BaseComponent {
|
|
|
549
549
|
*/
|
|
550
550
|
async autoLoadCSS() {
|
|
551
551
|
if (!this.name || typeof window === 'undefined') return;
|
|
552
|
-
|
|
553
|
-
// Generate CSS filename from component name
|
|
552
|
+
|
|
554
553
|
const cssFileName = this.name.toLowerCase()
|
|
555
|
-
.replace(/component$/, '')
|
|
556
|
-
.replace(/[^a-z0-9]/g, '-')
|
|
557
|
-
.replace(/-+/g, '-')
|
|
558
|
-
.replace(/^-|-$/g, '');
|
|
559
|
-
|
|
560
|
-
// Add 'component' suffix back for CSS file naming convention
|
|
554
|
+
.replace(/component$/, '')
|
|
555
|
+
.replace(/[^a-z0-9]/g, '-')
|
|
556
|
+
.replace(/-+/g, '-')
|
|
557
|
+
.replace(/^-|-$/g, '');
|
|
558
|
+
|
|
561
559
|
const fullCssName = cssFileName ? `${cssFileName}-component` : this.name.toLowerCase();
|
|
562
|
-
|
|
560
|
+
|
|
563
561
|
const cssPaths = [
|
|
564
|
-
`styles/components/${fullCssName}.css`,
|
|
565
|
-
`src/styles/components/${fullCssName}.css`,
|
|
566
|
-
`styles/components/${this.name.toLowerCase()}.css`,
|
|
567
|
-
`src/styles/components/${this.name.toLowerCase()}.css
|
|
562
|
+
`styles/components/${fullCssName}.css`,
|
|
563
|
+
`src/styles/components/${fullCssName}.css`,
|
|
564
|
+
`styles/components/${this.name.toLowerCase()}.css`,
|
|
565
|
+
`src/styles/components/${this.name.toLowerCase()}.css`,
|
|
568
566
|
];
|
|
569
|
-
|
|
567
|
+
|
|
570
568
|
for (const cssPath of cssPaths) {
|
|
571
569
|
try {
|
|
570
|
+
// HEAD check avoids a visible 404 in the browser console when
|
|
571
|
+
// the file simply doesn't exist (common in generated projects).
|
|
572
|
+
// AbortController prevents the fetch from hanging indefinitely
|
|
573
|
+
// in file:// environments (e.g. VS Code's built-in browser).
|
|
574
|
+
const controller = new AbortController();
|
|
575
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
576
|
+
let resp;
|
|
577
|
+
try {
|
|
578
|
+
resp = await fetch(cssPath, { method: 'HEAD', signal: controller.signal });
|
|
579
|
+
} finally {
|
|
580
|
+
clearTimeout(timeout);
|
|
581
|
+
}
|
|
582
|
+
if (!resp.ok) continue;
|
|
572
583
|
await this.loadCSS(cssPath);
|
|
573
584
|
this.logger.debug(`Auto-loaded CSS: ${cssPath}`);
|
|
574
|
-
break;
|
|
585
|
+
break;
|
|
575
586
|
} catch (_e) {
|
|
576
|
-
// Continue to next path - this is expected behavior
|
|
577
587
|
continue;
|
|
578
588
|
}
|
|
579
589
|
}
|
package/src/framework.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-in font manifests for VanillaForge fontsPlugin.
|
|
3
3
|
*
|
|
4
|
-
* Each manifest describes a font family.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Each manifest describes a font family. Font data is loaded lazily via
|
|
5
|
+
* dynamic import — it is only fetched when fontsPlugin is actually installed
|
|
6
|
+
* with a given family. This prevents ~244 KB of base64 font data from being
|
|
7
|
+
* eagerly parsed by every app that imports framework.js.
|
|
7
8
|
*
|
|
8
9
|
* Bundled fonts are Latin-subset, variable-weight woff2 files sourced from
|
|
9
10
|
* the @fontsource-variable packages (open-source, MIT-licensed subsets).
|
|
@@ -15,13 +16,11 @@
|
|
|
15
16
|
* @property {boolean} variable - True when a single file covers all weights.
|
|
16
17
|
* @property {number[]} weights - [min, max] for variable fonts; list for static.
|
|
17
18
|
* @property {string[]} styles - Available styles ('normal', 'italic').
|
|
18
|
-
* @property {function(string): string|null} dataUri -
|
|
19
|
-
*
|
|
19
|
+
* @property {function(string): Promise<string|null>} dataUri - Async function returning the
|
|
20
|
+
* bundled data URI for a style, or null (triggers URL path
|
|
21
|
+
* lookup instead).
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
|
-
import { INTER_NORMAL, INTER_ITALIC } from './files/inter.js';
|
|
23
|
-
import { JETBRAINS_MONO_NORMAL, JETBRAINS_MONO_ITALIC } from './files/jetbrains-mono.js';
|
|
24
|
-
|
|
25
24
|
/** @type {Map<string, FontManifest>} */
|
|
26
25
|
export const FONT_MANIFESTS = new Map([
|
|
27
26
|
[
|
|
@@ -33,7 +32,10 @@ export const FONT_MANIFESTS = new Map([
|
|
|
33
32
|
variable: true,
|
|
34
33
|
weights: [100, 900],
|
|
35
34
|
styles: ['normal', 'italic'],
|
|
36
|
-
dataUri: (style) =>
|
|
35
|
+
dataUri: async (style) => {
|
|
36
|
+
const { INTER_NORMAL, INTER_ITALIC } = await import('./files/inter.js');
|
|
37
|
+
return style === 'italic' ? INTER_ITALIC : INTER_NORMAL;
|
|
38
|
+
},
|
|
37
39
|
filename: () => 'Inter-Variable.woff2',
|
|
38
40
|
},
|
|
39
41
|
],
|
|
@@ -46,7 +48,10 @@ export const FONT_MANIFESTS = new Map([
|
|
|
46
48
|
variable: true,
|
|
47
49
|
weights: [100, 800],
|
|
48
50
|
styles: ['normal', 'italic'],
|
|
49
|
-
dataUri: (style) =>
|
|
51
|
+
dataUri: async (style) => {
|
|
52
|
+
const { JETBRAINS_MONO_NORMAL, JETBRAINS_MONO_ITALIC } = await import('./files/jetbrains-mono.js');
|
|
53
|
+
return style === 'italic' ? JETBRAINS_MONO_ITALIC : JETBRAINS_MONO_NORMAL;
|
|
54
|
+
},
|
|
50
55
|
filename: () => 'JetBrainsMono-Variable.woff2',
|
|
51
56
|
},
|
|
52
57
|
],
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* Installing JetBrains Mono sets --vf-font-mono to 'JetBrains Mono', monospace, ...
|
|
39
39
|
*
|
|
40
40
|
* Bundled fonts are Latin-subset, variable-weight woff2 (open-source, MIT-licensed).
|
|
41
|
+
* Font data is loaded lazily — it is only fetched when the plugin is actually installed.
|
|
41
42
|
*/
|
|
42
43
|
|
|
43
44
|
import { FONT_MANIFESTS } from './font-manifests.js';
|
|
@@ -64,10 +65,13 @@ export class FontsService {
|
|
|
64
65
|
this._manifests = new Map(FONT_MANIFESTS);
|
|
65
66
|
this._loadedFamilies = [];
|
|
66
67
|
this._styleEl = null;
|
|
68
|
+
// Resolves once @font-face CSS has been injected (after any lazy font data loads).
|
|
69
|
+
this._ready = Promise.resolve();
|
|
67
70
|
|
|
68
71
|
const requested = options.families || [];
|
|
69
72
|
if (requested.length > 0) {
|
|
70
73
|
this._load(requested);
|
|
74
|
+
this._ready = this._reinject();
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -92,7 +96,7 @@ export class FontsService {
|
|
|
92
96
|
addFamily(name, manifest) {
|
|
93
97
|
this._manifests.set(name, manifest);
|
|
94
98
|
this._loadOne(name, manifest, null, null);
|
|
95
|
-
this._reinject();
|
|
99
|
+
this._ready = this._reinject();
|
|
96
100
|
return this;
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -115,8 +119,7 @@ export class FontsService {
|
|
|
115
119
|
|
|
116
120
|
this._loadOne(name, manifest, weightFilter, styleFilter);
|
|
117
121
|
}
|
|
118
|
-
|
|
119
|
-
this._reinject();
|
|
122
|
+
// _reinject() is called by the constructor after _load()
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
_loadOne(name, manifest, weightFilter, styleFilter) {
|
|
@@ -125,10 +128,12 @@ export class FontsService {
|
|
|
125
128
|
this._loadedFamilies.push({ name, manifest, weights, styles, cssFamily: manifest.cssFamily });
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
_reinject() {
|
|
131
|
+
async _reinject() {
|
|
129
132
|
if (typeof document === 'undefined') return;
|
|
130
133
|
|
|
131
134
|
if (!this._styleEl) {
|
|
135
|
+
// Create / reuse the style element synchronously before any await so that
|
|
136
|
+
// the element exists in the DOM as soon as _reinject() is called.
|
|
132
137
|
this._styleEl = document.getElementById('vf-fonts');
|
|
133
138
|
if (!this._styleEl) {
|
|
134
139
|
this._styleEl = document.createElement('style');
|
|
@@ -137,23 +142,24 @@ export class FontsService {
|
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
|
|
140
|
-
|
|
145
|
+
// Build CSS — may trigger lazy font data imports (async).
|
|
146
|
+
this._styleEl.textContent = await this._buildCSS();
|
|
141
147
|
}
|
|
142
148
|
|
|
143
|
-
_buildCSS() {
|
|
149
|
+
async _buildCSS() {
|
|
144
150
|
const blocks = [];
|
|
145
151
|
|
|
146
152
|
for (const { manifest, weights, styles, cssFamily } of this._loadedFamilies) {
|
|
147
153
|
if (manifest.variable) {
|
|
148
154
|
const [minW, maxW] = [Math.min(...weights), Math.max(...weights)];
|
|
149
155
|
for (const style of styles) {
|
|
150
|
-
const src = this._src(manifest, null, style);
|
|
156
|
+
const src = await this._src(manifest, null, style);
|
|
151
157
|
blocks.push(this._block(cssFamily, style, `${minW} ${maxW}`, src));
|
|
152
158
|
}
|
|
153
159
|
} else {
|
|
154
160
|
for (const weight of weights) {
|
|
155
161
|
for (const style of styles) {
|
|
156
|
-
const src = this._src(manifest, weight, style);
|
|
162
|
+
const src = await this._src(manifest, weight, style);
|
|
157
163
|
blocks.push(this._block(cssFamily, style, String(weight), src));
|
|
158
164
|
}
|
|
159
165
|
}
|
|
@@ -169,9 +175,9 @@ export class FontsService {
|
|
|
169
175
|
* - a path was explicitly provided, OR
|
|
170
176
|
* - the manifest has no dataUri function (custom family).
|
|
171
177
|
*/
|
|
172
|
-
_src(manifest, weight, style) {
|
|
178
|
+
async _src(manifest, weight, style) {
|
|
173
179
|
if (!this._path && typeof manifest.dataUri === 'function') {
|
|
174
|
-
const uri = manifest.dataUri(style);
|
|
180
|
+
const uri = await manifest.dataUri(style);
|
|
175
181
|
if (uri) return `url('${uri}') format('woff2')`;
|
|
176
182
|
}
|
|
177
183
|
|
|
@@ -231,7 +237,7 @@ export class FontsService {
|
|
|
231
237
|
* display {string} - CSS font-display value (default 'swap').
|
|
232
238
|
*
|
|
233
239
|
* Side effects on install:
|
|
234
|
-
* - Injects <style id="vf-fonts"> with @font-face declarations.
|
|
240
|
+
* - Injects <style id="vf-fonts"> with @font-face declarations (async, after dynamic import).
|
|
235
241
|
* - Registers FontsService under 'fonts' (app.get('fonts')).
|
|
236
242
|
* - If themePlugin is installed, updates --vf-font-sans / --vf-font-mono.
|
|
237
243
|
*/
|