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 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | 105x 105x 105x 105x 105x 105x 105x 105x 315x 107x 107x 107x 107x 107x 105x 2x 2x 1x 36x 108x 108x 13x 13x 95x 190x 95x 95x 36x 33x 10x 33x 33x 6x 27x 27x 32x 32x 2x 30x 32x 32x 25x 25x 25x 5x 20x 20x 20x 27x 27x 27x 27x 27x 105x | /**
* PostCSS Plugin: Inject Styling Hooks
* Injects SLDS styling hooks CSS custom properties into the stylesheet
*
* This plugin works by:
* 1. Converting the entire CSS to a string
* 2. Injecting styling hooks after the first :root block
* 3. Re-parsing the processed CSS
*/
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import path from 'node:path';
import fs from 'fs-extra';
import postcss from 'postcss';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const require = createRequire(import.meta.url);
const { wrapInLayer } = require('../helpers/styling-hooks.js');
export { wrapInLayer };
// Path to design-tokens package in monorepo root node_modules
const packageRoot = path.resolve(__dirname, '../..');
const monorepoRoot = path.resolve(packageRoot, '../..');
const LAYER_ORDER = '@layer deprecated, defaults, shared, theme, component;';
// Map of CSS files to their @layer name
const designTokensLayerMap = [
{ file: 'lightning-blue.reference.tokens.css', layer: 'defaults' },
{ file: 'lightning-blue.global.tokens.css', layer: 'defaults' },
{ file: 'lightning-blue.shared.tokens.css', layer: 'shared' },
];
// List of CSS file names (derived from the layer map)
const sdsStylingHooksSourceFiles = designTokensLayerMap.map((entry) => entry.file);
/**
* Resolve the styling hooks base path
* Checks monorepo root node_modules first, then local package dist
* @returns {string} Resolved base path
*/
export function resolveStylingHooksPath() {
// Check monorepo root node_modules (published package uses themes/slds directly)
const rootNodeModulesPath = path.resolve(monorepoRoot, 'node_modules/@salesforce-ux/design-tokens/themes/lightning-blue');
Iif (fs.existsSync(rootNodeModulesPath)) {
return rootNodeModulesPath;
}
// Fallback to sibling package dist (for local development)
const siblingPkgPath = path.resolve(packageRoot, '../design-tokens/dist/themes/lightning-blue');
Eif (fs.existsSync(siblingPkgPath)) {
return siblingPkgPath;
}
// Final fallback to local node_modules
return path.resolve(packageRoot, 'node_modules/@salesforce-ux/design-tokens/themes/lightning-blue');
}
// Default styling hooks base path
let sdsStylingHooksBasePath = resolveStylingHooksPath();
/**
* Set the styling hooks base path (for testing)
* @param {string} basePath - Base path to styling hooks
*/
export function setStylingHooksBasePath(basePath) {
sdsStylingHooksBasePath = basePath;
}
/**
* Get the current styling hooks base path
* @returns {string} Current base path
*/
export function getStylingHooksBasePath() {
return sdsStylingHooksBasePath;
}
/**
* Get the list of styling hook files to read
* @returns {string[]} Array of file names
*/
export function getStylingHookFiles() {
return sdsStylingHooksSourceFiles;
}
/**
* Reads design-tokens CSS files, wraps each in its @layer, and prepends the layer order.
* @param {string} basePath - Base path to design-tokens (optional)
* @param {string[]} hookFiles - Array of token file names (optional)
* @returns {string} Combined CSS with @layer wrappers and order declaration
*/
export function readStylingHooksContent(
basePath = sdsStylingHooksBasePath,
hookFiles = sdsStylingHooksSourceFiles,
) {
const layerBlocks = hookFiles
.map((file) => {
const filePath = path.join(basePath, file);
if (!fs.existsSync(filePath)) {
console.warn(`Design tokens file not found: ${filePath}`);
return '';
}
const raw = fs
.readFileSync(filePath, 'utf8')
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove all comments (generation + inline)
.trim();
const entry = designTokensLayerMap.find((e) => e.file === file);
Iif (!entry) {
console.warn(`No @layer mapping for token file: ${file}`);
return raw;
}
return wrapInLayer(raw, entry.layer);
})
.filter(Boolean);
if (layerBlocks.length === 0) return '';
return `${LAYER_ORDER}\n${layerBlocks.join('\n')}`;
}
/**
* Custom CSS minification that preserves @layer and :where()
* @param {string} css - CSS content
* @returns {string} Minified CSS
*/
export function minifyCssCustom(css) {
return css
.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '') // Remove comments
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
.replace(/\s*([{}:;,])\s*/g, '$1') // Remove spaces around braces, colons, semicolons, and commas
.replace(/;\}/g, '}') // Remove semicolons before closing braces
.trim();
}
/**
* Find the position after the first :root block
* @param {string} css - CSS content
* @returns {{ found: boolean, position: number }}
*/
export function findRootBlockEnd(css) {
const rootIndex = css.indexOf(':root');
if (rootIndex === -1) {
return { found: false, position: 0 };
}
const rootEnd = css.indexOf('}', rootIndex) + 1;
return { found: true, position: rootEnd };
}
/**
* Inject styling hooks into CSS content with original scope preserved
* @param {string} css - Original CSS content
* @param {string} basePath - Base path to styling hooks
* @param {string[]} hookFiles - Array of hook file names
* @param {boolean} shouldMinify - Whether to minify the injected hooks
* @returns {string} CSS with styling hooks injected
*/
export function injectStylingHooks(
css,
basePath = sdsStylingHooksBasePath,
hookFiles = sdsStylingHooksSourceFiles,
shouldMinify = false,
) {
const hooks = readStylingHooksContent(basePath, hookFiles);
if (!hooks) {
return css;
}
const hooksToInject = shouldMinify ? minifyCssCustom(hooks) : hooks;
// Find the end of the :root (WCAG) block to inject hooks
const { found, position: rootEnd } = findRootBlockEnd(css);
if (found) {
// Trim the existing CSS and the remaining CSS
const rootCss = css.slice(0, rootEnd).trim();
const remainingCss = css.slice(rootEnd).trim();
// Ensure exactly one newline between sections
return `${rootCss}\n${hooksToInject}\n${remainingCss}`;
}
// If no :root found, prepend hooks
return `${hooksToInject}\n${css}`;
}
/**
* PostCSS plugin factory
* @param {Object} options - Plugin options
* @param {string} options.basePath - Override styling hooks base path
* @param {boolean} options.minify - Whether to minify the hooks (default: false)
* @returns {Object} PostCSS plugin
*/
export default function postcssInjectStylingHooks(options = {}) {
const basePath = options.basePath || sdsStylingHooksBasePath;
const shouldMinify = options.minify || false;
return {
postcssPlugin: 'postcss-inject-styling-hooks',
Once(root) {
// Convert the AST to a string
const css = root.toString();
// Inject styling hooks
const processedCss = injectStylingHooks(css, basePath, sdsStylingHooksSourceFiles, shouldMinify);
// Remove all existing nodes from the AST
root.removeAll();
// Parse the processed CSS and append it to the root
const parsedRoot = postcss.parse(processedCss);
root.append(parsedRoot.nodes);
},
};
}
postcssInjectStylingHooks.postcss = true;
|