All files / scripts/shared theme-init.ts

90% Statements 9/10
100% Branches 0/0
100% Functions 2/2
90% Lines 9/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74                                        22x 22x                       22x                         22x   22x 22x 22x                       22x                   22x  
/**
 * @module shared/theme-init
 * @description Centralised loader for the inline anti-flash theme-init
 * bootstrap.  The bootstrap is kept in `js/theme-init.js` as the canonical
 * source; all HTML generators (article template, news-indexes template,
 * any future root-page generator) inline its minified body via this
 * helper so every page ships identical bytes.
 *
 * The bootstrap must be inlined (not loaded via `<script src>`) to avoid
 * a FOUC window while the external request resolves.  Inlining is the
 * only way to set `data-theme` synchronously before first paint.
 *
 * @author Hack23 AB
 * @license Apache-2.0
 */
 
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
 
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
 
/**
 * Strip comments + compress whitespace to keep the inline payload small.
 * Removes:
 *   - JSDoc `/** ... * /` blocks
 *   - Single-line `// ...` comments
 *   - `/* ... * /` inline block comments
 *   - Leading indentation and multiple blank lines
 * The resulting IIFE remains functionally identical.
 */
function minifyBootstrap(src: string): string {
  return src
    // strip JSDoc / block comments
    .replace(/\/\*[\s\S]*?\*\//g, '')
    // strip line comments (but only those at line start to avoid breaking URL-like fragments)
    .replace(/^\s*\/\/.*$/gm, '')
    // collapse whitespace between tokens
    .replace(/\s+/g, ' ')
    // tighten around punctuation
    .replace(/\s*([;{}(),=!<>+\-*/])\s*/g, '$1')
    .trim();
}
 
function loadBootstrap(): string {
  try {
    // From scripts/shared/theme-init.ts → repo root is two parents up.
    const path = join(__dirname, '..', '..', 'js', 'theme-init.js');
    const raw = readFileSync(path, 'utf-8');
    return minifyBootstrap(raw);
  } catch {
    // Defensive fallback: hand-written copy identical to js/theme-init.js.
    // Kept in sync manually; `tests/theme-init.test.ts` asserts they match.
    return "(function(){var key='riksdagsmonitor-theme';var t=null;try{t=localStorage.getItem(key);}catch(e){}if(t!=='dark'&&t!=='light'){if(t!==null){try{localStorage.removeItem(key);}catch(e){}}t=(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';}document.documentElement.setAttribute('data-theme',t);}());";
  }
}
 
/**
 * Minified, single-line IIFE sourced from `js/theme-init.js`.
 * Safe to embed inside a `<script>…</script>` tag directly.
 */
export const THEME_INIT_INLINE: string = loadBootstrap();
 
/**
 * Ready-to-inject `<script>` tag containing the inline theme bootstrap.
 * Use in template literals, e.g.:
 *
 * ```ts
 * html += THEME_INIT_SCRIPT_TAG;
 * ```
 */
export const THEME_INIT_SCRIPT_TAG: string = `<script>${THEME_INIT_INLINE}</script>`;