less-openui5 0.11.1 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,19 +1,17 @@
1
1
  "use strict";
2
2
 
3
3
  const path = require("path");
4
- const clone = require("clone");
5
- const css = require("css");
4
+ const css = require("@adobe/css-tools");
5
+
6
6
 
7
- const createParser = require("./less/parser");
8
7
  const diff = require("./diff");
9
8
  const scope = require("./scope");
10
9
 
11
10
  const fileUtilsFactory = require("./fileUtils");
12
11
 
13
- // Plugins
14
- const ImportCollectorPlugin = require("./plugin/import-collector");
15
- const VariableCollectorPlugin = require("./plugin/variable-collector");
16
- const UrlCollector = require("./plugin/url-collector");
12
+ const Compiler = require("./Compiler");
13
+
14
+ const themingParametersDataUri = require("./themingParameters/dataUri");
17
15
 
18
16
  // Workaround for a performance issue in the "css" parser module when used in combination
19
17
  // with the "colors" module that enhances the String prototype.
@@ -104,7 +102,7 @@ Builder.prototype.cacheTheme = function(result) {
104
102
  };
105
103
 
106
104
  /**
107
- * Creates a themebuild
105
+ * Runs a theme build
108
106
  * @param {object} options
109
107
  * @param {object} options.compiler compiler object as passed to less
110
108
  * @param {boolean} options.cssVariables whether or not to enable css variables output
@@ -115,10 +113,6 @@ Builder.prototype.cacheTheme = function(result) {
115
113
  Builder.prototype.build = function(options) {
116
114
  const that = this;
117
115
 
118
- // Stores mapping between "virtual" paths (used within LESS) and real filesystem paths.
119
- // Only relevant when using the "rootPaths" option.
120
- const mFileMappings = {};
121
-
122
116
  // Assign default options
123
117
  options = Object.assign({
124
118
  lessInput: null,
@@ -143,211 +137,14 @@ Builder.prototype.build = function(options) {
143
137
  options.parser.relativeUrls = true;
144
138
  }
145
139
 
146
- function fileHandler(file, currentFileInfo, handleDataAndCallCallback, callback) {
147
- let pathname;
148
-
149
- // support absolute paths such as "/resources/my/base.less"
150
- if (path.posix.isAbsolute(file)) {
151
- pathname = path.posix.normalize(file);
152
- } else {
153
- pathname = path.posix.join(currentFileInfo.currentDirectory, file);
154
- }
155
-
156
- that.fileUtils.readFile(pathname, options.rootPaths).then(function(result) {
157
- if (!result) {
158
- // eslint-disable-next-line no-console
159
- console.log("File not found: " + pathname);
160
- callback({type: "File", message: "Could not find file at path '" + pathname + "'"});
161
- } else {
162
- try {
163
- // Save import mapping to calculate full import paths later on
164
- mFileMappings[currentFileInfo.rootFilename] = mFileMappings[currentFileInfo.rootFilename] || {};
165
- mFileMappings[currentFileInfo.rootFilename][pathname] = result.path;
166
-
167
- handleDataAndCallCallback(pathname, result.content);
168
- } catch (e) {
169
- // eslint-disable-next-line no-console
170
- console.log(e);
171
- callback(e);
172
- }
173
- }
174
- }, function(err) {
175
- // eslint-disable-next-line no-console
176
- console.log(err);
177
- callback(err);
178
- });
179
- }
180
-
181
- function compile(config) {
182
- const parserOptions = clone(options.parser);
183
- let rootpath;
184
-
185
- if (config.path) {
186
- // Keep rootpath for later usage in the ImportCollectorPlugin
187
- rootpath = config.path;
188
- parserOptions.filename = config.localPath;
189
- }
190
-
191
- // inject the library name as prefix (e.g. "sap.m" as "_sap_m")
192
- if (options.library && typeof options.library.name === "string") {
193
- const libName = config.libName = options.library.name;
194
- config.prefix = "_" + libName.replace(/\./g, "_") + "_";
195
- }
196
-
197
- // Keep filename for later usage (see ImportCollectorPlugin) as less modifies the parserOptions.filename
198
- const filename = parserOptions.filename;
199
-
200
- // Only use custom file handler when "rootPaths" or custom "fs" are used
201
- let fnFileHandler;
202
- if ((options.rootPaths && options.rootPaths.length > 0) || that.customFs) {
203
- fnFileHandler = fileHandler;
204
- }
205
-
206
- const parser = createParser(parserOptions, fnFileHandler);
207
-
208
- return new Promise(function(resolve, reject) {
209
- parser.parse(config.content, function(err, tree) {
210
- if (err) {
211
- reject(err);
212
- } else {
213
- resolve(tree);
214
- }
215
- });
216
- }).then(async function(tree) {
217
- const result = {};
218
-
219
- result.tree = tree;
220
-
221
- // plugins to collect imported files and variable values
222
- const oImportCollector = new ImportCollectorPlugin({
223
- importMappings: mFileMappings[filename]
224
- });
225
- const oVariableCollector = new VariableCollectorPlugin(options.compiler);
226
- const oUrlCollector = new UrlCollector();
227
-
228
- // render to css
229
- result.css = tree.toCSS(Object.assign({}, options.compiler, {
230
- plugins: [oImportCollector, oVariableCollector, oUrlCollector]
231
- }));
232
-
233
- // retrieve imported files
234
- result.imports = oImportCollector.getImports();
235
-
236
- // retrieve reduced set of variables
237
- result.variables = oVariableCollector.getVariables(Object.keys(mFileMappings[filename] || {}));
238
-
239
- // retrieve all variables
240
- result.allVariables = oVariableCollector.getAllVariables();
241
-
242
- // also compile rtl-version if requested
243
- let oRTL;
244
- if (options.rtl) {
245
- const RTLPlugin = require("./plugin/rtl");
246
- oRTL = new RTLPlugin();
247
-
248
- const urls = oUrlCollector.getUrls();
249
-
250
- const existingImgRtlUrls = (await Promise.all(
251
- urls.map(async ({currentDirectory, relativeUrl}) => {
252
- const relativeImgRtlUrl = RTLPlugin.getRtlImgUrl(relativeUrl);
253
- if (relativeImgRtlUrl) {
254
- const resolvedImgRtlUrl = path.posix.join(currentDirectory, relativeImgRtlUrl);
255
- if (await that.fileUtils.findFile(resolvedImgRtlUrl, options.rootPaths)) {
256
- return resolvedImgRtlUrl;
257
- }
258
- }
259
- })
260
- )).filter(Boolean);
261
-
262
- oRTL.setExistingImgRtlPaths(existingImgRtlUrls);
263
- }
264
-
265
- if (oRTL) {
266
- result.cssRtl = tree.toCSS(Object.assign({}, options.compiler, {
267
- plugins: [oRTL]
268
- }));
269
- }
270
-
271
- if (rootpath) {
272
- result.imports.unshift(rootpath);
273
- }
274
-
275
- // also compile css-variables version if requested
276
- if (options.cssVariables) {
277
- return new Promise(function(resolve, reject) {
278
- // parse the content again to have a clean tree
279
- parser.parse(config.content, function(err, tree) {
280
- if (err) {
281
- reject(err);
282
- } else {
283
- resolve(tree);
284
- }
285
- });
286
- }).then(function(tree) {
287
- // generate the skeleton-css and the less-variables
288
- const CSSVariablesCollectorPlugin = require("./plugin/css-variables-collector");
289
- const oCSSVariablesCollector = new CSSVariablesCollectorPlugin(config);
290
- result.cssSkeleton = tree.toCSS(Object.assign({}, options.compiler, {
291
- plugins: [oCSSVariablesCollector]
292
- }));
293
- result.cssVariablesSource = oCSSVariablesCollector.toLessVariables();
294
- if (oRTL) {
295
- const oCSSVariablesCollectorRTL = new CSSVariablesCollectorPlugin(config);
296
- result.cssSkeletonRtl = tree.toCSS(Object.assign({}, options.compiler, {
297
- plugins: [oCSSVariablesCollectorRTL, oRTL]
298
- }));
299
- }
300
- return tree;
301
- }).then(function(tree) {
302
- // generate the css-variables content out of the less-variables
303
- return new Promise(function(resolve, reject) {
304
- parser.parse(result.cssVariablesSource, function(err, tree) {
305
- if (err) {
306
- reject(err);
307
- } else {
308
- const CSSVariablesPointerPlugin = require("./plugin/css-variables-pointer");
309
- result.cssVariables = tree.toCSS(Object.assign({}, options.compiler, {
310
- plugins: [new CSSVariablesPointerPlugin()]
311
- }));
312
- resolve(result);
313
- }
314
- });
315
- });
316
- });
317
- }
318
-
319
- return result;
320
- });
321
- }
140
+ const compiler = new Compiler({
141
+ options,
142
+ fileUtils: this.fileUtils,
143
+ customFs: this.customFs
144
+ });
322
145
 
323
146
  function addInlineParameters(result) {
324
- return new Promise(function(resolve, reject) {
325
- if (typeof options.library === "object" && typeof options.library.name === "string") {
326
- const parameters = JSON.stringify(result.variables);
327
-
328
- // properly escape the parameters to be part of a data-uri
329
- // + escaping single quote (') as it is used to surround the data-uri: url('...')
330
- const escapedParameters = encodeURIComponent(parameters).replace(/'/g, function(char) {
331
- return escape(char);
332
- });
333
-
334
- // embed parameter variables as plain-text string into css
335
- const parameterStyleRule = "\n/* Inline theming parameters */\n#sap-ui-theme-" +
336
- options.library.name.replace(/\./g, "\\.") +
337
- "{background-image:url('data:text/plain;utf-8," + escapedParameters + "')}\n";
338
-
339
- // embed parameter variables as plain-text string into css
340
- result.css += parameterStyleRule;
341
- if (options.rtl) {
342
- result.cssRtl += parameterStyleRule;
343
- }
344
- if (options.cssVariables) {
345
- // for the css variables build we just add it to the variables
346
- result.cssVariables += parameterStyleRule;
347
- }
348
- }
349
- resolve(result);
350
- });
147
+ return themingParametersDataUri.addInlineParameters({result, options});
351
148
  }
