All files / packages/design-system-2/scripts/buildCss buildThemeLayer.js

0% Statements 0/59
0% Branches 0/39
0% Functions 0/6
0% Lines 0/54

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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207                                                                                                                                                                                                                                                                                                                                                                                                                             
/**
 * Theme-layer CSS build.
 *
 * Dev outputs (build/css/theme-layer/) consumed by Storybook, invoked via --dev-theme-layer:
 *
 *   buildThemeLayerBase()              Light DOM base + per-theme files
 *   buildThemeLayerBase({ lbc: true }) LBC per-theme files
 *
 *     slds2.theme-layer.base.css                   all themes/base.css concatenated
 *     themes/slds2[.lbc].theme.{theme}.css         per-theme files
 *
 * Dist outputs (dist/css/theme-layer/) called by --production orchestrator (264+):
 *
 *   buildThemeLayerBase({ forDist: true })
 *     modular/slds2.theme-layer.base.css           base building block
 *     modular/slds2[.lbc].theme.{theme}.css        per-theme building block
 *
 *   buildThemeLayerDist({ lbc })
 *     bundled/slds2[.lbc].theme-layer.{theme}.css  monolith: tokens + component CSS + theme layer
 */
 
import path from 'node:path';
import fs from 'fs-extra';
import postcss from 'postcss';
import postcssAtImport from 'postcss-import';
import postcssNesting from 'postcss-nested';
import postcssFor from 'postcss-for';
import postcssEach from 'postcss-each';
import postcssRemoveComments from 'postcss-discard-comments';
import { args, THEMES, deriveFilename } from './args.js';
import { root, themeLayerOutputDir } from './config.js';
import postCssDeprecatedSelector from '../plugins/postcss-deprecated-selector.js';
import postCssScopeTokens, { THEME_LAYER_PRESET } from '../plugins/postcss-scope-tokens.js';
import postCssFigletComment from '../plugins/postcss-figlet-comment.js';
import postCssHeaderComment from '../plugins/postcss-header-comment.js';
import postCssLbcTheme from '../plugins/postcss-lbc-theme.js';
import postCssMergeLayers from '../plugins/postcss-merge-layers.js';
import {
  getThemeBaseFileList,
  getThemeOnlyFileList,
  getThemeLayerDistFileList,
  getUtilitiesFiles,
  getModularThemeFileList,
} from './file-lists-theme-layer.js';
import { sortImports } from './sort-imports.js';
 
// ─── Plugin pipelines ────────────────────────────────────────────────────────
 
/** Shared plugin prefix (import resolution, syntax transforms). */
const baseTransforms = [
  postcssAtImport({ cache: false }),
  postcssRemoveComments(),
  postcssEach(),
  postcssFor(),
  postcssNesting(),
];
 
/**
 * Rewrites `@layer component` and `@layer shared` to `@layer deprecated`.
 *
 * Used by all theme-layer builds (intermediate and dist) where the cascade is
 * `deprecated, theme, customization`. Legacy hook files hardcode `@layer component`
 * in their source; an unregistered layer would float above `customization`, breaking
 * priority. This plugin remaps them before merge-layers consolidates.
 */
const remapLegacyLayers = () => ({
  postcssPlugin: 'postcss-remap-legacy-layers',
  AtRule: {
    layer(atRule) {
      if (!atRule.nodes) return; // skip order declarations
      const name = atRule.params.trim();
      if (name === 'component' || name === 'shared') {
        atRule.params = 'deprecated';
      }
    },
  },
});
remapLegacyLayers.postcss = true;
 
/** Plugins for the base output (slds2.theme-layer.base.css): includes @layer order declaration. */
const basePlugins = [
  ...baseTransforms,
  postCssScopeTokens({ ...THEME_LAYER_PRESET }),
  postCssDeprecatedSelector(),
  remapLegacyLayers(),
  postCssMergeLayers(),
];
 
/** Plugins for per-theme/combined outputs: no @layer order declaration. */
const themePlugins = [
  ...baseTransforms,
  postCssScopeTokens({ injectLayerOrder: false, ...THEME_LAYER_PRESET }),
  postCssDeprecatedSelector(),
  remapLegacyLayers(),
  postCssMergeLayers(),
];
 
// Dist monolith outputs reuse basePlugins (same @layer order + transforms).
 
// ─── Shared helpers ──────────────────────────────────────────────────────────
 
const processCss = async (css, outputPath, pluginSet) => {
  console.info(`Compiling ${outputPath}...`);
  const result = await postcss(pluginSet).process(css, { from: '' });
  await fs.outputFile(outputPath, result.css);
};
 
