Skip to content

Instantly share code, notes, and snippets.

@MagicDuck
Last active July 12, 2017 21:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MagicDuck/5061fbf17406526521dae3771a4803ad to your computer and use it in GitHub Desktop.
Save MagicDuck/5061fbf17406526521dae3771a4803ad to your computer and use it in GitHub Desktop.
webpack Compilation.js patch
"use strict";
/**
* processDependenciesBlockForChunk() would go into insane recursion in the old code, this patch fixes that up
* by using a stack to process the work instead; it also checks module membership into chunk.modules using a Set
* to speed things up.
*
* Original code license:
* MIT License http://www.opensource.org/licenses/mit-license.php
* Author Tobias Koppers @sokra
*/
var Compilation = require("webpack/lib/Compilation");
Compilation.prototype.processDependenciesBlockForChunk = function processDependenciesBlockForChunk(block, chunk) {
for (let c of this.chunks) {
c.__modulesSet = new Set(c.modules);
}
var workStack = [{block: block, chunk: chunk}];
while(workStack.length > 0) {
var workUnit = workStack.pop();
this.__processBlock(workUnit.block, workUnit.chunk, workStack);
}
for (let c of this.chunks) {
delete c.__modulesSet;
}
};
Compilation.prototype.__processBlock = function __processBlock(block, chunk, workStack) {
if(block.variables) {
for (let v of block.variables) {
for (let dep of v.dependencies) {
this.__processDependency(chunk, dep, workStack);
}
}
}
if(block.dependencies) {
for (let dep of block.dependencies) {
this.__processDependency(chunk, dep, workStack);
}
}
if(block.blocks) {
for (let b of block.blocks) {
var c;
if(!b.chunks) {
c = this.addChunk(b.chunkName, b.module, b.loc);
c.__modulesSet = new Set(c.modules);
b.chunks = [c];
c.addBlock(b);
} else {
c = b.chunks[0];
}
chunk.addChunk(c);
c.addParent(chunk);
workStack.push({block: b, chunk: c});
}
}
};
Compilation.prototype.__processDependency = function __processDependency(chunk, d, workStack) {
if(!d.module) {
return;
}
if(typeof d.module.index !== "number") {
d.module.index = this.nextFreeModuleIndex++;
}
if(d.weak) {
return;
}
if(d.module.error) {
d.module = null;
return;
}
// Note: we check memebership using a set here to improve performance
if(!chunk.__modulesSet.has(d.module)) {
chunk.__modulesSet.add(d.module);
chunk.modules.push(d.module);
d.module.addChunk(chunk);
workStack.push({chunk: chunk, block: d.module});
}
if(typeof d.module.index2 !== "number") {
d.module.index2 = this.nextFreeModuleIndex2++;
}
};
var LoadersList = require("webpack-core/lib/LoadersList");
// empty array returned when there are no matches
// this improves performance due to not having to allocate memory for each empty array
var NO_MATCHES = [];
var _match = LoadersList.prototype.match;
/**
* determines the list of loaders matched by the given request (str)
* @param {string} str request string
* @return {[loader]} loaders
*/
LoadersList.prototype.match = function match(str) {
if (this.list.length === 0) {
return NO_MATCHES;
}
this.list._matchCache = this.list._matchCache || new Map();
var result = this.list._matchCache.get(str);
if (!result) {
result = _match.call(this, str);
this.list._matchCache.set(str, result);
}
return result;
};
var MappingsContext = require("source-list-map").MappingsContext;
/**
* Note: this code was copied over from the original implementation and
* modified to do lookups using a Map to get O(1) amortized performance instead of indexOf(), which is O(n)
*/
MappingsContext.prototype.ensureSource = function(source, originalSource) {
this.__sourcesIdxMap = this.__sourcesIdxMap || new Map();
var idx = this.__sourcesIdxMap.get(source);
if(idx !== undefined) {
return idx;
}
// old code follows:
// originally lookup was done using indexOf
// var idx = this.sources.indexOf(source);
idx = this.sources.length;
this.sources.push(source);
this.__sourcesIdxMap.set(source, idx);
this.sourcesContent.push(originalSource);
if(typeof originalSource === "string")
this.hasSourceContent = true;
return idx;
};
"use strict";
/**
* enhancements to bad/slow implementations of members of Module
*
* Original code license:
* MIT License http://www.opensource.org/licenses/mit-license.php
* Author Tobias Koppers @sokra
*/
var Module = require("webpack/lib/Module");
/**
* a slightly faster version of Module.prototype.moduleRewriteChunkInReasons()
* (unfortunatelly, not a ton better, I wish we didn't need this)
* @param {webpack Module} mod
* @param {webpack Chunk} oldChunk
* @param {webpack Chunk array} newChunks
*/
Module.prototype.rewriteChunkInReasons = function(oldChunk, newChunks) {
this.reasons.forEach(function(r) {
var oldChunkIdx = (r.chunks || r.module.chunks).indexOf(oldChunk);
if (oldChunkIdx < 0) {
return;
}
if (!r.chunks) {
r.chunks = r.module.chunks.slice();
}
r.chunks.splice(oldChunkIdx, 1);
for (let c of newChunks) {
if (r.chunks.indexOf(c) < 0) {
r.chunks.push(c);
}
}
});
};
"use strict";
/**
* overrides original RemoveParentModulesPlugin with a more efficient algorithm
* The idea of this plugin is to simply remove a module from a chunk if it occurs in all its eventual parents,
* since it's assumed that at least one of the parents has been loaded before we load the child chunk
*/
var RemoveParentModulesPlugin = require("webpack/lib/optimize/RemoveParentModulesPlugin");
/**
* checks if all eventual parents of a given childChunk occur in chunkSet
* If computeEventualParents is true, the set of eventual parents is returned as an array
*
* @param {Chunk[]} allChunks - all the chunks in the graph
* @param {Chunk} childChunk
* @param {Set<Chunk>} chunkSet
* @param {boolean} computeEventualParents
* @return {boolean|array}
*/
function allEventualChunkParentsInSet(allChunks, childChunk, chunkSet, computeEventualParents) {
// if childChunk is an entry chunk, we're done
if (childChunk.parents.length === 0) {
return false;
}
// set up visited flag
for (let chunk of allChunks) {
chunk._rpm_visited = chunk._rpm_mark = chunkSet.has(chunk);
}
var eventualParents;
if (computeEventualParents) {
eventualParents = new Set(childChunk.parents.filter(p => p._rpm_mark));
}
var chunkStack = childChunk.parents.filter(p => !p._rpm_visited);
while (chunkStack.length > 0) {
var chunk = chunkStack.pop();
if (chunk.parents.length === 0) {
// we reached an entry point (which is not in chunkSet), bail out
return false;
}
chunk._rpm_visited = true;
for (let parent of chunk.parents) {
if (!parent._rpm_visited) {
chunkStack.push(parent);
}
if (eventualParents && parent._rpm_mark) {
eventualParents.add(parent);
}
}
}
return eventualParents ? Array.from(eventualParents) : true;
}
RemoveParentModulesPlugin.prototype.apply = function(compiler) {
compiler.plugin("this-compilation", function(compilation) {
compilation.plugin(["optimize-chunks", "optimize-extracted-chunks"], function(chunks) {
if (chunks.length <= 1) {
return;
}
// assign each chunk a unique id
chunks.forEach(function(chunk, index) {
chunk._rpm_id = index;
});
// get a set of the unique modules which occur in more than 1 chunk
var modulesSet = new Set();
chunks.forEach(function(chunk) {
chunk.modules.forEach(function(module) {
if (module.chunks.length > 1) {
modulesSet.add(module);
}
});
});
// construct set of unique 'module removing problems' to solve
var problemsMap = new Map();
for (let module of modulesSet) {
var key = module.chunks.map(function(chunk) {
return chunk._rpm_id;
}).sort().join(",");
var problem = problemsMap.get(key);
if (!problem) {
problem = {
chunksSet: new Set(module.chunks),
modulesSet: new Set()
};
problemsMap.set(key, problem);
}
problem.modulesSet.add(module);
}
// solve each problem
for (let problem of problemsMap.values()) {
var removedChunksSet = new Set();
for (let chunk of problem.chunksSet) {
if (allEventualChunkParentsInSet(chunks, chunk, problem.chunksSet, false)) {
// we can remove problem.modulesSet modules from this chunk
removedChunksSet.add(chunk);
problem.chunksSet.delete(chunk);
chunk._rpm_modulesToRemove = chunk._rpm_modulesToRemove || [];
chunk._rpm_modulesToRemove.push(problem.modulesSet);
}
}
if (removedChunksSet.size > 0) {
// compute eventual parents of removed chunks (so we can rewrite module reasons)
var newChunks = Array.from(problem.chunksSet);
for (let chunk of removedChunksSet) {
chunk._rpm_eventualParents = newChunks.length === 1
? newChunks
: allEventualChunkParentsInSet(chunks, chunk, problem.chunksSet, true);
}
// update module.chunks for each modules in problem.modulesSet
for (let module of problem.modulesSet) {
// update chunks array, preserving exiting array
module.chunks.length = 0; // clear array
for (let chunk of problem.chunksSet) {
module.chunks.push(chunk);
}
for (let chunk of removedChunksSet) {
module.rewriteChunkInReasons(chunk, chunk._rpm_eventualParents);
}
}
}
}
// update chunks.modules for any chunks with chunk._rpm_modulesToRemove
for (let chunk of chunks) {
if (chunk._rpm_modulesToRemove) {
var chunkMods = chunk.modules.slice();
chunk.modules.length = 0; // clear array
for (let mod of chunkMods) {
if (!chunk._rpm_modulesToRemove.some(modulesSet => modulesSet.has(mod))) {
chunk.modules.push(mod);
}
}
}
}
// cleanup
for (let chunk of chunks) {
delete chunk._rpm_id;
delete chunk._rpm_visited;
delete chunk._rpm_modulesToRemove;
delete chunk._rpm_mark;
delete chunk._rpm_eventualParents;
}
});
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment