UNPKG

@ui5/builder

Version:
185 lines (168 loc) 6.45 kB
import {getLogger} from "@ui5/logger"; const log = getLogger("builder:processors:libraryLessGenerator"); import {promisify} from "node:util"; import posixPath from "node:path/posix"; import Resource from "@ui5/fs/Resource"; const IMPORT_PATTERN = /@import .*"(.*)";/g; const BASE_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/base\.less$/; const GLOBAL_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/global\.less$/; class LibraryLessGenerator { constructor({fs}) { const readFile = promisify(fs.readFile); this.readFile = async (filePath) => readFile(filePath, {encoding: "utf8"}); } async generate({filePath, fileContent}) { return `/* NOTE: This file was generated as an optimized version of ` + `"library.source.less" for the Theme Designer. */\n\n` + (await this.resolveLessImports({ filePath, fileContent })); } static getPathToRoot(baseDir) { return posixPath.relative(baseDir, "/") + "/"; } async resolveLessImports({filePath, fileContent}) { const imports = this.findLessImports(fileContent); if (!imports.length) { // Skip processing when no imports are found return fileContent; } const replacements = await Promise.all(imports.map(async (importMatch) => { const baseDir = posixPath.dirname(filePath); const resolvedFilePath = posixPath.resolve(baseDir, importMatch.path); importMatch.content = await this.resolveLessImport(importMatch.path, resolvedFilePath, baseDir); return importMatch; })); // Apply replacements in reverse order to not modify the relevant indices const array = Array.from(fileContent); for (let i = replacements.length - 1; i >= 0; i--) { const replacement = replacements[i]; if (!replacement.content) { continue; } array.splice( /* index */ replacement.matchStart, /* count */ replacement.matchLength, /* insert */ replacement.content ); } return array.join(""); } async resolveLessImport(originalFilePath, resolvedFilePath, baseDir) { // Rewrite base.less imports const baseLessMatch = BASE_LESS_PATTERN.exec(resolvedFilePath); if (baseLessMatch) { let baseLessThemeName = baseLessMatch[1]; if (baseLessThemeName === "base") { baseLessThemeName = "baseTheme"; } const baseLessPath = LibraryLessGenerator.getPathToRoot(baseDir) + "Base/baseLib/" + baseLessThemeName + "/base.less"; return "@import \"" + baseLessPath + "\"; /* ORIGINAL IMPORT PATH: \"" + originalFilePath + "\" */\n"; } // Rewrite library imports to correct file name if (posixPath.basename(resolvedFilePath) === "library.source.less") { return `@import "${originalFilePath.replace(/library\.source\.less$/, "library.less")}";`; } // Excluding global.less within sap.ui.core // It must be imported by the Theme Designer (also see declaration in sap/ui/core/.theming) if (GLOBAL_LESS_PATTERN.test(resolvedFilePath)) { return null; } /* * Throw error in case of files which are not in the same directory as the current file because * inlining them would break relative URLs. * A possible solution would be to rewrite relative URLs when inlining the content. * * Keeping the import will cause errors since only "library.less" and "global.less" are * configured to be available to the Theme Designer (.theming generated in generateThemeDesignerResources). */ const relativeFilePath = posixPath.relative(baseDir, resolvedFilePath); if (relativeFilePath.includes(posixPath.sep)) { throw new Error( `Could not inline import '${resolvedFilePath}' outside of theme directory '${baseDir}'. ` + `Stylesheets must be located in the theme directory (no sub-directories).` ); } let importedFileContent; try { importedFileContent = await this.readFile(resolvedFilePath); } catch (err) { if (err.code === "ENOENT") { throw new Error( `libraryLessGenerator: Unable to resolve import '${originalFilePath}' from '${baseDir}'\n` + err.message ); } else { throw err; } } return `/* START "${originalFilePath}" */\n` + (await this.resolveLessImports({ filePath: resolvedFilePath, fileContent: importedFileContent })) + `\n/* END "${originalFilePath}" */\n`; } findLessImports(fileContent) { const imports = []; let match; while ((match = IMPORT_PATTERN.exec(fileContent)) !== null) { imports.push({ path: match[1], matchStart: match.index, matchLength: match[0].length }); } return imports; } } /** * @public * @module @ui5/builder/processors/libraryLessGenerator */ /** * Creates a "library.less" file for the SAP Theme Designer based on a "library.source.less" file. * * <ul> * <li>Bundles all *.less file of the theme by replacing the import with the corresponding file content</li> * <li>Imports to "base.less" are adopted so that they point to the "BaseLib" that is available within * the Theme Designer infrastructure</li> * <li>Imports to "global.less" are kept as they should not be bundled</li> * <li>Imports to "library.source.less" are adopted to "library.less"</li> * </ul> * * @public * @function default * @static * * @param {object} parameters Parameters * @param {@ui5/fs/Resource[]} parameters.resources List of <code>library.source.less</code> * resources * @param {fs|module:@ui5/fs/fsInterface} parameters.fs Node fs or custom * [fs interface]{@link module:@ui5/fs/fsInterface} * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with library.less resources */ async function createLibraryLess({resources, fs}) { const generator = new LibraryLessGenerator({fs}); return Promise.all(resources.map(async (librarySourceLessResource) => { const filePath = librarySourceLessResource.getPath(); const fileContent = await librarySourceLessResource.getString(); log.verbose(`Generating library.less file based on ${filePath}`); const libraryLessFileContent = await generator.generate({filePath, fileContent}); const libraryLessFilePath = posixPath.join(posixPath.dirname(filePath), "library.less"); return new Resource({ path: libraryLessFilePath, string: libraryLessFileContent }); })); } export default createLibraryLess; let myLibraryLessGenerator; // Export class for testing only /* istanbul ignore else */ if (process.env.NODE_ENV === "test") { myLibraryLessGenerator = LibraryLessGenerator; } export const _LibraryLessGenerator = myLibraryLessGenerator;