diff --git a/lib/builder/BuildContext.js b/lib/builder/BuildContext.js index 0f61271a7..e46e788ed 100644 --- a/lib/builder/BuildContext.js +++ b/lib/builder/BuildContext.js @@ -7,18 +7,23 @@ const ProjectBuildContext = require("./ProjectBuildContext"); * @memberof module:@ui5/builder.builder */ class BuildContext { - constructor({rootProject}) { + constructor({rootProject, options = {}}) { if (!rootProject) { throw new Error(`Missing parameter 'rootProject'`); } this.rootProject = rootProject; this.projectBuildContexts = []; + this.options = options; } getRootProject() { return this.rootProject; } + getOption(key) { + return this.options[key]; + } + createProjectContext({project, resources}) { const projectBuildContext = new ProjectBuildContext({ buildContext: this, diff --git a/lib/builder/ProjectBuildContext.js b/lib/builder/ProjectBuildContext.js index a9dbf9c54..f82f12fce 100644 --- a/lib/builder/ProjectBuildContext.js +++ b/lib/builder/ProjectBuildContext.js @@ -37,6 +37,10 @@ class ProjectBuildContext { return this._project === this._buildContext.getRootProject(); } + getOption(key) { + return this._buildContext.getOption(key); + } + registerCleanupTask(callback) { this.queues.cleanup.push(callback); } diff --git a/lib/builder/builder.js b/lib/builder/builder.js index cb81de584..786114500 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -225,6 +225,7 @@ module.exports = { * @param {boolean} [parameters.dev=false] * Decides whether a development build should be activated (skips non-essential and time-intensive tasks) * @param {boolean} [parameters.selfContained=false] Flag to activate self contained build + * @param {boolean} [parameters.cssVariables=false] Flag to activate CSS variables generation * @param {boolean} [parameters.jsdoc=false] Flag to activate JSDoc build * @param {Array.} [parameters.includedTasks=[]] List of tasks to be included * @param {Array.} [parameters.excludedTasks=[]] List of tasks to be excluded. @@ -235,7 +236,7 @@ module.exports = { async build({ tree, destPath, cleanDest = false, buildDependencies = false, includedDependencies = [], excludedDependencies = [], - dev = false, selfContained = false, jsdoc = false, + dev = false, selfContained = false, cssVariables = false, jsdoc = false, includedTasks = [], excludedTasks = [], devExcludeProject = [] }) { const startTime = process.hrtime(); @@ -250,7 +251,12 @@ module.exports = { virBasePath: "/" }); - const buildContext = new BuildContext({rootProject: tree}); + const buildContext = new BuildContext({ + rootProject: tree, + options: { + cssVariables: cssVariables + } + }); const cleanupSigHooks = registerCleanupSigHooks(buildContext); const projects = {}; // Unique project index to prevent building the same project multiple times diff --git a/lib/tasks/TaskUtil.js b/lib/tasks/TaskUtil.js index 0420d1b39..f61691aad 100644 --- a/lib/tasks/TaskUtil.js +++ b/lib/tasks/TaskUtil.js @@ -108,6 +108,18 @@ class TaskUtil { return this._projectBuildContext.isRootProject(); } + /** + * Retrieves a build option defined by it's keykey is stored, undefined is returned. + * + * @param {string} key The option key + * @returns {object|undefined} The build option (or undefined) + * @public + */ + getBuildOption(key) { + return this._projectBuildContext.getOption(key); + } + /** * Register a function that must be executed once the build is finished. This can be used to, for example, * clean up files temporarily created on the file system. If the callback returns a Promise, it will be waited for. diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index 90149a18f..4c98f9918 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -178,7 +178,8 @@ class LibraryBuilder extends AbstractBuilder { projectName: project.metadata.name, librariesPattern: !taskUtil.isRootProject() ? "/resources/**/(*.library|library.js)" : undefined, themesPattern: !taskUtil.isRootProject() ? "/resources/sap/ui/core/themes/*" : undefined, - inputPattern + inputPattern, + cssVariables: taskUtil.getBuildOption("cssVariables") } }); }); diff --git a/lib/types/themeLibrary/ThemeLibraryBuilder.js b/lib/types/themeLibrary/ThemeLibraryBuilder.js index 2f49912af..a5a760e6f 100644 --- a/lib/types/themeLibrary/ThemeLibraryBuilder.js +++ b/lib/types/themeLibrary/ThemeLibraryBuilder.js @@ -31,7 +31,8 @@ class ThemeLibraryBuilder extends AbstractBuilder { projectName: project.metadata.name, librariesPattern: !taskUtil.isRootProject() ? "/resources/**/(*.library|library.js)" : undefined, themesPattern: !taskUtil.isRootProject() ? "/resources/sap/ui/core/themes/*" : undefined, - inputPattern: "/resources/**/themes/*/library.source.less" + inputPattern: "/resources/**/themes/*/library.source.less", + cssVariables: taskUtil.getBuildOption("cssVariables") } }); }); diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/Button.less b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/Button.less new file mode 100644 index 000000000..ca968183f --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/Button.less @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.css b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.css new file mode 100644 index 000000000..6232d9e35 --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.css @@ -0,0 +1,3 @@ +:root{--someColor:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.source.less b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.source.less new file mode 100644 index 000000000..5a6ce08e9 --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.source.less @@ -0,0 +1,5 @@ +@someColor: #000000; + +:root { +--someColor: @someColor; +} diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-RTL.css b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-RTL.css new file mode 100644 index 000000000..5009ca50e --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-RTL.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-parameters.json b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-parameters.json new file mode 100644 index 000000000..a190cda03 --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-parameters.json @@ -0,0 +1 @@ +{"someColor":"#000"} \ No newline at end of file diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.css b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.css new file mode 100644 index 000000000..5009ca50e --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.source.less b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.source.less new file mode 100644 index 000000000..834de919e --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.source.less @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css new file mode 100644 index 000000000..7db086289 --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css @@ -0,0 +1 @@ +.someClass{color:var(--someColor)} \ No newline at end of file diff --git a/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton.css b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton.css new file mode 100644 index 000000000..7db086289 --- /dev/null +++ b/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton.css @@ -0,0 +1 @@ +.someClass{color:var(--someColor)} \ No newline at end of file diff --git a/test/lib/builder/BuildContext.js b/test/lib/builder/BuildContext.js index 1021fce32..389750e6d 100644 --- a/test/lib/builder/BuildContext.js +++ b/test/lib/builder/BuildContext.js @@ -25,6 +25,25 @@ test("getRootProject", (t) => { t.is(buildContext.getRootProject(), "pony", "Returned correct value"); }); +test("getBuildOption", (t) => { + const buildContext = new BuildContext({ + rootProject: "root_project", + options: { + a: true, + b: "Pony", + c: 235, + d: { + d1: "Bee" + } + } + }); + + t.is(buildContext.getOption("a"), true, "Returned 'boolean' value is correct"); + t.is(buildContext.getOption("b"), "Pony", "Returned 'String' value is correct"); + t.is(buildContext.getOption("c"), 235, "Returned 'Number' value is correct"); + t.deepEqual(buildContext.getOption("d"), {d1: "Bee"}, "Returned 'object' value is correct"); +}); + test.serial("createProjectContext", (t) => { class DummyProjectContext { constructor({buildContext, project, resources}) { diff --git a/test/lib/builder/ProjectBuildContext.js b/test/lib/builder/ProjectBuildContext.js index d14b3072c..6d5375719 100644 --- a/test/lib/builder/ProjectBuildContext.js +++ b/test/lib/builder/ProjectBuildContext.js @@ -41,6 +41,18 @@ test("isRootProject: false", (t) => { t.false(projectBuildContext.isRootProject(), "Correctly identified non-root project"); }); +test("getBuildOption", (t) => { + const projectBuildContext = new ProjectBuildContext({ + buildContext: { + getOption: () => "Pony" + }, + project: "my project", + resources: "resources" + }); + + t.deepEqual(projectBuildContext.getOption("a"), "Pony", "Returned value is correct"); +}); + test("registerCleanupTask", (t) => { const projectBuildContext = new ProjectBuildContext({ buildContext: { diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js index b40fa6d45..5dade8b0a 100644 --- a/test/lib/builder/builder.js +++ b/test/lib/builder/builder.js @@ -31,6 +31,7 @@ const libraryØPath = path.join(__dirname, "..", "..", "fixtures", "library.ø") const libraryCore = path.join(__dirname, "..", "..", "fixtures", "sap.ui.core-evo"); const libraryCoreBuildtime = path.join(__dirname, "..", "..", "fixtures", "sap.ui.core-buildtime"); const themeJPath = path.join(__dirname, "..", "..", "fixtures", "theme.j"); +const themeLibraryEPath = path.join(__dirname, "..", "..", "fixtures", "theme.library.e"); const recursive = require("recursive-readdir"); @@ -111,9 +112,11 @@ test.serial("Build", async (t) => { getTag: getTagStub }); const isRootProjectStub = sinon.stub().returns(true); + const getOptionStub = sinon.stub().returns("Pony"); const dummyProjectContext = { getResourceTagCollection: getResourceTagCollectionStub, isRootProject: isRootProjectStub, + getOption: getOptionStub, STANDARD_TAGS: { OmitFromBuildResult: "👻" } @@ -888,6 +891,44 @@ test.serial("Build library.coreBuildtime: replaceBuildtime", (t) => { }); }); +test.serial("Build library with theme configured for CSS variables", (t) => { + const destPath = "./test/tmp/build/theme.j/dest-css-variables"; + const expectedPath = "./test/expected/build/theme.j/dest-css-variables"; + return builder.build({ + tree: themeJTree, + cssVariables: true, + destPath + }).then(() => { + return findFiles(expectedPath); + }).then((expectedFiles) => { + // Check for all directories and files + assert.directoryDeepEqual(destPath, expectedPath); + + return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + }).then(() => { + t.pass(); + }); +}); + +test.serial("Build theme-library with CSS variables", (t) => { + const destPath = "./test/tmp/build/theme.library.e/dest-css-variables"; + const expectedPath = "./test/expected/build/theme.library.e/dest-css-variables"; + return builder.build({ + tree: themeLibraryETree, + cssVariables: true, + destPath + }).then(() => { + return findFiles(expectedPath); + }).then((expectedFiles) => { + // Check for all directories and files + assert.directoryDeepEqual(destPath, expectedPath); + + return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + }).then(() => { + t.pass(); + }); +}); + test.serial("Cleanup", async (t) => { const BuildContext = require("../../../lib/builder/BuildContext"); const createProjectContextStub = sinon.spy(BuildContext.prototype, "createProjectContext"); @@ -1868,3 +1909,27 @@ const themeJTree = { } } }; + +const themeLibraryETree = { + "id": "theme.library.e.id", + "version": "1.0.0", + "path": themeLibraryEPath, + "dependencies": [], + "_level": 0, + "_isRoot": true, + "specVersion": "1.1", + "type": "theme-library", + "metadata": { + "name": "theme.library.e", + "namespace": "theme/library/e", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + } + } + } +}; diff --git a/test/lib/tasks/TaskUtil.js b/test/lib/tasks/TaskUtil.js index 5f2aba82c..cdb2c3dc5 100644 --- a/test/lib/tasks/TaskUtil.js +++ b/test/lib/tasks/TaskUtil.js @@ -93,6 +93,21 @@ test("isRootProject", async (t) => { t.is(res, true, "Correct result"); }); +test("getBuildOption", (t) => { + const getOptionStub = sinon.stub().returns("Pony"); + const taskUtil = new TaskUtil({ + projectBuildContext: { + STANDARD_TAGS: ["some tag"], + getOption: getOptionStub + } + }); + + const res = taskUtil.getBuildOption("friend"); + + t.is(getOptionStub.callCount, 1, "ProjectBuildContext#getBuildOption got called once"); + t.is(res, "Pony", "Correct result"); +}); + test("registerCleanupTask", async (t) => { const registerCleanupTaskStub = sinon.stub(); const taskUtil = new TaskUtil({ diff --git a/test/lib/types/themeLibrary/ThemeLibraryBuilder.js b/test/lib/types/themeLibrary/ThemeLibraryBuilder.js index 7deab2f94..b3bdb0f55 100644 --- a/test/lib/types/themeLibrary/ThemeLibraryBuilder.js +++ b/test/lib/types/themeLibrary/ThemeLibraryBuilder.js @@ -39,6 +39,12 @@ test("tasks", async (t) => { taskUtil: { isRootProject: () => { return true; + }, + getBuildOptions: () => { + return {}; + }, + getBuildOption: (key) => { + return key; } } });