352
149
 
353
150
  function getScopeVariables(options) {
@@ -380,13 +177,13 @@ Builder.prototype.build = function(options) {
380
177
  if (!config) {
381
178
  throw new Error("Could not find embeddedCompareFile at path '" + scopeOptions.embeddedCompareFilePath + "'");
382
179
  }
383
- return compile(config);
180
+ return compiler.compile(config);
384
181
  }),
385
182
  that.fileUtils.readFile(scopeOptions.embeddedFilePath, options.rootPaths).then(function(config) {
386
183
  if (!config) {
387
184
  throw new Error("Could not find embeddedFile at path '" + scopeOptions.embeddedFilePath + "'");
388
185
  }
389
- return compile(config);
186
+ return compiler.compile(config);
390
187
  })
391
188
  ]).then(function(results) {
392
189
  return {
@@ -528,7 +325,9 @@ Builder.prototype.build = function(options) {
528
325
  }
529
326
 
530
327
  // No css diffing and scoping
531
- return that.fileUtils.readFile(options.lessInputPath, options.rootPaths).then(compile);
328
+ return that.fileUtils.readFile(options.lessInputPath, options.rootPaths).then((config) => {
329
+ return compiler.compile(config);
330
+ });
532
331
  });
533
332
  }
534
333
 
