Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ordering inputFiles (all conflicts fixed) #122

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,48 @@ The structure of `output.js` will be as follows:
// - footer
```

##### Ordering of inputFiles

Several orderings may be appropriate for `inputFiles`. Some are slightly more
expensive then others, and others are specific to certain scenarios.

By default files matched within `inputFiles` will be sorted lexicographically.
This is to ensure that between multiple concats, the output remain stable and
also as files are added and removed, that the output file change in predictable
(diff friendly) ways.

A common alternative is to sort these files based on the matcher precedent.

Given:

```
inputFiles: ['foo/*.global.css', 'foo/*.overrides.css']
```

One may want all .global.css files to precede .overrides.css files, while
within each glob preserving lexicographical order

To accomplish this, broccoli-concat provides an alternative comparator.

```js
var concat = require('broccoli-concat');
var matchComparator = require('broccoli-concat/comparators/match');

concat(input, {
outputFile: 'styles.css',
inputFiles: ['foo/*.global.css', 'foo/*.overrides.css'],
inputFilesComparator: matchComparator
})
```

If an alternative ordering strategy is required, the signature of inputFilesComparator is:

* firstFile: {String} fileName
* secondFile: {String} fileName
* matchers: {Array} matching the inputFiles argument, but where each entry has been converted into a minimatch instance

For an example see: [comparators/match](comparators/match)

#### Debug Usage

*note: this is intended for debugging purposes only, and will most likely negatively affect your build performace is left enabled*
Expand Down
22 changes: 22 additions & 0 deletions comparators/match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

module.exports = function comparator(x, y, inputFileMatchers) {
if (x === y) { return 0; }
if (inputFileMatchers.length < 2) { return 0; }

for (var i = 0; i < inputFileMatchers.length; i++) {
var matcher = inputFileMatchers[i];

if (matcher.match(x) && matcher.match(y)) {
continue
} else if (matcher.match(x)) {
return -1;
} else if (matcher.match(y)) {
return 1;
} else {
return 1;
}
}

return 0;
}
14 changes: 14 additions & 0 deletions concat.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var omit = require('lodash.omit');
var uniq = require('lodash.uniq');
var walkSync = require('walk-sync');
var ensurePosix = require('ensure-posix-path');
var Minimatch = require('minimatch').Minimatch;

var ensureNoGlob = require('./lib/utils/ensure-no-glob');
var isDirectory = require('./lib/utils/is-directory');
Expand Down Expand Up @@ -47,6 +48,9 @@ class Concat extends Plugin {
this.sourceMapConfig = omit(options.sourceMapConfig || {}, 'enabled');
this.allInputFiles = uniq([].concat(options.headerFiles || [], options.inputFiles || [], options.footerFiles || []));
this.inputFiles = options.inputFiles;
this.inputFilesMatchers = (options.inputFiles || []).map(function(string) {
return new Minimatch(string);
});
this.outputFile = options.outputFile;
this.allowNone = options.allowNone;
this.header = options.header;
Expand All @@ -55,6 +59,7 @@ class Concat extends Plugin {
this.footer = options.footer;
this.footerFiles = options.footerFiles;
this.separator = (options.separator != null) ? options.separator : '\n';
this.inputFilesComparator = options.inputFilesComparator;

ensureNoGlob('headerFiles', this.headerFiles);
ensureNoGlob('footerFiles', this.footerFiles);
Expand Down Expand Up @@ -262,6 +267,15 @@ class Concat extends Plugin {
throw new Error('Concat: nothing matched [' + this.inputFiles + ']');
}

var inputFilesComparator = this.inputFilesComparator;
if (inputFilesComparator) {
var inputFilesMatchers = this.inputFilesMatchers;

files = files.slice().sort(function(x, y) {
return inputFilesComparator(x, y, inputFilesMatchers);
});
}

files.forEach(function(file) {
beginSection();
this.concat.addFile(file.replace(posixInputPath + '/', ''));
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"lodash.merge": "^4.3.0",
"lodash.omit": "^4.1.0",
"lodash.uniq": "^4.2.0",
"minimatch": "3.0.4",
"walk-sync": "^0.3.1"
},
"engines": {
Expand Down
124 changes: 124 additions & 0 deletions test/concat-ordering-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
'use strict';

var concat = require('..');
var fs = require('fs-extra');
var path = require('path');
var broccoli = require('broccoli');
var validateSourcemap = require('sourcemap-validator');
var Minimatch = require('minimatch').Minimatch;
var expectFile = require('./helpers/expect-file');

var chai = require('chai');
chai.config.truncateThreshold = 0;
var chaiAsPromised = require('chai-as-promised');

chai.use(chaiAsPromised);

var expect = chai.expect;

var fixtures = path.join(__dirname, 'fixtures');
var firstFixture = path.join(fixtures, 'first');

describe('sourcemap-concat', function() {
var builder;

afterEach(function() {
if (builder) {
return builder.cleanup();
}
});

describe('inputFiles optional postProcessing', function() {
var matchComparator = require('../comparators/match');

describe('inputFiles matcher matchComparator', function() {

it('scenario 1', function() {
var globs = [
new Minimatch('a*'),
new Minimatch('b*')
];

var files = ['zasdf', 'asdf', 'basdf'].sort(function(a, b) {
return matchComparator(a,b, globs);
});

expect(files).to.eql(['asdf', 'basdf', 'zasdf']);
});

it('scenario 2', function() {
var globs = [
new Minimatch('b*'),
new Minimatch('a*')
];

var files = ['zasdf', 'asdf', 'basdf'].sort(function(a, b) {
return matchComparator(a,b, globs);
});

expect(files).to.eql(['basdf', 'asdf', 'zasdf']);
});
});

it('response order first -> second', function() {
var firstMatcher = new Minimatch('**/first*');
var secondMatcher = new Minimatch('**/second*');

var node = concat(firstFixture, {
inputFiles: ['inner/**/*.js'],
outputFile: 'inner-first-second.js',
inputFilesComparator: function(x, y) {
return matchComparator(x,y, [firstMatcher, secondMatcher]);
}
});

builder = new broccoli.Builder(node);
return builder.build().then(function(result) {
expectFile('inner-first-second.js').in(result);
expectFile('inner-first-second.map').in(result);
expectValidSourcemap('inner-first-second.js').in(result);
});
});

it('response order second -> first', function() {
var firstMatcher = new Minimatch('**/first*');
var secondMatcher = new Minimatch('**/second*');

var node = concat(firstFixture, {
inputFiles: ['inner/**/*.js'],
outputFile: 'inner-second-first.js',
inputFilesComparator: function(x, y) {
return matchComparator(x, y, [secondMatcher, firstMatcher]);
}
});

builder = new broccoli.Builder(node);
return builder.build().then(function(result) {
expectFile('inner-second-first.js').in(result);
expectFile('inner-second-first.map').in(result);
expectValidSourcemap('inner-second-first.js').in(result);
});
});
});
});

function expectValidSourcemap(jsFilename, mapFilename) {
return {
in: function (result, subdir) {
if (!subdir) {
subdir = '.';
}

if (!mapFilename) {
mapFilename = jsFilename.replace(/\.js$/, '.map');
}

expectFile(jsFilename).in(result, subdir);
expectFile(mapFilename).in(result, subdir);

var actualMin = fs.readFileSync(path.join(result.directory, subdir, jsFilename), 'utf-8');
var actualMap = fs.readFileSync(path.join(result.directory, subdir, mapFilename), 'utf-8');
validateSourcemap(actualMin, actualMap, {});
}
};
}
12 changes: 12 additions & 0 deletions test/expected/inner-first-second.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/expected/inner-first-second.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions test/expected/inner-second-first.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/expected/inner-second-first.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.