| // 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(); | |
| } |