@@ -541,7 +340,7 @@ Builder.prototype.build = function(options) {
541
340
  }
542
341
 
543
342
  if (options.lessInput) {
544
- return compile({
343
+ return compiler.compile({
545
344
  content: options.lessInput
546
345
  }).then(addInlineParameters).then(that.cacheTheme.bind(that));
547
346
  } else {
@@ -11,12 +11,13 @@ const CSSVariablesCollectorPlugin = module.exports = function(config) {
11
11
  this.ruleStack = [];
12
12
  this.mixinStack = [];
13
13
  this.parenStack = [];
14
- this.importStack = [];
15
14
  };
16
15
 
17
16
  CSSVariablesCollectorPlugin.prototype = {
18
17
 
18
+ // needed to keep the less variable references intact to use this info for the CSS variables references
19
19
  isPreEvalVisitor: true,
20
+
20
21
  isReplacing: true,
21
22
 
22
23
  _isInMixinOrParen() {
@@ -27,33 +28,47 @@ CSSVariablesCollectorPlugin.prototype = {
27
28
  return this.ruleStack.length > 0 && !this.ruleStack[this.ruleStack.length - 1].variable;
28
29
  },
29
30
 
30
- _isInGlobalOrBaseImport() {
31
- return this.config.libName !== "sap.ui.core" && this.importStack.filter((importNode) => {
32
- return /\/(?:global|base)\.less$/.test(importNode.importedFilename);
33
- }).length > 0;
31
+ _isVarInLibrary({filename} = {}) {
32
+ // for libraries we check that the file is within the libraries theme path
33
+ // in all other cases with no filename (indicates calculated variables)
34
+ // or in case of variables in standalone less files we just include them!
35
+ const regex = new RegExp(`(^|/)${this.config.libPath}/themes/`);
36
+ const include = !filename ||
37
+ (this.config.libPath ? regex.test(filename) : true);
38
+ return include;
34
39
  },
35
40
 
36
41
  _isRelevant() {
37
42
  return !this._isInMixinOrParen() && this._isVarInRule();
38
43
  },
39
44
 
40
- toLessVariables() {
45
+ toLessVariables(varsOverride) {
46
+ const vars = {};
47
+ Object.keys(this.vars).forEach((variableName) => {
48
+ const override = this.vars[variableName].updateAfterEval && varsOverride[variableName] !== undefined;
49
+ vars[variableName] = {
50
+ css: override ? varsOverride[variableName] : this.vars[variableName].css,
51
+ export: this.vars[variableName].export
52
+ };
53
+ });
41
54
  let lessVariables = "";
42
- Object.keys(this.vars).forEach((value, index) => {
43
- lessVariables += "@" + value + ": " + this.vars[value].css + ";\n";
55
+ Object.keys(vars).forEach((variableName) => {
56
+ const variableValue = vars[variableName].css;
57
+ lessVariables += `@${variableName}: ${variableValue};\n`;
44
58
  });
45
- Object.keys(this.calcVars).forEach((value, index) => {
46
- lessVariables += "@" + value + ": " + this.calcVars[value].css + ";\n";
59
+ Object.keys(this.calcVars).forEach((variableName) => {
60
+ const variableValue = this.calcVars[variableName].css;
61
+ lessVariables += `@${variableName}: ${variableValue};\n`;
47
62
  });
48
63
  lessVariables += "\n:root {\n";
49
- Object.keys(this.vars).forEach((value, index) => {
50
- if (this.vars[value].export) {
51
- lessVariables += "--" + value + ": @" + value + ";\n";
64
+ Object.keys(vars).forEach((variableName) => {
65
+ if (vars[variableName].export) {
66
+ lessVariables += `--${variableName}: @${variableName};\n`;
52
67
  }
53
68
  });
54
- Object.keys(this.calcVars).forEach((value, index) => {
55
- if (this.calcVars[value].export) {
56
- lessVariables += "--" + value + ": @" + value + ";\n";
69
+ Object.keys(this.calcVars).forEach((variableName) => {
70
+ if (this.calcVars[variableName].export) {
71
+ lessVariables += `--${variableName}: @${variableName};\n`;
57
72
  }
58
73
  });
59
74
  lessVariables += "}\n";
@@ -89,15 +104,17 @@ CSSVariablesCollectorPlugin.prototype = {
89
104
 
90
105
  visitOperation(node, visitArgs) {
91
106
  if (this._isRelevant()) {
107
+ // console.log("visitOperation", this.ruleStack[this.ruleStack.length - 1], this._getCSS(node));
92
108
  return new less.tree.Call("calc", [new less.tree.Expression([node.operands[0], new less.tree.Anonymous(node.op), node.operands[1]])]);
93
109
  }
94
110
  return node;
95
111
  },
96
112
 
97
113
  visitCall(node, visitArgs) {
98
- // if variables are used inside rules, generate a new dynamic variable for it!
114
+ // if variables are used inside rules, generate a new calculated variable for it!
99
115
  const isRelevantFunction = typeof less.tree.functions[node.name] === "function" && ["rgba"].indexOf(node.name) === -1;
100
116
  if (this._isRelevant() && isRelevantFunction) {
117
+ // console.log("visitCall", this.ruleStack[this.ruleStack.length - 1], this._getCSS(node));
101
118
  const css = this._getCSS(node);
102
119
  let newName = this.config.prefix + "function_" + node.name + Object.keys(this.vars).length;
103
120
  // check for duplicate value in vars already
@@ -109,7 +126,7 @@ CSSVariablesCollectorPlugin.prototype = {
109
126
  }
110
127
  this.calcVars[newName] = {
111
128
  css: css,
112
- export: !this._isInGlobalOrBaseImport()
129
+ export: this._isVarInLibrary()
113
130
  };
114
131
  return new less.tree.Call("var", [new less.tree.Anonymous("--" + newName, node.index, node.currentFileInfo, node.mapLines)]);
115
132
  }
@@ -119,6 +136,7 @@ CSSVariablesCollectorPlugin.prototype = {
119
136
  visitNegative(node, visitArgs) {
120
137
  // convert negative into calc function
121
138
  if (this._isRelevant()) {
139
+ // console.log("visitNegative", this.ruleStack[this.ruleStack.length - 1], this._getCSS(node));
122
140
  return new less.tree.Call("calc", [new less.tree.Expression([new less.tree.Anonymous("-1"), new less.tree.Anonymous("*"), node.value])]);
123
141
  }
124
142
  return node;
@@ -133,6 +151,19 @@ CSSVariablesCollectorPlugin.prototype = {
133
151
  },
134
152
 
135
153
  visitRule(node, visitArgs) {
154
+ // check rule for being a variable declaration
155
+ const isVarDeclaration = typeof node.name === "string" && node.name.startsWith("@");
156
+ if (!this._isInMixinOrParen() && isVarDeclaration) {
157
+ // add the variable declaration to the list of vars
158
+ const varName = node.name.substr(1);
159
+ const isVarInLib = this._isVarInLibrary({
160
+ filename: node.currentFileInfo.filename
161
+ });
162
+ this.vars[varName] = {
163
+ css: this._getCSS(node.value),
164
+ export: isVarInLib
165
+ };
166
+ }
136
167
  // store the rule context for the call variable extraction
137
168
  this.ruleStack.push(node);
138
169
  return node;
@@ -168,29 +199,13 @@ CSSVariablesCollectorPlugin.prototype = {
168
199
  return node;
169
200
  },
170
201
 
171
- visitImport(node, visitArgs) {
172
- // store the import context
173
- this.importStack.push(node);
174
- return node;
175
- },
176
-
177
- visitImportOut(node) {
178
- // remove import context
179
- this.importStack.pop();
180
- return node;
181
- },
182
-
183
- visitRuleset(node, visitArgs) {
184
- node.rules.forEach((value) => {
185
- const isVarDeclaration = value instanceof less.tree.Rule && typeof value.name === "string" && value.name.startsWith("@");
186
- if (!this._isInMixinOrParen() && isVarDeclaration) {
187
- // add the variable declaration to the list of vars
188
- this.vars[value.name.substr(1)] = {
189
- css: this._getCSS(value.value),
190
- export: !this._isInGlobalOrBaseImport()
191
- };
192
- }
193
- });
202
+ visitUrl(node, visitArgs) {
203
+ // we mark the less variables which should be updated after eval
204
+ // => strangewise less variables with "none" values are also urls
205
+ // after the less variables have been evaluated
206
+ if (this.ruleStack.length > 0 && this.ruleStack[0].variable) {
207
+ this.vars[this.ruleStack[0].name.substr(1)].updateAfterEval = true;
208
+ }
194
209
  return node;
195
210
  }
196
211
 
@@ -0,0 +1,33 @@
1
+ module.exports = {
2
+ addInlineParameters: function({result, options}) {
3
+ // Inline parameters can only be added when the library name is known
4
+ if (typeof options.library !== "object" || typeof options.library.name !== "string") {
5
+ return result;
6
+ }
7
+
8
+ const parameters = JSON.stringify(result.variables);
9
+
10
+ // properly escape the parameters to be part of a data-uri
11
+ // + escaping single quote (') as it is used to surround the data-uri: url('...')
12
+ const escapedParameters = encodeURIComponent(parameters).replace(/'/g, function(char) {
13
+ return escape(char);
14
+ });
15
+
16
+ // embed parameter variables as plain-text string into css
17
+ const parameterStyleRule = "\n/* Inline theming parameters */\n#sap-ui-theme-" +
18
+ options.library.name.replace(/\./g, "\\.") +
19
+ "{background-image:url('data:text/plain;utf-8," + escapedParameters + "')}\n";
20
+
21
+ // embed parameter variables as plain-text string into css
22
+ result.css += parameterStyleRule;
23
+ if (options.rtl) {
24
+ result.cssRtl += parameterStyleRule;
25
+ }
26
+ if (options.cssVariables) {
27
+ // for the css variables build we just add it to the variables
28
+ result.cssVariables += parameterStyleRule;
29
+ }
30
+
31
+ return result;
32
+ }
33
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "less-openui5",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "Build OpenUI5 themes with Less.js",
5
5
  "author": {
6
6
  "name": "SAP SE",
@@ -23,8 +23,8 @@
23
23
  },
24
24
  "scripts": {
25
25
  "lint": "eslint ./",
26
- "unit": "mocha test/*.js",
27
- "unit-debug": "mocha --inspect --inspect-brk test/*.js",
26
+ "unit": "mocha test/*.js test/lib/**",
27
+ "unit-debug": "mocha --inspect --inspect-brk test/*.js test/lib/**",
28
28
  "coverage": "nyc npm run unit",
29
29
  "test": "npm run lint && npm run coverage && npm run depcheck",
30
30
  "preversion": "npm test",
@@ -52,10 +52,10 @@
52
52
  "lib/thirdparty/**"
53
53
  ],
54
54
  "check-coverage": true,
55
- "statements": 90,
55
+ "statements": 95,
56
56
  "branches": 85,
57
- "functions": 90,
58
- "lines": 90,
57
+ "functions": 100,
58
+ "lines": 95,
59
59
  "watermarks": {
60
60
  "statements": [
61
61
  70,
@@ -82,17 +82,17 @@
82
82
  "url": "git@github.com:SAP/less-openui5.git"
83
83
  },
84
84
  "dependencies": {
85
- "clone": "^2.1.0",
86
- "css": "^3.0.0",
85
+ "@adobe/css-tools": "^4.0.1",
86
+ "clone": "^2.1.2",
87
87
  "mime": "^1.6.0"
88
88
  },
89
89
  "devDependencies": {
90
- "depcheck": "^1.4.0",
91
- "eslint": "^7.22.0",
90
+ "depcheck": "^1.4.3",
91
+ "eslint": "^7.32.0",
92
92
  "eslint-config-google": "^0.14.0",
93
- "graceful-fs": "^4.2.6",
94
- "mocha": "^8.3.2",
93
+ "graceful-fs": "^4.2.10",
94
+ "mocha": "^8.4.0",
95
95
  "nyc": "^15.1.0",
96
- "sinon": "^9.2.4"
96
+ "sinon": "^11.1.2"
97
97
  }
98
98
  }