All files / packages/theme-selector/src blocking-script.ts

100% Statements 3/3
100% Branches 5/5
100% Functions 1/1
100% Lines 3/3

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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108                                                                                                  21x   21x               21x                                                                                                
// SPDX-License-Identifier: MIT
/**
 * Generates a self-contained inline JavaScript string that prevents FOUC
 * (Flash of Unstyled Content) by applying the saved theme before first paint.
 *
 * The returned string is a complete IIFE suitable for injection into a
 * `<script>` tag in the document `<head>`. It has no module dependencies.
 *
 * @example
 * // Astro frontmatter
 * import { generateBlockingScript } from '@lgtm-hq/turbo-theme-selector';
 * const blockingScript = generateBlockingScript();
 *
 * // Astro template
 * <Fragment set:html={`<script>${blockingScript}</script>`} />
 */
 
import { DEFAULT_THEME, VALID_THEMES } from '@lgtm-hq/turbo-themes-core';
import { CSS_LINK_ID, STORAGE_KEY, LEGACY_STORAGE_KEYS } from './constants.js';
 
export interface BlockingScriptOptions {
  /** Valid theme IDs to accept. Defaults to VALID_THEMES from core. */
  validThemes?: readonly string[];
  /** Fallback theme when no preference is stored. Defaults to DEFAULT_THEME from core. */
  defaultTheme?: string;
  /** localStorage key for the active theme. Defaults to STORAGE_KEY from core. */
  storageKey?: string;
  /** Legacy localStorage keys to migrate from. Defaults to LEGACY_STORAGE_KEYS from core. */
  legacyKeys?: readonly string[];
}
 
/**
 * Generates a self-contained inline IIFE that prevents FOUC. It:
 * 1. Migrates legacy storage keys to the current `storageKey`
 * 2. Reads and validates the stored theme against the allowlist
 * 3. Sets `data-theme` and the `theme-{id}` class on `<html>`
 * 4. Sets `window.__INITIAL_THEME__` so `initTheme` can short-circuit
 * 5. Updates the existing CSS `<link>` href when the theme is non-default
 *
 * The body uses readable identifiers; the bundler/gzip handles size. The
 * outer try/catch keeps the page from breaking when localStorage throws
 * (e.g. private browsing).
 */
export function generateBlockingScript(options: BlockingScriptOptions = {}): string {
  const {
    validThemes = VALID_THEMES,
    defaultTheme = DEFAULT_THEME,
    storageKey = STORAGE_KEY,
    legacyKeys = LEGACY_STORAGE_KEYS,
  } = options;
 
  const config = {
    storageKey: JSON.stringify(storageKey),
    defaultTheme: JSON.stringify(defaultTheme),
    validThemes: JSON.stringify(validThemes),
    legacyKeys: JSON.stringify(legacyKeys),
    cssLinkId: JSON.stringify(CSS_LINK_ID),
  };
 
  return `(function () {
  try {
    var storageKey = ${config.storageKey};
    var defaultTheme = ${config.defaultTheme};
    var validThemes = ${config.validThemes};
    var legacyKeys = ${config.legacyKeys};
    var cssLinkId = ${config.cssLinkId};
 
    for (var i = 0; i < legacyKeys.length; i++) {
      var legacyValue = localStorage.getItem(legacyKeys[i]);
      if (legacyValue && !localStorage.getItem(storageKey)) {
        localStorage.setItem(storageKey, legacyValue);
        localStorage.removeItem(legacyKeys[i]);
      }
    }
 
    var theme = localStorage.getItem(storageKey) || defaultTheme;
    if (validThemes.indexOf(theme) === -1) theme = defaultTheme;
 
    var root = document.documentElement;
    root.setAttribute('data-theme', theme);
    var classes = root.classList;
    for (var j = classes.length - 1; j >= 0; j--) {
      if (classes[j].indexOf('theme-') === 0) classes.remove(classes[j]);
    }
    classes.add('theme-' + theme);
    window.__INITIAL_THEME__ = theme;
 
    var rawBase = root.getAttribute('data-baseurl') || '';
    var baseUrl = '';
    if (rawBase && rawBase.indexOf('//') !== 0) {
      try {
        var parsed = new URL(rawBase, location.href);
        if (parsed.origin === location.origin) {
          baseUrl = parsed.pathname.replace(/[/]+$/g, '');
          if (baseUrl === '/') baseUrl = '';
        }
      } catch (urlErr) {
        baseUrl = '';
      }
    }
    var link = document.getElementById(cssLinkId);
    if (link) link.href = baseUrl + '/assets/css/themes/turbo/' + theme + '.css';
  } catch (e) {
    console.warn('Unable to load saved theme:', e);
  }
})();`;
}