Skip to content

Commit 10e89b9

Browse files
committed
[FEATURE] Theming Parameters via CSS Variables
1 parent caccdf3 commit 10e89b9

File tree

9 files changed

+248
-18
lines changed

9 files changed

+248
-18
lines changed

lib/engine.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
let engineInfo;
2+
module.exports.getInfo = function() {
3+
if (!engineInfo) {
4+
const {name, version} = require("../package.json");
5+
engineInfo = {name, version};
6+
}
7+
return engineInfo;
8+
};

lib/index.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const fileUtilsFactory = require("./fileUtils");
1111

1212
const Compiler = require("./Compiler");
1313

14-
const themingParametersDataUri = require("./themingParameters/dataUri");
14+
const themingParameters = require("./themingParameters/index");
1515

1616
// Workaround for a performance issue in the "css" parser module when used in combination
1717
// with the "colors" module that enhances the String prototype.
@@ -108,6 +108,8 @@ Builder.prototype.cacheTheme = function(result) {
108108
* @param {boolean} options.cssVariables whether or not to enable css variables output
109109
* @param {string} options.lessInput less string input
110110
* @param {string} options.lessInputPath less file input
111+
* @param {themingParameters.STRATEGY} [options.themingParameters=DATA_URI]
112+
* Strategy of including theming parameters within library CSS
111113
* @returns {{css: string, cssRtl: string, variables: {}, imports: [], cssSkeleton: string, cssSkeletonRtl: string, cssVariables: string, cssVariablesSource: string }}
112114
*/
113115
Builder.prototype.build = function(options) {
@@ -122,9 +124,16 @@ Builder.prototype.build = function(options) {
122124
parser: {},
123125
compiler: {},
124126
library: {},
125-
scope: {}
127+
scope: {},
128+
themingParameters: themingParameters.STRATEGY.DATA_URI
126129
}, options);
127130

131+
if (!themingParameters.STRATEGY[options.themingParameters]) {
132+
return Promise.reject(
133+
new Error("Invalid themingParameters strategy provided! Valid values: CSS_VARIABLES, DATA_URI")
134+
);
135+
}
136+
128137
if (options.compiler.sourceMap) {
129138
return Promise.reject(new Error("compiler.sourceMap option is not supported!"));
130139
}
@@ -144,7 +153,7 @@ Builder.prototype.build = function(options) {
144153
});
145154

146155
function addInlineParameters(result) {
147-
return themingParametersDataUri.addInlineParameters({result, options});
156+
return themingParameters.addInlineParameters({result, options});
148157
}
149158

