bs_base-8.x-1.x-dev/build-icons.js
build-icons.js
/**
* build-icons.js
*
* This file is part of the bs_base parent theme build process and is used to
* generate the icon font assets for the theme, including calculating the hash
* of the generated font icon file.
*
* It is designed to be executed locally using Node.js as part of the theme's
* build and deployment process.
*
* Important: This script is not intended for browser execution. It should only
* be run in a local development environment as a build tool. It processes font
* icons and updates related assets (like SCSS and YAML) with the necessary
* font hash.
*
* This file is part of the open-source bs_base Drupal theme, available on
* Drupal.org: https://www.drupal.org/project/bs_base
*
* Please note that this file is not meant for direct interaction from a web
* browser. It should only be executed within a Node.js environment during
* theme build and is not exposed as part of the web-facing assets.
*/
const { generateFonts } = require('@twbs/fantasticon');
const { createHash } = require('crypto');
const { readFile, writeFile } = require('fs/promises');
const path = require('path');
const config = require('./.fantasticonrc.js');
async function calculateFileHash(filePath) {
const fileBuffer = await readFile(filePath);
const hashSum = createHash('sha256');
hashSum.update(fileBuffer);
// Short 8-char hash.
return hashSum.digest('hex').slice(0, 8);
}
async function replaceFontHashInScss(filePath, token, fontHash) {
let scssContent = await readFile(filePath, 'utf8');
scssContent = scssContent.replace(token, fontHash);
await writeFile(filePath, scssContent);
}
async function updateThemeInfo(fontFileName, fontHash) {
// Open theme info file.
const currentDir = path.basename(process.cwd());
const infoYmlPath = path.resolve(`${currentDir}.info.yml`);
let infoContent = await readFile(infoYmlPath, 'utf8');
// Remove ./ from the start of font name if needed.
const fontFileCleanPath = fontFileName.replace(/^\.\//, '');
// Check if preload-fonts section in theme info contains the font.
const fontRegex = new RegExp(`${fontFileCleanPath}(\\?v=[a-zA-Z0-9]+)?`);
if (fontRegex.test(infoContent)) {
console.log('Detected icons font preloading in theme info.yml, updating icons hash.');
// Replace hash with a help of a regular expression.
const fontRegex = /fonts\/icons\.woff2(\?v=[a-zA-Z0-9]+)?/g;
const infoNewContent = infoContent.replace(fontRegex, `${fontFileCleanPath}?v=${fontHash}`);
if (infoContent !== infoNewContent) {
await writeFile(infoYmlPath, infoNewContent);
}
}
}
async function buildIcons() {
console.log('Building icons font...');
// We will build a font first so we are sure that font exists (first build),
// we can calculate new font hash and then detect icons font change.
await generateFonts(config);
// Use pathOptions if defined, fallback to default location.
// NOTE Hardcoded to woff2 type for now.
const fontFileRelativePath = config.pathOptions?.woff2 ?? `${config.outputDir}/${config.name}.woff2`;
const fontFilePath = path.resolve(fontFileRelativePath);
const fontHash = await calculateFileHash(fontFilePath);
const scssFileRelativePath = config.pathOptions?.scss ?? `./sass/variables/_icons.scss`;
const scssFilePath = path.resolve(scssFileRelativePath);
await replaceFontHashInScss(scssFilePath, '__ICON_FONT_HASH__', fontHash);
await updateThemeInfo(fontFileRelativePath, fontHash);
console.log("Icons build complete.\n");
}
buildIcons();
