Merge pull request #192 from ruehl/dependency-sort

Implemented new chunk sorting mode 'dependency'
This commit is contained in:
Jan Nicklas 2016-01-28 00:08:28 +01:00
commit 0bdc43f478
5 changed files with 142 additions and 12 deletions

View File

@ -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:

View File

@ -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) {
@ -280,17 +281,21 @@ HtmlWebpackPlugin.prototype.addFileToAssets = function (filename, compilation) {
HtmlWebpackPlugin.prototype.sortChunks = function (chunks, sortMode) {
// Sort mode auto by default:
if (typeof sortMode === 'undefined' || sortMode === 'auto') {
return chunks.sort(function orderEntryLast (a, b) {
if (a.entry !== b.entry) {
return b.entry ? 1 : -1;
} else {
return b.id - a.id;
}
});
return chunkSorter.auto(chunks);
}
// Sort mode 'dependency':
if (sortMode === 'dependency') {
var sortResult = chunkSorter.dependency(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;
return chunkSorter.none(chunks);
}
// Custom function
if (typeof sortMode === 'function') {

96
lib/chunksorter.js Normal file
View File

@ -0,0 +1,96 @@
'use strict';
var toposort = require('toposort');
/*
Sorts dependencies between chunks by their "parents" attribute.
This function 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.
@param {Array} 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 {Array} 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).
*/
module.exports.dependency = function (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;
};
/**
* Sorts the chunks based on the chunk id.
*
* @param {Array} chunks the list of chunks to sort
* @return {Array} The sorted list of chunks
*/
module.exports.id = function (chunks) {
return chunks.sort(function orderEntryLast (a, b) {
if (a.entry !== b.entry) {
return b.entry ? 1 : -1;
} else {
return b.id - a.id;
}
});
};
/**
* Performs identity mapping (no-sort).
* @param {Array} chunks the chunks to sort
* @return {Array} The sorted chunks
*/
module.exports.none = function (chunks) {
return chunks;
};
/**
* Defines the default sorter.
*/
module.exports.auto = module.exports.id;

View File

@ -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"
}
}

View File

@ -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 () {
/<script src="common_bundle.js">.+<script src="util_bundle.js">.+<script src="index_bundle.js">/], null, done);
});
it('should short the chunks with custom (alphabetical) order', function (done) {
it('should sort the chunks in custom (alphabetical) order', function (done) {
testHtmlPlugin({
entry: {
b: path.join(__dirname, 'fixtures/index.js'),
@ -891,4 +891,32 @@ describe('HtmlWebpackPlugin', function () {
]
}, [/<script src="a_bundle.js">.+<script src="b_bundle.js">.+<script src="c_bundle.js">/], null, done);
});
it('should sort the chunks by chunk dependencies', function (done) {
testHtmlPlugin({
entry: {
util: path.join(__dirname, 'fixtures/util.js'),
theme: path.join(__dirname, 'fixtures/theme.js')
},
output: {
path: OUTPUT_DIR,
filename: '[name]_bundle.js'
},
module: {
loaders: [
{ test: /\.css$/, loader: 'css-loader' }
]
},
plugins: [
new CommonsChunkPlugin({
name: 'common',
filename: 'common_bundle.js'
}),
new HtmlWebpackPlugin({
chunksSortMode: 'dependency'
})
]
}, [
/<script src="common_bundle.js">.+<script src="theme_bundle.js">.+<script src="util_bundle.js">/], null, done);
});
});