diff --git a/README.md b/README.md index 3844683..ab7b125 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Allowed values are as follows: - `cache`: `true | false` if `true` (default) try to emit the file only if it was changed. - `showErrors`: `true | false` if `true` (default) errors details will be written into the html page. - `chunks`: Allows you to add only some chunks (e.g. only the unit-test chunk) -- `chunksSortMode`: Allows to controll how chunks should be sorted before they are included to the html. Allowed values: 'none' | 'default' | {function} - default: 'auto' +- `chunksSortMode`: Allows to control how chunks should be sorted before they are included to the html. Allowed values: 'none' | 'auto' | 'dependency' | {function} - default: 'auto' - `excludeChunks`: Allows you to skip some chunks (e.g. don't add the unit-test chunk) Here's an example webpack config illustrating how to use these options: diff --git a/index.js b/index.js index 2217c3e..e758843 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ var Promise = require('bluebird'); var path = require('path'); var childCompiler = require('./lib/compiler.js'); var prettyError = require('./lib/errors.js'); +var chunkSorter = require('./lib/chunksorter.js'); Promise.promisifyAll(fs); function HtmlWebpackPlugin (options) { @@ -288,6 +289,16 @@ HtmlWebpackPlugin.prototype.sortChunks = function (chunks, sortMode) { } }); } + // Sort mode 'dependency': + if (sortMode === 'dependency') { + var sortResult = chunkSorter().sortChunksByDependencies(chunks); + + if (!sortResult) { + throw new Error('Chunk sorting based on dependencies failed. Please consider custom sort mode.'); + } + + return sortResult; + } // Disabled sorting: if (sortMode === 'none') { return chunks; diff --git a/lib/chunksorter.js b/lib/chunksorter.js new file mode 100644 index 0000000..220f0f5 --- /dev/null +++ b/lib/chunksorter.js @@ -0,0 +1,72 @@ +/* +This module sorts chunks based on their dependencies with each other. +The parent relation between chunks as generated by Webpack for each chunk +is used to define a directed (and hopefully acyclic) graph, which is then +topologically sorted in order to retrieve the correct order in which +chunks need to be embedded into HTML. A directed edge in this graph is +describing a "is parent of" relationship from a chunk to another (distinct) +chunk. Thus topological sorting orders chunks from bottom-layer chunks to +highest level chunks that use the lower-level chunks. +*/ +'use strict'; + +var toposort = require('toposort'); +module.exports = function () { + /* + Sorts dependencies between chunks by their "parents" attribute. + + @param chunks an array of chunks as generated by the html-webpack-plugin. + It is assumed that each entry contains at least the properties "id" + (containing the chunk id) and "parents" (array containing the ids of the + parent chunks). Must not be null/undefined or empty + + @return A topologically sorted version of the input chunks, or null if + no such order could be calculated (e.g. because the chunks and their + parent relations did not define an directed acyclic graph). + */ + function sortChunksByDependencies (chunks) { + if (!chunks) { + return null; + } + + // We build a map (chunk-id -> chunk) for faster access during graph building. + var nodeMap = []; + + chunks.forEach(function (chunk) { + nodeMap[chunk.id] = chunk; + }); + + // Next, we add an edge for each parent relationship into the graph + var edges = []; + + chunks.forEach(function (chunk) { + if (chunk.parents) { + // Add an edge for each parent (parent -> child) + chunk.parents.forEach(function (parentId) { + var parentChunk = nodeMap[parentId]; + + if (!parentChunk) { + return null; // We haven't found the referenced chunk in our map! + } + + edges.push([parentChunk, chunk]); + }); + } + }); + + // We now perform a topological sorting on the input chunks and built edges + var sortedVertices = null; + + try { + sortedVertices = toposort.array(chunks, edges); + } catch (err) { + return null; // Error during sort + } + + return sortedVertices; + } + + return { + sortChunksByDependencies: sortChunksByDependencies + }; +}; diff --git a/package.json b/package.json index d9cc045..a6b9c5a 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "html-minifier": "^1.1.1", "loader-utils": "^0.2.12", "lodash": "^3.10.1", - "pretty-error": "^2.0.0" + "pretty-error": "^2.0.0", + "toposort": "^0.2.12" } } diff --git a/spec/HtmlWebpackPluginSpec.js b/spec/HtmlWebpackPluginSpec.js index c74e942..bdcbb24 100644 --- a/spec/HtmlWebpackPluginSpec.js +++ b/spec/HtmlWebpackPluginSpec.js @@ -842,7 +842,7 @@ describe('HtmlWebpackPlugin', function () { }, ["Child compilation failed:\n Entry module not found: Error: Cannot resolve 'file' or 'directory'"], null, done, true); }); - it('should short the chunks', function (done) { + it('should sort the chunks', function (done) { testHtmlPlugin({ entry: { util: path.join(__dirname, 'fixtures/util.js'), @@ -865,7 +865,7 @@ describe('HtmlWebpackPlugin', function () { /