diff --git a/README.md b/README.md index 3e8250f..ee9a145 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,12 @@ Type: `function` Modify files details before the manifest is created. [more details](#hooks-options) +### `sort` + +Type: `function`
+Default: in dependency order + +Sort files before they are passed to reduce. [more details](#hooks-options) ### `reduce` @@ -119,7 +125,7 @@ Create the manifest. It can return anything as long as it's serialisable by `JSO ## Hooks Options -`filter`, `map`, `reduce` takes as an input an Object with the following properties: +`filter`, `map`, `sort`, `reduce` takes as an input an Object with the following properties: ### `path` diff --git a/lib/plugin.js b/lib/plugin.js index 95790e4..2912f22 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -1,6 +1,7 @@ var path = require('path'); var fse = require('fs-extra'); var _ = require('lodash'); +var toposort = require('toposort'); function ManifestPlugin(opts) { this.opts = _.assign({ @@ -44,8 +45,34 @@ ManifestPlugin.prototype.apply = function(compiler) { compiler.plugin('emit', function(compilation, compileCallback) { var stats = compilation.getStats().toJson(); + var nodeMap = {}; - var files = compilation.chunks.reduce(function(files, chunk) { + compilation.chunks.forEach(function (chunk) { + nodeMap[chunk.id] = chunk; + }); + + // Next, we add an edge for each parent relationship into the graph + var edges = []; + + compilation.chunks.forEach(function (chunk) { + if (chunk.parents) { + // Add an edge for each parent (parent -> child) + chunk.parents.forEach(function (parentId) { + // webpack2 chunk.parents are chunks instead of string id(s) + var parentChunk = _.isObject(parentId) ? parentId : nodeMap[parentId]; + // If the parent chunk does not exist (e.g. because of an excluded chunk) + // we ignore that parent + if (parentChunk) { + edges.push([parentChunk, chunk]); + } + }); + } + }); + + // We now perform a topological sorting on the input chunks and built edges + var chunks = toposort.array(compilation.chunks, edges); + + var files = chunks.reduce(function(files, chunk) { return chunk.files.reduce(function (files, path) { // Don't add hot updates to manifest if (path.indexOf('hot-update') >= 0) { @@ -118,18 +145,9 @@ ManifestPlugin.prototype.apply = function(compiler) { files = files.map(this.opts.map); } - files = files.sort(function (fileA, fileB) { - var a = fileA.name; - var b = fileB.name; - - if (a < b) { - return -1; - } else if (a > b) { - return 1; - } else { - return 0; - } - }); + if (this.opts.sort) { + files = files.sort(this.opts.sort); + } var manifest; if (this.opts.reduce) { diff --git a/package.json b/package.json index 10a7935..35bb30b 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "homepage": "https://github.com/danethurber/webpack-manifest-plugin", "dependencies": { "fs-extra": "^0.30.0", - "lodash": ">=3.5 <5" + "lodash": ">=3.5 <5", + "toposort": "^1.0.3" }, "nyc": { "reporter": [ diff --git a/spec/fixtures/common.js b/spec/fixtures/common.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/spec/fixtures/common.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/spec/fixtures/main.js b/spec/fixtures/main.js new file mode 100644 index 0000000..d3ade1d --- /dev/null +++ b/spec/fixtures/main.js @@ -0,0 +1,2 @@ +require('./common.js'); +console.log('main'); diff --git a/spec/fixtures/util.js b/spec/fixtures/util.js new file mode 100644 index 0000000..8b37142 --- /dev/null +++ b/spec/fixtures/util.js @@ -0,0 +1,2 @@ +require('./common.js'); +console.log('util'); diff --git a/spec/plugin.spec.js b/spec/plugin.spec.js index 60a82ca..8b3e532 100644 --- a/spec/plugin.spec.js +++ b/spec/plugin.spec.js @@ -90,6 +90,32 @@ describe('ManifestPlugin', function() { }); }); + it('multiple file output is dependency ordered', function(done) { + webpackCompile({ + context: __dirname, + entry: { + main: './fixtures/main.js', + vendor: './fixtures/util.js' + }, + plugins: [ + new plugin({ + seed: [], + reduce: function (manifest, file) { + return manifest.concat(file.name); + } + }), + new webpack.optimize.CommonsChunkPlugin({ + name: 'common', + filename: 'common.js' + }) + ] + }, {}, function(manifest) { + expect(manifest).toEqual(['common.js', 'vendor.js', 'main.js']); + + done(); + }); + }); + it('works with hashes in the filename', function(done) { webpackCompile({ context: __dirname, @@ -581,6 +607,35 @@ describe('ManifestPlugin', function() { }); }); + describe('sort', function() { + it('should allow ordering of output', function(done) { + webpackCompile({ + context: __dirname, + entry: { + one: './fixtures/file.js', + two: './fixtures/file-two.js' + }, + output: { + filename: '[name].js' + } + }, { + manifestOptions: { + seed: [], + sort: function(file, i) { + return 1; + }, + reduce: function (manifest, file) { + return manifest.concat(file.name); + } + } + }, function(manifest, stats) { + expect(manifest).toEqual(['two.js', 'one.js']); + + done(); + }); + }); + }); + describe('reduce', function() { it('should generate custom manifest', function(done) { webpackCompile({