150159
function getScopeVariables(options) {

lib/themingParameters/cssVariables.js

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
const engine = require("../engine");
2+
3+
// match a CSS url
4+
// (taken from sap/ui/core/theming/Parameters.js)
5+
const rCssUrl = /url[\s]*\('?"?([^'")]*)'?"?\)/;
6+
7+
module.exports = {
8+
addInlineParameters({result, options}) {
9+
// CSS Variables can only be added when the library name is known
10+
if (typeof options.library !== "object" || typeof options.library.name !== "string") {
11+
return;
12+
}
13+
14+
const libraryNameDashed = options.library.name.replace(/\./g, "-");
15+
const themeMetadata = this.getThemeMetadata({result, options});
16+
17+
const themeMetadataVariable = `
18+
:root {
19+
--sapThemeMetaData-UI5-${libraryNameDashed}: ${JSON.stringify(themeMetadata)};
20+
}
21+
`;
22+
23+
const urlVariables = this.getUrlVariables({result});
24+
const themingCssVariables = this.getThemingCssVariables({result});
25+
26+
const themingParameters = `
27+
/* Inline theming parameters (CSS Variables) */
28+
:root {
29+
--sapUiTheme-${libraryNameDashed}: ${JSON.stringify(urlVariables)};
30+
${themingCssVariables}
31+
}
32+
`;
33+
34+
result.css += themeMetadataVariable + themingParameters;
35+
if (options.rtl) {
36+
result.cssRtl += themeMetadataVariable + themingParameters;
37+
}
38+
},
39+
getThemeMetadata({result, options}) {
40+
let scopes;
41+
if (typeof result.variables.scopes === "object") {
42+
scopes = Object.keys(result.variables.scopes);
43+
} else {
44+
scopes = [];
45+
}
46+
47+
const libraryNameSlashed = options.library.name.replace(/\./g, "/");
48+
49+
// TODO: How to get theme name? .theming "sId"? parse from file path? new parameter?
50+
const themeId = "<theme-name>";
51+
// TODO: How to get base theme name(s)? Read from .theming?
52+
const Extends = ["<base-theme>"];
53+
54+
const {version, name} = engine.getInfo();
55+
return {
56+
Path: `UI5.${libraryNameSlashed}.${themeId}.library`,
57+
PathPattern: "/%frameworkId%/%libId%/themes/%themeId%/%fileId%.css",
58+
Extends,
59+
Scopes: scopes,
60+
Engine: {
61+
Version: version,
62+
Name: name
63+
},
64+
Version: {
65+
Build: "<TODO>", // TOOD: add new property options.library.version
66+
Source: "<TODO>" // TOOD: add new property options.library.version
67+
}
68+
};
69+
},
70+
getUrlVariables({result}) {
71+
const urlVariables = {};
72+
73+
// TODO: support scopes (default/scopes top-level properties, see runtime code)
74+
Object.entries(result.variables).map(([name, value]) => {
75+
if (rCssUrl.test(value)) {
76+
urlVariables[name] = value;
77+
}
78+
});
79+
80+
return urlVariables;
81+
},
82+
getThemingCssVariables({result}) {
83+
return Object.entries(result.variables).map(([name, value]) => {
84+
return ` --${name}: ${value};`;
85+
}).join("\n");
86+
},
87+
};

lib/themingParameters/dataUri.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module.exports = {
22
addInlineParameters: function({result, options}) {
33
// Inline parameters can only be added when the library name is known
44
if (typeof options.library !== "object" || typeof options.library.name !== "string") {
5-
return result;
5+
return;
66
}
77

88
const parameters = JSON.stringify(result.variables);
@@ -27,7 +27,5 @@ module.exports = {
2727
// for the css variables build we just add it to the variables
2828
result.cssVariables += parameterStyleRule;
2929
}
30-
31-
return result;
3230
}
3331
};

lib/themingParameters/index.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const themingParameters = {
2+
addInlineParameters({result, options}) {
3+
switch (options.themingParameters) {
4+
case themingParameters.STRATEGY.DATA_URI:
5+
require("./dataUri").addInlineParameters({result, options});
6+
break;
7+
case themingParameters.STRATEGY.CSS_VARIABLES:
8+
require("./cssVariables").addInlineParameters({result, options});
9+
break;
10+
}
11+
return result;
12+
},
13+
STRATEGY: {
14+
DATA_URI: "DATA_URI",
15+
CSS_VARIABLES: "CSS_VARIABLES"
16+
}
17+
};
18+
19+
module.exports = themingParameters;

package-lock.json

+33
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"eslint-config-google": "^0.14.0",
9393
"graceful-fs": "^4.2.8",
9494
"mocha": "^8.4.0",
95+
"mock-require": "^3.0.3",
9596
"nyc": "^15.1.0",
9697
"sinon": "^11.1.2"
9798
}
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* eslint-env mocha */
2+
3+
const assert = require("assert");
4+
const sinon = require("sinon");
5+
const mock = require("mock-require");
6+
7+
describe("themingParameters/cssVariables", function() {
8+
let themingParametersCssVariables;
9+
10+
before(() => {
11+
mock("../../../lib/engine", {
12+
getInfo: sinon.stub().returns({
13+
name: "less-openui5",
14+
version: "1.0.0-test"
15+
})
16+
});
17+
themingParametersCssVariables = require("../../../lib/themingParameters/cssVariables");
18+
});
19+
after(() => {
20+
mock.stopAll();
21+
});
22+
23+
it("should not add theming parameters when library name is missing", function() {
24+
const result = {};
25+
const options = {};
26+
const returnValue = themingParametersCssVariables.addInlineParameters({result, options});
27+
28+
assert.equal(returnValue, undefined, "nothing should be returned");
29+
assert.deepEqual(result, {}, "result object should not be modified");
30+
});
31+
32+
it("should add theming parameters to css", function() {
33+
const result = {
34+
css: "/* css */",
35+
variables: {foo: "bar"}
36+
};
37+
const options = {
38+
library: {
39+
name: "sap.ui.test"
40+
}
41+
};
42+
const expectedMetadata = {
43+
Path: `UI5.sap/ui/test.<theme-name>.library`, // TODO: theme name placeholder
44+
PathPattern: "/%frameworkId%/%libId%/themes/%themeId%/%fileId%.css",
45+
Extends: ["<base-theme>"], // TODO: base theme placeholder
46+
Scopes: [],
47+
Engine: {
48+
Version: "1.0.0-test",
49+
Name: "less-openui5"
50+
},
51+
Version: {
52+
Build: "<TODO>",
53+
Source: "<TODO>"
54+
}
55+
};
56+
57+
const returnValue = themingParametersCssVariables.addInlineParameters({result, options});
58+
59+
assert.equal(returnValue, undefined, "nothing should be returned");
60+
assert.deepEqual(result, {
61+
variables: {foo: "bar"},
62+
css: `/* css */
63+
:root {
64+
--sapThemeMetaData-UI5-sap-ui-test: ${JSON.stringify(expectedMetadata)};
65+
}
66+
67+
/* Inline theming parameters (CSS Variables) */
68+
:root {
69+
--sapUiTheme-sap-ui-test: {};
70+
--foo: bar;
71+
}
72+
`
73+
}, "result.css should be enhanced");
74+
});
75+
});

