blob: 31ae1419d53293b2a0afcfe51ca00f90193c6fe2 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import {parseArgs} from 'node:util';
import {optimize} from 'svgo';
import {writeIfChanged} from './ninja/write-if-changed.js';
/**
* Optimizes SVG markup using SVGO with default preset and inlineStyles disabled.
* @param {string} svgContent
* @param {string} [filePath]
* @returns {string}
*/
export function optimizeSvg(svgContent, filePath) {
const result = optimize(svgContent, {
path: filePath,
plugins: [
{
name: 'preset-default',
params: {
overrides: {
inlineStyles: false,
},
},
},
],
});
return result.data;
}
/**
* Parses the response file contents into svgs and images lists.
* @param {string} fileContent
* @returns {{svgs: string[], images: string[]}}
*/
export function parseResponseFile(fileContent) {
const tokens = fileContent.split(/\s+/).filter(Boolean);
let currentSection = null;
const svgs = [];
const images = [];
for (const token of tokens) {
if (token === '--svgs') {
currentSection = 'svgs';
} else if (token === '--images') {
currentSection = 'images';
} else if (currentSection === 'svgs') {
svgs.push(token);
} else if (currentSection === 'images') {
images.push(token);
}
}
return {svgs, images};
}
/**
* Generates the JavaScript source content for Images.js.
* @param {string[]} imageFiles
* @param {string[]} svgFiles
* @returns {string}
*/
export function generateImagesJsContent(imageFiles, svgFiles) {
function generateCSSVarDefinition(fileName) {
const varName = fileName.replace(path.extname(fileName), '');
return `style.setProperty('--image-file-${varName}', 'url(\"' + new URL('./${
fileName}', import.meta.url).toString() + '\")');`;
}
const allFiles = [...imageFiles, ...svgFiles];
return `const sheet = new CSSStyleSheet();
sheet.replaceSync(':root {}');
const style = sheet.cssRules[0].style;
${allFiles.map(generateCSSVarDefinition).join('\n')}
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
`;
}
async function main() {
const {values} = parseArgs({
options: {
'target-gen-dir': {
type: 'string',
},
'file-list': {
type: 'string',
},
},
});
const targetGenDir = values['target-gen-dir'];
const fileListPath = values['file-list'];
if (!targetGenDir || !fileListPath) {
throw new Error('Missing required arguments: --target-gen-dir and --file-list must be provided');
}
const fileListContent = await fs.readFile(fileListPath, 'utf8');
const {svgs, images} = parseResponseFile(fileListContent);
await Promise.all(svgs.map(async svgPath => {
const basename = path.basename(svgPath);
const content = await fs.readFile(svgPath, 'utf8');
const optimized = optimizeSvg(content, svgPath);
await writeIfChanged(path.join(targetGenDir, basename), optimized);
}));
const imageBasenames = images.map(img => path.basename(img));
const svgBasenames = svgs.map(s => path.basename(s));
const imagesJsContent = generateImagesJsContent(imageBasenames, svgBasenames);
await writeIfChanged(path.join(targetGenDir, 'Images.js'), imagesJsContent);
}
if (import.meta.main) {
await main();
}