From b87291f290de4a477887bca93b6e2e7005904d1d Mon Sep 17 00:00:00 2001 From: Jiawei Cao Date: Tue, 28 Jul 2026 10:32:27 +0200 Subject: [PATCH] feat(builder): Rewrite svg.less imports for Theme Designer in libraryLessGenerator The libraryLessGenerator processor already rewrites base.less imports from the UI5 resource path (sap/ui/core/themes//base.less) to the theming-base-content path (Base/baseLib//base.less) used by the Theme Designer. Extend this to also rewrite svg.less imports to Base/icons//svg.less. The Theme Designer resolves imports against the theming-base-content structure. When a customer creates a custom theme, icon overrides are stored under Base/icons//. The parent theme's library.less must import from the same Base/icons/ path so that custom content layered on top takes effect via LESS "last definition wins" semantics. Extract a shared rewriteThemeDesignerImport() static method and drive both rewrites from a config array, so adding future rewrites is a one-liner. --- .../lib/processors/libraryLessGenerator.js | 36 ++++++++---- .../lib/processors/libraryLessGenerator.js | 58 +++++++++++++++++++ 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/packages/builder/lib/processors/libraryLessGenerator.js b/packages/builder/lib/processors/libraryLessGenerator.js index 9903b10ccb4..770ec3274db 100644 --- a/packages/builder/lib/processors/libraryLessGenerator.js +++ b/packages/builder/lib/processors/libraryLessGenerator.js @@ -7,6 +7,7 @@ import Resource from "@ui5/fs/Resource"; const IMPORT_PATTERN = /@import .*"(.*)";/g; const BASE_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/base\.less$/; +const SVG_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/svg\.less$/; const GLOBAL_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/global\.less$/; class LibraryLessGenerator { @@ -53,18 +54,33 @@ class LibraryLessGenerator { } 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"; + static rewriteThemeDesignerImport({pattern, resolvedFilePath, baseDir, baseFolder, fileName, originalFilePath}) { + const match = pattern.exec(resolvedFilePath); + if (match) { + let themeName = match[1]; + if (themeName === "base") { + themeName = "baseTheme"; } - const baseLessPath = LibraryLessGenerator.getPathToRoot(baseDir) + - "Base/baseLib/" + baseLessThemeName + "/base.less"; - return "@import \"" + baseLessPath + "\"; /* ORIGINAL IMPORT PATH: \"" + originalFilePath + "\" */\n"; + const rewrittenPath = LibraryLessGenerator.getPathToRoot(baseDir) + + "Base/" + baseFolder + "/" + themeName + "/" + fileName; + return "@import \"" + rewrittenPath + "\"; /* ORIGINAL IMPORT PATH: \"" + originalFilePath + "\" */\n"; + } + return null; + } + async resolveLessImport(originalFilePath, resolvedFilePath, baseDir) { + // Rewrite Theme Designer imports (base.less, svg.less) + const themeDesignerRewrites = [ + {pattern: BASE_LESS_PATTERN, baseFolder: "baseLib", fileName: "base.less"}, + {pattern: SVG_LESS_PATTERN, baseFolder: "icons", fileName: "svg.less"}, + ]; + for (const rewrite of themeDesignerRewrites) { + const result = LibraryLessGenerator.rewriteThemeDesignerImport({ + ...rewrite, resolvedFilePath, baseDir, originalFilePath + }); + if (result) { + return result; + } } // Rewrite library imports to correct file name diff --git a/packages/builder/test/lib/processors/libraryLessGenerator.js b/packages/builder/test/lib/processors/libraryLessGenerator.js index 1563bef367d..fe8ff867d39 100644 --- a/packages/builder/test/lib/processors/libraryLessGenerator.js +++ b/packages/builder/test/lib/processors/libraryLessGenerator.js @@ -395,6 +395,64 @@ test.serial("LibraryLessGenerator: Rewrite for sap.ui.core base.less (from diffe t.is(loggerStub.error.callCount, 0); }); +test.serial("LibraryLessGenerator: Rewrite for sap.ui.core svg.less", async (t) => { + const {LibraryLessGenerator, loggerStub} = t.context; + + const input = `@import "../base/svg.less";\n` + + `@import "svg.less";`; + const expectedOutput = `${FILE_HEADER}\n\n` + + `@import "../../../../../../Base/icons/baseTheme/svg.less"; ` + + `/* ORIGINAL IMPORT PATH: "../base/svg.less" */\n\n` + + `@import "../../../../../../Base/icons/sap_horizon/svg.less"; ` + + `/* ORIGINAL IMPORT PATH: "svg.less" */\n`; + + const fs = { + readFile: sinon.stub().callsFake((filePath, options, cb) => { + const err = new Error("ENOENT: no such file or directory, open " + filePath); + err.code = "ENOENT"; + cb(err); + }) + }; + + const output = await (new LibraryLessGenerator({fs})).generate({ + filePath: "/resources/sap/ui/core/themes/sap_horizon/library.source.less", + fileContent: input + }); + + t.is(output, expectedOutput); + t.is(fs.readFile.callCount, 0, "fs.readFile should not be called"); + t.is(loggerStub.error.callCount, 0); +}); + +test.serial("LibraryLessGenerator: Rewrite for sap.ui.core svg.less (from different library)", async (t) => { + const {LibraryLessGenerator, loggerStub} = t.context; + + const input = `@import "../../../../sap/ui/core/themes/base/svg.less";\n` + + `@import "../../../../sap/ui/core/themes/sap_fiori_3/svg.less";`; + const expectedOutput = `${FILE_HEADER}\n\n` + + `@import "../../../../../Base/icons/baseTheme/svg.less"; ` + + `/* ORIGINAL IMPORT PATH: "../../../../sap/ui/core/themes/base/svg.less" */\n\n` + + `@import "../../../../../Base/icons/sap_fiori_3/svg.less"; ` + + `/* ORIGINAL IMPORT PATH: "../../../../sap/ui/core/themes/sap_fiori_3/svg.less" */\n`; + + const fs = { + readFile: sinon.stub().callsFake((filePath, options, cb) => { + const err = new Error("ENOENT: no such file or directory, open " + filePath); + err.code = "ENOENT"; + cb(err); + }) + }; + + const output = await (new LibraryLessGenerator({fs})).generate({ + filePath: "/resources/sap/f/themes/sap_fiori_3/library.source.less", + fileContent: input + }); + + t.is(output, expectedOutput); + t.is(fs.readFile.callCount, 0, "fs.readFile should not be called"); + t.is(loggerStub.error.callCount, 0); +}); + test.serial("LibraryLessGenerator: Rewrite for library.source.less", async (t) => { const {LibraryLessGenerator, loggerStub} = t.context;