test/lib/themingParameters/dataUri.js

+12-12
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ describe("themingParameters/dataUri", function() {
99
it("should not add theming parameters when library name is missing", function() {
1010
const result = {};
1111
const options = {};
12-
const returnedResult = themingParametersDataUri.addInlineParameters({result, options});
12+
const returnValue = themingParametersDataUri.addInlineParameters({result, options});
1313

14-
assert.equal(returnedResult, result, "result object reference should be returned");
14+
assert.equal(returnValue, undefined, "nothing should be returned");
1515
assert.deepEqual(result, {}, "result object should not be modified");
1616
});
1717

@@ -25,9 +25,9 @@ describe("themingParameters/dataUri", function() {
2525
name: "sap.ui.test"
2626
}
2727
};
28-
const returnedResult = themingParametersDataUri.addInlineParameters({result, options});
28+
const returnValue = themingParametersDataUri.addInlineParameters({result, options});
2929

30-
assert.equal(returnedResult, result, "result object reference should be returned");
30+
assert.equal(returnValue, undefined, "nothing should be returned");
3131
assert.deepEqual(result, {
3232
variables: {foo: "bar"},
3333
css: `/* css */
@@ -50,9 +50,9 @@ describe("themingParameters/dataUri", function() {
5050
name: "sap.ui.test"
5151
}
5252
};
53-
const returnedResult = themingParametersDataUri.addInlineParameters({result, options});
53+
const returnValue = themingParametersDataUri.addInlineParameters({result, options});
5454

55-
assert.equal(returnedResult, result, "result object reference should be returned");
55+
assert.equal(returnValue, undefined, "nothing should be returned");
5656
assert.deepEqual(result, {
5757
variables: {
5858
foo: "50%",
@@ -78,9 +78,9 @@ data:text/plain;utf-8,%7B%22foo%22%3A%2250%25%22%2C%22bar%22%3A%22%27%5C%22%27%2
7878
},
7979
rtl: true
8080
};
81-
const returnedResult = themingParametersDataUri.addInlineParameters({result, options});
81+
const returnValue = themingParametersDataUri.addInlineParameters({result, options});
8282

83-
assert.equal(returnedResult, result, "result object reference should be returned");
83+
assert.equal(returnValue, undefined, "nothing should be returned");
8484
assert.deepEqual(result, {
8585
variables: {foo: "bar"},
8686
css: `/* css */
@@ -106,9 +106,9 @@ data:text/plain;utf-8,%7B%22foo%22%3A%2250%25%22%2C%22bar%22%3A%22%27%5C%22%27%2
106106
},
107107
cssVariables: true
108108
};
109-
const returnedResult = themingParametersDataUri.addInlineParameters({result, options});
109+
const returnValue = themingParametersDataUri.addInlineParameters({result, options});
110110

111-
assert.equal(returnedResult, result, "result object reference should be returned");
111+
assert.equal(returnValue, undefined, "nothing should be returned");
112112
assert.deepEqual(result, {
113113
variables: {foo: "bar"},
114114
css: `/* css */
@@ -136,9 +136,9 @@ data:text/plain;utf-8,%7B%22foo%22%3A%2250%25%22%2C%22bar%22%3A%22%27%5C%22%27%2
136136
rtl: true,
137137
cssVariables: true
138138
};
139-
const returnedResult = themingParametersDataUri.addInlineParameters({result, options});
139+
const returnValue = themingParametersDataUri.addInlineParameters({result, options});
140140

141-
assert.equal(returnedResult, result, "result object reference should be returned");
141+
assert.equal(returnValue, undefined, "nothing should be returned");
142142
assert.deepEqual(result, {
143143
variables: {foo: "bar"},
144144
css: `/* css */

0 commit comments

Comments
 (0)