@uniweb/theming 0.1.7 → 0.1.9
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/package.json +1 -1
- package/src/css-generator.js +45 -5
- package/src/index.js +4 -1
- package/src/processor.js +4 -3
package/package.json
CHANGED
package/src/css-generator.js
CHANGED
|
@@ -277,6 +277,27 @@ function filterGoogleFontsUrl(url, usedFamilies) {
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Prefix a root-relative asset URL with the site's base path.
|
|
282
|
+
*
|
|
283
|
+
* Self-hosted font `src` values are authored root-relative (`/fonts/x.woff2`)
|
|
284
|
+
* because that is what they are relative to the site. Under a subdirectory
|
|
285
|
+
* deployment (GitHub Pages project sites, `/docs/`, ...) the served root moves,
|
|
286
|
+
* so the emitted URL has to carry the base. A relative URL is not an option:
|
|
287
|
+
* the rules live in an inline `<style>`, so they would resolve against the
|
|
288
|
+
* *document* URL, which differs per route.
|
|
289
|
+
*
|
|
290
|
+
* Left untouched: absolute URLs (http:, //, data:), already-based URLs, and
|
|
291
|
+
* document-relative ones (`./`, `../`) whose author clearly meant them.
|
|
292
|
+
*/
|
|
293
|
+
function withBase(url, base) {
|
|
294
|
+
if (!url || !base || base === '/') return url
|
|
295
|
+
if (!url.startsWith('/') || url.startsWith('//')) return url
|
|
296
|
+
const prefix = base.endsWith('/') ? base.slice(0, -1) : base
|
|
297
|
+
if (url === prefix || url.startsWith(prefix + '/')) return url // already based
|
|
298
|
+
return prefix + url
|
|
299
|
+
}
|
|
300
|
+
|
|
280
301
|
/**
|
|
281
302
|
* Check whether a URL points to Google Fonts.
|
|
282
303
|
*/
|
|
@@ -293,9 +314,10 @@ function isGoogleFontsUrl(url) {
|
|
|
293
314
|
*
|
|
294
315
|
* @param {Object} fontRoles - Resolved font roles (name → { value, applyTo, apply, load })
|
|
295
316
|
* @param {Object} fonts - Font loading config (import[] / faces[])
|
|
317
|
+
* @param {string} [base='/'] - Site base path, prefixed onto root-relative face srcs
|
|
296
318
|
* @returns {{ css: string, links: string }} css for <style>, links for <head>
|
|
297
319
|
*/
|
|
298
|
-
function generateFontCSS(fontRoles = {}, fonts = {}) {
|
|
320
|
+
function generateFontCSS(fontRoles = {}, fonts = {}, base = '/') {
|
|
299
321
|
const cssLines = []
|
|
300
322
|
const linkTags = []
|
|
301
323
|
|
|
@@ -311,10 +333,11 @@ function generateFontCSS(fontRoles = {}, fonts = {}) {
|
|
|
311
333
|
if (!usedFamilies.has(face.family.toLowerCase())) continue
|
|
312
334
|
|
|
313
335
|
const format = face.format || (face.src.endsWith('.woff2') ? 'woff2' : 'woff')
|
|
336
|
+
const src = withBase(face.src, base)
|
|
314
337
|
cssLines.push(
|
|
315
338
|
`@font-face {`,
|
|
316
339
|
` font-family: ${face.family};`,
|
|
317
|
-
` src: url('${
|
|
340
|
+
` src: url('${src}') format('${format}');`,
|
|
318
341
|
` font-weight: ${face.weight || 400};`,
|
|
319
342
|
` font-style: ${face.style || 'normal'};`,
|
|
320
343
|
` font-display: swap;`,
|
|
@@ -323,7 +346,7 @@ function generateFontCSS(fontRoles = {}, fonts = {}) {
|
|
|
323
346
|
|
|
324
347
|
const mimeType = PRELOAD_TYPE[format]
|
|
325
348
|
if (mimeType) {
|
|
326
|
-
linkTags.push(`<link rel="preload" href="${
|
|
349
|
+
linkTags.push(`<link rel="preload" href="${src}" as="font" type="${mimeType}" crossorigin>`)
|
|
327
350
|
}
|
|
328
351
|
}
|
|
329
352
|
if (cssLines.length > 0) {
|
|
@@ -333,6 +356,7 @@ function generateFontCSS(fontRoles = {}, fonts = {}) {
|
|
|
333
356
|
|
|
334
357
|
// --- External imports → <link> tags (filtered to used families) ---
|
|
335
358
|
const googleUrls = []
|
|
359
|
+
const otherOrigins = new Set()
|
|
336
360
|
|
|
337
361
|
if (fonts.import && Array.isArray(fonts.import)) {
|
|
338
362
|
for (const entry of fonts.import) {
|
|
@@ -343,10 +367,23 @@ function generateFontCSS(fontRoles = {}, fonts = {}) {
|
|
|
343
367
|
if (filtered) googleUrls.push(filtered)
|
|
344
368
|
} else {
|
|
345
369
|
linkTags.push(`<link rel="stylesheet" href="${entry.url}">`)
|
|
370
|
+
try {
|
|
371
|
+
otherOrigins.add(new URL(entry.url).origin)
|
|
372
|
+
} catch {
|
|
373
|
+
// relative or malformed — no origin to preconnect to
|
|
374
|
+
}
|
|
346
375
|
}
|
|
347
376
|
}
|
|
348
377
|
}
|
|
349
378
|
|
|
379
|
+
// Preconnect to non-Google font hosts so the DNS/TLS handshake overlaps the
|
|
380
|
+
// stylesheet request instead of following it. Google's own preconnects are
|
|
381
|
+
// emitted below, alongside the merged css2 link. Emitting every font <link>
|
|
382
|
+
// from one place keeps consumers from having to re-derive any of them.
|
|
383
|
+
for (const origin of otherOrigins) {
|
|
384
|
+
linkTags.unshift(`<link rel="preconnect" href="${origin}">`)
|
|
385
|
+
}
|
|
386
|
+
|
|
350
387
|
// Merge Google Fonts into a single <link> with preconnect
|
|
351
388
|
if (googleUrls.length > 0) {
|
|
352
389
|
// Collect all family params from all URLs into one
|
|
@@ -482,9 +519,12 @@ export function generateFoundationVars(vars = {}) {
|
|
|
482
519
|
* @param {Object} config.fonts - Font configuration
|
|
483
520
|
* @param {Object} config.appearance - Appearance settings (dark mode, etc.)
|
|
484
521
|
* @param {Object} config.foundationVars - Foundation-specific variables
|
|
522
|
+
* @param {Object} [options] - Generation options
|
|
523
|
+
* @param {string} [options.base='/'] - Site base path for subdirectory deployments
|
|
485
524
|
* @returns {{ css: string, links: string }} CSS string and font link tags
|
|
486
525
|
*/
|
|
487
|
-
export function generateThemeCSS(config = {}) {
|
|
526
|
+
export function generateThemeCSS(config = {}, options = {}) {
|
|
527
|
+
const { base = '/' } = options
|
|
488
528
|
const {
|
|
489
529
|
colors = DEFAULT_COLORS,
|
|
490
530
|
contexts = {},
|
|
@@ -501,7 +541,7 @@ export function generateThemeCSS(config = {}) {
|
|
|
501
541
|
// resolved by the processor; `fonts` carries only import[] / faces[] here.
|
|
502
542
|
// Font-typed vars are already split out of `foundationVars`, so the generic
|
|
503
543
|
// foundation-var emission below never double-writes a --font-<name>.
|
|
504
|
-
const fontResult = generateFontCSS(fontRoles, fonts)
|
|
544
|
+
const fontResult = generateFontCSS(fontRoles, fonts, base)
|
|
505
545
|
if (fontResult.css) {
|
|
506
546
|
sections.push('/* Typography */\n' + fontResult.css)
|
|
507
547
|
}
|
package/src/index.js
CHANGED
|
@@ -34,6 +34,7 @@ export {
|
|
|
34
34
|
processTheme,
|
|
35
35
|
extractFoundationVars,
|
|
36
36
|
foundationHasVars,
|
|
37
|
+
isFontVar,
|
|
37
38
|
} from './processor.js'
|
|
38
39
|
|
|
39
40
|
// Value normalization
|
|
@@ -52,11 +53,13 @@ import { generateThemeCSS } from './css-generator.js'
|
|
|
52
53
|
* @param {Object} themeYml - Raw theme.yml content
|
|
53
54
|
* @param {Object} options - Processing options
|
|
54
55
|
* @param {Object} options.foundationVars - Foundation variables
|
|
56
|
+
* @param {string} [options.base='/'] - Site base path, prefixed onto root-relative
|
|
57
|
+
* font `src` URLs so self-hosted fonts resolve under subdirectory deployments
|
|
55
58
|
* @returns {{ css: string, links: string, config: Object, errors: string[], warnings: string[] }}
|
|
56
59
|
*/
|
|
57
60
|
export function buildTheme(themeYml = {}, options = {}) {
|
|
58
61
|
const { config, errors, warnings } = processTheme(themeYml, options)
|
|
59
|
-
const { css, links } = generateThemeCSS(config)
|
|
62
|
+
const { css, links } = generateThemeCSS(config, { base: options.base })
|
|
60
63
|
|
|
61
64
|
return {
|
|
62
65
|
css,
|
package/src/processor.js
CHANGED
|
@@ -83,10 +83,11 @@ const NON_FAMILY_FONT_VARS = new Set([
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* A foundation var is a themeable typeface (font role) when it declares
|
|
86
|
-
* `type: 'font'
|
|
87
|
-
*
|
|
86
|
+
* `type: 'font'`, or — the inferred case — its name is `font-*` and not a known
|
|
87
|
+
* CSS longhand. Exported so the build can persist the inferred `type: 'font'`
|
|
88
|
+
* into schema.json (the runtime relies on the same predicate).
|
|
88
89
|
*/
|
|
89
|
-
function isFontVar(name, config) {
|
|
90
|
+
export function isFontVar(name, config) {
|
|
90
91
|
const type = typeof config === 'object' ? config?.type : undefined
|
|
91
92
|
if (type === 'font') return true
|
|
92
93
|
return name.startsWith('font-') && !NON_FAMILY_FONT_VARS.has(name)
|