Skip to content

Commit

Permalink
[FEATURE] Add 'generateThemeDesignerResources' task
Browse files Browse the repository at this point in the history
Generates resources required for integration with the
SAP Theme Designer.

The task is not enabled by default as it is only relevant for the
OpenUI5 / SAPUI5 libraries.
  • Loading branch information
matz3 committed Jan 14, 2021
1 parent a7e1e5c commit 03241c0
Show file tree
Hide file tree
Showing 12 changed files with 787 additions and 36 deletions.
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ module.exports = {
* @type {import('./lib/tasks/bundlers/generateStandaloneAppBundle')}
*/
generateStandaloneAppBundle: "./lib/tasks/bundlers/generateStandaloneAppBundle",
/**
* @type {import('./lib/tasks/generateThemeDesignerResources')}
*/
generateThemeDesignerResources: "./lib/tasks/generateThemeDesignerResources",
/**
* @type {import('./lib/tasks/bundlers/generateBundle')}
*/
Expand Down
1 change: 1 addition & 0 deletions lib/builder/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask
selectedTasks.executeJsdocSdkTransformation = false;
selectedTasks.generateCachebusterInfo = false;
selectedTasks.generateApiIndex = false;
selectedTasks.generateThemeDesignerResources = false;

// Disable generateResourcesJson due to performance.
// When executed it analyzes each module's AST and therefore
Expand Down
13 changes: 7 additions & 6 deletions lib/processors/libraryLessGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class LibraryLessGenerator {
fileContent
});
}
getPathToRoot(filePath) {
return posixPath.relative(posixPath.dirname(filePath), "/");
static getPathToRoot(baseDir) {
return posixPath.relative(baseDir, "/") + "/";
}
async resolveLessImports({filePath, fileContent}) {
const imports = this.findLessImports(fileContent);
Expand Down Expand Up @@ -60,8 +60,9 @@ class LibraryLessGenerator {
if (baseLessThemeName === "base") {
baseLessThemeName = "baseTheme";
}
const baseLessPath = this.getPathToRoot(resolvedFilePath) +
"/Base/baseLib/" + baseLessThemeName + "/base.less";

const baseLessPath = LibraryLessGenerator.getPathToRoot(baseDir) +
"Base/baseLib/" + baseLessThemeName + "/base.less";
return "@import \"" + baseLessPath + "\"; /* ORIGINAL IMPORT PATH: \"" + originalFilePath + "\" */\n";
}

Expand Down Expand Up @@ -99,7 +100,7 @@ class LibraryLessGenerator {
if (err.code === "ENOENT") {
throw new Error(
`libraryLessGenerator: Unable to resolve import '${originalFilePath}' from '${baseDir}'\n` +
err
err.message
);
} else {
throw err;
Expand All @@ -110,7 +111,7 @@ class LibraryLessGenerator {
filePath: resolvedFilePath,
fileContent: importedFileContent
}) +
`/* END "${originalFilePath}" */\n`;
`\n/* END "${originalFilePath}" */\n`;
}
findLessImports(fileContent) {
const imports = [];
Expand Down
157 changes: 157 additions & 0 deletions lib/tasks/generateThemeDesignerResources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const posixPath = require("path").posix;
const log = require("@ui5/logger").getLogger("builder:tasks:generateThemeDesignerResources");
const libraryLessGenerator = require("../processors/libraryLessGenerator");
const {ReaderCollectionPrioritized, Resource, fsInterface} = require("@ui5/fs");

function generateLibraryDotTheming({namespace, version, hasThemes}) {
const dotTheming = {
sEntity: "Library",
sId: namespace,
sVersion: version
};

if (namespace === "sap/ui/core") {
dotTheming.aFiles = [
"library",
"global", // Additional entry compared to UI5 root .theming
"css_variables",
];
}
if (!hasThemes) {
// Set ignore flag when there are no themes at all
// This is important in case a library used to contain themes that have been removed
// in a later version of the library.
dotTheming.bIgnore = true;
}

return new Resource({
path: `/resources/${namespace}/.theming`,
string: JSON.stringify(dotTheming, null, 2)
});
}

async function generateThemeDotTheming({workspace, combo, themeFolder}) {
const themeName = posixPath.basename(themeFolder);
const libraryMatchPattern = /^\/resources\/(.*)\/themes\/[^/]*$/i;
const libraryMatch = libraryMatchPattern.exec(themeFolder);
let libraryName;
if (libraryMatch) {
libraryName = libraryMatch[1].replace(/\//g, ".");
} else {
throw new Error(`Failed to extract library name from theme folder path: ${themeFolder}`);
}

const dotThemingTargetPath = posixPath.join(themeFolder, ".theming");
if (libraryName === "sap.ui.core") {
// sap.ui.core should always have a .theming file for all themes

if (await workspace.byPath(dotThemingTargetPath)) {
// .theming file present, skip further processing
return;
} else {
throw new Error(`.theming file for theme ${themeName} missing in sap.ui.core library source`);
}
}

let newDotThemingResource;
const coreDotThemingResource = await combo.byPath(`/resources/sap/ui/core/themes/${themeName}/.theming`);

if (coreDotThemingResource) {
// Copy .theming file from core
newDotThemingResource = await coreDotThemingResource.clone();
newDotThemingResource.setPath(dotThemingTargetPath);
} else {
// No core .theming file found for this theme => Generate a .theming file
const dotTheming = {
sEntity: "Theme",
sId: themeName,
sVendor: "SAP"
};

if (themeName !== "base") {
dotTheming.oExtends = "base";
}

newDotThemingResource = new Resource({
path: dotThemingTargetPath,
string: JSON.stringify(dotTheming, null, 2)
});
}
return newDotThemingResource;
}

/**
* Generates resources required for integration with the SAP Theme Designer.
*
* @public
* @alias module:@ui5/builder.tasks.generateThemeDesignerResources
* @param {object} parameters Parameters
* @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files
* @param {object} parameters.options Options
* @param {string} parameters.options.projectName Project name
* @param {string} parameters.options.version Project version
* @param {string} [parameters.options.namespace] If the project is of type <code>library</code>, provide its namespace.
* Omit for type <code>theme-library</code>
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
module.exports = async function({workspace, dependencies, options: {projectName, version, namespace}}) {
// Skip sap.ui.documentation since it is not intended to be available in SAP Theme Designer to create custom themes
if (namespace === "sap/ui/documentation") {
return;
}

const librarySourceLessResources = await workspace.byGlob("/resources/**/themes/*/library.source.less");

const hasThemes = librarySourceLessResources.length > 0;

// library .theming file
// Only for type "library". Type "theme-library" does not provide a namespace
// Also needs to be created in case a library does not have any themes (see bIgnore flag)
if (namespace) {
log.verbose(`Generating .theming for namespace ${namespace}`);
const libraryDotThemingResource = generateLibraryDotTheming({
namespace,
version,
hasThemes
});
await workspace.write(libraryDotThemingResource);
}

if (!hasThemes) {
// Skip further processing as there are no themes
return;
}

const combo = new ReaderCollectionPrioritized({
name: `generateThemeDesignerResources - prioritize workspace over dependencies: ${projectName}`,
readers: [workspace, dependencies]
});

// theme .theming files
const themeDotThemingFiles = await Promise.all(
librarySourceLessResources.map((librarySourceLess) => {
const themeFolder = posixPath.dirname(librarySourceLess.getPath());
log.verbose(`Generating .theming for theme ${themeFolder}`);
return generateThemeDotTheming({
workspace, combo, themeFolder
});
})
);
await Promise.all(
themeDotThemingFiles.map(async (resource) => {
if (resource) {
await workspace.write(resource);
}
})
);

// library.less files
const libraryLessResources = await libraryLessGenerator({
resources: librarySourceLessResources,
fs: fsInterface(combo),
});
await Promise.all(
libraryLessResources.map((resource) => workspace.write(resource))
);
};
1 change: 1 addition & 0 deletions lib/tasks/taskRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const taskInfos = {
generateFlexChangesBundle: {path: "./bundlers/generateFlexChangesBundle"},
generateComponentPreload: {path: "./bundlers/generateComponentPreload"},
generateResourcesJson: {path: "./generateResourcesJson"},
generateThemeDesignerResources: {path: "./generateThemeDesignerResources"},
generateStandaloneAppBundle: {path: "./bundlers/generateStandaloneAppBundle"},
generateBundle: {path: "./bundlers/generateBundle"},
generateLibraryPreload: {path: "./bundlers/generateLibraryPreload"},
Expand Down
12 changes: 12 additions & 0 deletions lib/types/library/LibraryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ class LibraryBuilder extends AbstractBuilder {
});
});

this.addTask("generateThemeDesignerResources", async () => {
return getTask("generateThemeDesignerResources").task({
workspace: resourceCollections.workspace,
dependencies: resourceCollections.dependencies,
options: {
projectName: project.metadata.name,
version: project.version,
namespace: project.metadata.namespace
}
});
});

this.addTask("createDebugFiles", async () => {
return getTask("createDebugFiles").task({
workspace: resourceCollections.workspace,
Expand Down
11 changes: 11 additions & 0 deletions lib/types/themeLibrary/ThemeLibraryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ class ThemeLibraryBuilder extends AbstractBuilder {
});
});

this.addTask("generateThemeDesignerResources", async () => {
return getTask("generateThemeDesignerResources").task({
workspace: resourceCollections.workspace,
dependencies: resourceCollections.dependencies,
options: {
projectName: project.metadata.name,
version: project.version
}
});
});

this.addTask("generateResourcesJson", () => {
return getTask("generateResourcesJson").task({
workspace: resourceCollections.workspace,
Expand Down
1 change: 1 addition & 0 deletions test/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ test("index.js exports all expected modules", (t) => {
t.truthy(index.tasks.generateLibraryPreload, "Module exported");
t.truthy(index.tasks.generateManifestBundle, "Module exported");
t.truthy(index.tasks.generateStandaloneAppBundle, "Module exported");
t.truthy(index.tasks.generateThemeDesignerResources, "Module exported");
t.truthy(index.tasks.generateBundle, "Module exported");
t.truthy(index.tasks.generateCachebusterInfo, "Module exported");
t.truthy(index.tasks.buildThemes, "Module exported");
Expand Down

0 comments on commit 03241c0

Please sign in to comment.