From 3fedf6ac335871c95e81f60d773ecd0dc49c5738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20R=C3=BChl?= Date: Mon, 25 Jan 2016 15:13:19 +0100 Subject: [PATCH] Implemented new chunk sorting mode 'dependency', which attempts to order chunks based on their relation within the dependency tree as devised by Webpack. Added unit test for new sorting mode. Fixed a few typos in other unit tests...sorry, couldn't resist ;-) --- README.md | 2 +- index.js | 11 ++++++ lib/chunksorter.js | 72 +++++++++++++++++++++++++++++++++++ package.json | 3 +- spec/HtmlWebpackPluginSpec.js | 32 +++++++++++++++- 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 lib/chunksorter.js 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 () { /