Skip to content

Commit

Permalink
Merge pull request #10 from SAP/feature-belize-build
Browse files Browse the repository at this point in the history
BREAKING: Rewrite theme build and support scopes (Belize Themes)
  • Loading branch information
matz3 authored Mar 23, 2017
2 parents 8aed03f + 186ceed commit fd1e9ce
Show file tree
Hide file tree
Showing 76 changed files with 3,036 additions and 196 deletions.
52 changes: 37 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ npm install less-openui5
```js
var lessOpenUI5 = require('less-openui5');

lessOpenUI5.build({
// Create a builder instance
var builder = new lessOpenUI5.Builder();

// Build a theme
builder.build({
lessInput: '@var: #ffffff; .class { color: @var; float: left }'
}, function(err, result) {
})
.then(function(result) {

console.log(result.css); // => regular css
/*
Expand Down Expand Up @@ -48,22 +53,41 @@ lessOpenUI5.build({
[]
*/

// Clear builder cache when finished to cleanup memory
builder.clearCache();

});
```

## API

### build(options, callback)
### new Builder()

Creates a new `Builder` instance.

It caches build results to only rebuild a theme when related files have been changed.
This is mainly relevant when building themes as part of a server middleware like [`connect-openui5`](https://github.com/SAP/connect-openui5).

### .build(options)
Returns a Promise resolving with a [`result`](#result) object.

#### options

##### lessInput

*Required*
*Required (either `lessInput` or `lessInputPath`, not both)*
Type: `string`

Input less content.

##### lessInputPath

*Required (either `lessInput` or `lessInputPath`, not both)*
Type: `string`

Path to input less file.
When `rootPaths` is given this must be a relative path inside one of the provided `rootPaths`, otherwise just a regular filesystem path.

##### rtl

Type: `boolean`
Expand All @@ -79,9 +103,6 @@ Root paths to use for import directives.

This option differs from the less `compiler.paths` option.
It is useful if less files are located in separate folders but referenced as they would all be in one.
If `rootPaths` are provided and a file can not be found, the `compiler.paths` option will be used instead.

*Note:* `parser.filename` has to be set to the path of the `input` file in order to get this working.

###### Example

Expand Down Expand Up @@ -128,35 +149,36 @@ Type `string`
Dot-separated name of the corresponding library.
It will be used to inline the `variables` JSON as data-uri which can be retrieved at runtime.

#### callback(error, result)

*Required*
Type: `function`
#### result

##### result.css
##### css

Type: `string`

Regular css output.

##### result.cssRtl
##### cssRtl

Type: `string`

Mirrored css for right-to-left support (if rtl option was enabled).

##### result.variables
##### variables

Type: `object`

Key-value map of all global less variables (without @ prefix).

##### result.imports
##### imports

Type: `array`

Paths to files imported via import directives.

### .clearCache()
Clears all cached build results.
Use this method to prevent high memory consumption when building many themes within the same process.

## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).

Expand Down
162 changes: 162 additions & 0 deletions lib/diff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright 2017 SAP SE.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http: //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific
// language governing permissions and limitations under the License.

'use strict';

// Regular expression to match type of property in order to check whether
// it possibly contains color values.
var rProperties = /(color|background|border|text|outline)(?!\-(width|radius|offset|style|align|overflow|transform))(\-(color|shadow|image))?/;

function selectorEquals(s1, s2) {

// Make sure there is the same number of select parts
if (s1.length !== s2.length) {
return false;
}

// Check if all the parts are the same strings
for (var i = 0; i < s1.length; i++) {
if (s1[i] !== s2[i]) {
return false;
}
}

return true;
}

function Diffing(oBase, oCompare) {
this.oBase = oBase;
this.oCompare = oCompare;

this.oDiff = {
type: "stylesheet",
stylesheet: {
rules: []
},
};

this.oStack = {
type: "stylesheet",
stylesheet: {
rules: []
},
};
}

Diffing.prototype.diffRules = function(oBaseRules, oCompareRules) {
var aDiffRules = [];
var iBaseNode, iCompareNode;

for (iBaseNode = 0, iCompareNode = 0; iBaseNode < oBaseRules.length; iBaseNode++, iCompareNode++) {
var oBaseNode = oBaseRules[iBaseNode];
var oCompareNode = oCompareRules[iCompareNode];
var oDiffNode = null;

// Add all different compare nodes to stack and check for next one
while (oBaseNode.type !== oCompareNode.type) {
this.oStack.stylesheet.rules.push(oCompareNode);
iCompareNode++;
oCompareNode = oCompareRules[iCompareNode];
}

if (oBaseNode.type === "comment") {
var sBaseComment = oBaseNode.comment;
var sCompareComment = oCompareNode.comment;

if (sBaseComment !== sCompareComment) {
oDiffNode = oCompareNode;
}
}

if (oBaseNode.type === "rule") {

// Add all rules with different selector to stack and check for next one
while (!selectorEquals(oBaseNode.selectors, oCompareNode.selectors)) {
this.oStack.stylesheet.rules.push(oCompareNode);
iCompareNode++;
oCompareNode = oCompareRules[iCompareNode];
}

var aBaseDeclarations = oBaseNode.declarations;
var aCompareDeclarations = oCompareNode.declarations;
for (var j = 0; j < aBaseDeclarations.length; j++) {
var oBaseDeclaration = aBaseDeclarations[j];
var oCompareDeclaration = aCompareDeclarations[j];

if (oBaseDeclaration.type === "declaration") {

// TODO: Also check for different node and add to stack???
if (oBaseDeclaration.type === oCompareDeclaration.type) {

if (oBaseDeclaration.property === oCompareDeclaration.property) {

// Always add color properties to diff to prevent unexpected CSS overrides
// due to selectors with more importance
if (oBaseDeclaration.value !== oCompareDeclaration.value
|| oCompareDeclaration.property.match(rProperties)) {

// Add compared rule to diff
if (!oDiffNode) {
oDiffNode = oCompareNode;
oDiffNode.declarations = [];
}
oDiffNode.declarations.push(oCompareDeclaration);
}

}
}
}
}

} else if (oBaseNode.type === "media") {

var aMediaDiffRules = this.diffRules(oBaseNode.rules, oCompareNode.rules);

if (aMediaDiffRules.length > 0) {
oDiffNode = oCompareNode;
oDiffNode.rules = aMediaDiffRules;
}

}

if (oDiffNode) {
aDiffRules.push(oDiffNode);
}

}

// Add all leftover compare nodes to stack
for (; iCompareNode < oCompareRules.length; iCompareNode++) {
this.oStack.stylesheet.rules.push(oCompareRules[iCompareNode]);
}

return aDiffRules;
};

Diffing.prototype.run = function() {
var oBaseRules = this.oBase.stylesheet.rules;
var oCompareRules = this.oCompare.stylesheet.rules;

this.oDiff.stylesheet.rules = this.diffRules(oBaseRules, oCompareRules);

return {
diff: this.oDiff,
stack: this.oStack
};
};


module.exports = function diff(oBase, oCompare) {
return new Diffing(oBase, oCompare).run();
};
81 changes: 81 additions & 0 deletions lib/fileUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2017 SAP SE.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http: //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific
// language governing permissions and limitations under the License.

'use strict';

var fs = require('fs');
var path = require('path');

function statFile(filePath) {
return new Promise(function(resolve, reject) {
fs.stat(filePath, function(err, stat) {
// No rejection here as it is ok if the file was not found
resolve(stat ? { path: filePath, stat: stat } : null);
});
});
}

function statFiles(files) {
return Promise.all(files.map(statFile));
}

function findFile(filePath, rootPaths) {
if (rootPaths && rootPaths.length > 0) {
return statFiles(
rootPaths.map(function(rootPath) {
return path.join(rootPath, filePath);
})
).then(function(results) {
for (var i = 0; i < results.length; i++) {
if (results[i] !== null) {
return results[i];
}
}

// File not found
return null;
});
} else {
return statFile(filePath);
}
}

function readFile(lessInputPath, rootPaths) {
return findFile(lessInputPath, rootPaths).then(function(fileInfo) {
if (!fileInfo) {
return null;
}
return new Promise(function(resolve, reject) {
fs.readFile(fileInfo.path, {
encoding: 'utf8'
}, function(fileErr, content) {
if (fileErr) {
reject(fileErr);
} else {
resolve({
content: content,
path: fileInfo.path,
localPath: lessInputPath,
stats: fileInfo.stats
});
}
});
});
});
}

module.exports.statFile = statFile;
module.exports.statFiles = statFiles;
module.exports.findFile = findFile;
module.exports.readFile = readFile;
Loading

0 comments on commit fd1e9ce

Please sign in to comment.