// ─── Build functions ─────────────────────────────────────────────────────────
 
/**
 * Build theme-layer base + per-theme building blocks.
 *
 * @param {{ lbc?: boolean, forDist?: boolean }} options
 *   lbc:     true → produce LBC variants (defaults to CLI --lbc flag)
 *   forDist: true → dist/css/theme-layer/modular/ with header comment (called by --production)
 *            false → build/css/theme-layer/ for Storybook dev (called by --theme-layer-base)
 */
export const buildThemeLayerBase = async ({ lbc = false, forDist = false } = {}) => {
  const system = args['--system'];
  const lbcInfix = lbc ? '.lbc' : '';
 
  const outputDir = forDist ? path.resolve(root, 'dist/css/theme-layer/modular') : themeLayerOutputDir();
 
  const basePluginSet = forDist ? [...basePlugins, postCssHeaderComment()] : basePlugins;
  const perThemeBase = forDist ? [...themePlugins, postCssHeaderComment()] : themePlugins;
  const perThemeSet = lbc ? [...perThemeBase, postCssLbcTheme()] : perThemeBase;
 
  // Base building block: reset + utilities + all themes/base.css.
  // Only produced for Light DOM; the LBC transform has nothing to act on here.
  // This is the only output that declares the @layer order.
  if (!lbc) {
    const baseFiles = [...getUtilitiesFiles(), ...getThemeBaseFileList()];
    if (baseFiles.length > 0) {
      const baseOutputPath = path.resolve(outputDir, `${system}.theme-layer.base.css`);
      const css = sortImports(baseFiles);
      await processCss(css, baseOutputPath, basePluginSet);
    }
  }
 
  // Per-theme building blocks: foundation tokens + legacy hooks + themes/{theme}.css
  for (const themeName of THEMES) {
    const themeFiles = [...getModularThemeFileList(themeName), ...getThemeOnlyFileList(themeName)];
    if (themeFiles.length === 0) continue;
 
    // Dev: build/css/theme-layer/themes/  (Storybook staticDirs expects /themes/ subdir)
    // Dist: dist/css/theme-layer/modular/  (flat, mirrors css/modular/ convention)
    const themesDir = forDist ? outputDir : path.resolve(outputDir, 'themes');
    const themeOutputPath = path.resolve(themesDir, `${system}${lbcInfix}.theme.${themeName}.css`);
    const css = sortImports(themeFiles, { theme: themeName });
    await processCss(css, themeOutputPath, perThemeSet);
  }
};
 
export const buildThemeLayerDist = async ({ lbc = false } = {}) => {
  const system = args['--system'];
  const distBundledPlugins = lbc
    ? [...basePlugins, postCssLbcTheme(), postCssHeaderComment(), postCssFigletComment()]
    : [...basePlugins, postCssHeaderComment(), postCssFigletComment()];
 
  for (const themeName of THEMES) {
    const files = getThemeLayerDistFileList(themeName);
    if (files.length === 0) continue;
 
    const outputDir = path.resolve(root, 'dist/css/theme-layer/bundled');
    const filename = deriveFilename({ system, theme: themeName, lbc, themeLayer: true });
    const outputPath = path.resolve(outputDir, `${filename}.css`);
 
    const css = sortImports(files, { theme: themeName });
    await processCss(css, outputPath, distBundledPlugins);
  }
};
 
/**
 * Build modular theme-only CSS files.
 *
 * Produces one file per theme containing only token declarations and legacy hooks
 * (no config tokens, no component structure CSS). Uses the legacy cascade layer
 * mapping so these files pair with slds2.base.css.
 *
 * @param {{ forDist?: boolean }} options
 *   forDist: true → dist/css/modular/  (consumer delivery, called by --production)
 *            false → build/css/         (Storybook dev, called by --dev-standard)
 */
export const buildModularThemes = async ({ forDist = false } = {}) => {
  const system = args['--system'];
 
  const modularThemePlugins = [
    postcssAtImport({ cache: false }),
    postcssRemoveComments(),
    postcssNesting(),
    postCssScopeTokens({ injectLayerOrder: false }),
    postCssDeprecatedSelector(),
    postCssHeaderComment(),
  ];
 
  for (const themeName of THEMES) {
    const files = getModularThemeFileList(themeName);
    if (files.length === 0) continue;
 
    const outputDir = path.resolve(root, forDist ? 'dist/css/modular' : 'build/css');
    const outputPath = path.resolve(outputDir, `${system}.theme.${themeName}.css`);
 
    const css = sortImports(files);
    await processCss(css, outputPath, modularThemePlugins);
  }
};