2017-09-13 07:52:34 +02:00

349 lines
12 KiB
JavaScript
Vendored
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var fs = require('fs');
var ConcatSource = require("webpack-sources").ConcatSource;
var async = require("async");
var ExtractedModule = require("./ExtractedModule");
var Chunk = require("webpack/lib/Chunk");
var OrderUndefinedError = require("./OrderUndefinedError");
var loaderUtils = require("loader-utils");
var validateOptions = require('schema-utils');
var path = require('path');
var NS = fs.realpathSync(__dirname);
var nextId = 0;
function ExtractTextPluginCompilation() {
this.modulesByIdentifier = {};
}
function isInitialOrHasNoParents(chunk) {
return chunk.isInitial() || chunk.parents.length === 0;
}
ExtractTextPlugin.prototype.mergeNonInitialChunks = function(chunk, intoChunk, checkedChunks) {
if(!intoChunk) {
checkedChunks = [];
chunk.chunks.forEach(function(c) {
if(isInitialOrHasNoParents(c)) return;
this.mergeNonInitialChunks(c, chunk, checkedChunks);
}, this);
} else if(checkedChunks.indexOf(chunk) < 0) {
checkedChunks.push(chunk);
chunk.modules.slice().forEach(function(module) {
intoChunk.addModule(module);
module.addChunk(intoChunk);
});
chunk.chunks.forEach(function(c) {
if(isInitialOrHasNoParents(c)) return;
this.mergeNonInitialChunks(c, intoChunk, checkedChunks);
}, this);
}
};
ExtractTextPluginCompilation.prototype.addModule = function(identifier, originalModule, source, additionalInformation, sourceMap, prevModules) {
var m;
if(!this.modulesByIdentifier[identifier]) {
m = this.modulesByIdentifier[identifier] = new ExtractedModule(identifier, originalModule, source, sourceMap, additionalInformation, prevModules);
} else {
m = this.modulesByIdentifier[identifier];
m.addPrevModules(prevModules);
if(originalModule.index2 < m.getOriginalModule().index2) {
m.setOriginalModule(originalModule);
}
}
return m;
};
ExtractTextPluginCompilation.prototype.addResultToChunk = function(identifier, result, originalModule, extractedChunk) {
if(!Array.isArray(result)) {
result = [[identifier, result]];
}
var counterMap = {};
var prevModules = [];
result.forEach(function(item) {
var c = counterMap[item[0]];
var module = this.addModule.call(this, item[0] + (c || ""), originalModule, item[1], item[2], item[3], prevModules.slice());
extractedChunk.addModule(module);
module.addChunk(extractedChunk);
counterMap[item[0]] = (c || 0) + 1;
prevModules.push(module);
}, this);
};
ExtractTextPlugin.prototype.renderExtractedChunk = function(chunk) {
var source = new ConcatSource();
chunk.modules.forEach(function(module) {
var moduleSource = module.source();
source.add(this.applyAdditionalInformation(moduleSource, module.additionalInformation));
}, this);
return source;
};
function isInvalidOrder(a, b) {
var bBeforeA = a.getPrevModules().indexOf(b) >= 0;
var aBeforeB = b.getPrevModules().indexOf(a) >= 0;
return aBeforeB && bBeforeA;
}
function getOrder(a, b) {
var aOrder = a.getOrder();
var bOrder = b.getOrder();
if(aOrder < bOrder) return -1;
if(aOrder > bOrder) return 1;
var aIndex = a.getOriginalModule().index2;
var bIndex = b.getOriginalModule().index2;
if(aIndex < bIndex) return -1;
if(aIndex > bIndex) return 1;
var bBeforeA = a.getPrevModules().indexOf(b) >= 0;
var aBeforeB = b.getPrevModules().indexOf(a) >= 0;
if(aBeforeB && !bBeforeA) return -1;
if(!aBeforeB && bBeforeA) return 1;
var ai = a.identifier();
var bi = b.identifier();
if(ai < bi) return -1;
if(ai > bi) return 1;
return 0;
}
function ExtractTextPlugin(options) {
if(arguments.length > 1) {
throw new Error("Breaking change: ExtractTextPlugin now only takes a single argument. Either an options " +
"object *or* the name of the result file.\n" +
"Example: if your old code looked like this:\n" +
" new ExtractTextPlugin('css/[name].css', { disable: false, allChunks: true })\n\n" +
"You would change it to:\n" +
" new ExtractTextPlugin({ filename: 'css/[name].css', disable: false, allChunks: true })\n\n" +
"The available options are:\n" +
" filename: string\n" +
" allChunks: boolean\n" +
" disable: boolean\n");
}
if(isString(options)) {
options = { filename: options };
} else {
validateOptions(path.resolve(__dirname, './schema/plugin.json'), options, 'Extract Text Plugin');
}
this.filename = options.filename;
this.id = options.id != null ? options.id : ++nextId;
this.options = {};
mergeOptions(this.options, options);
delete this.options.filename;
delete this.options.id;
}
module.exports = ExtractTextPlugin;
function getLoaderObject(loader) {
if (isString(loader)) {
return {loader: loader};
}
return loader;
}
function mergeOptions(a, b) {
if(!b) return a;
Object.keys(b).forEach(function(key) {
a[key] = b[key];
});
return a;
}
function isString(a) {
return typeof a === "string";
}
function isFunction(a) {
return isType('Function', a);
}
function isType(type, obj) {
return Object.prototype.toString.call(obj) === '[object ' + type + ']';
}
ExtractTextPlugin.loader = function(options) {
return { loader: require.resolve("./loader"), options: options };
};
ExtractTextPlugin.prototype.applyAdditionalInformation = function(source, info) {
if(info) {
return new ConcatSource(
"@media " + info[0] + " {",
source,
"}"
);
}
return source;
};
ExtractTextPlugin.prototype.loader = function(options) {
return ExtractTextPlugin.loader(mergeOptions({id: this.id}, options));
};
ExtractTextPlugin.prototype.extract = function(options) {
if(arguments.length > 1) {
throw new Error("Breaking change: extract now only takes a single argument. Either an options " +
"object *or* the loader(s).\n" +
"Example: if your old code looked like this:\n" +
" ExtractTextPlugin.extract('style-loader', 'css-loader')\n\n" +
"You would change it to:\n" +
" ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' })\n\n" +
"The available options are:\n" +
" use: string | object | loader[]\n" +
" fallback: string | object | loader[]\n" +
" publicPath: string\n");
}
if(options.fallbackLoader) {
console.warn('fallbackLoader option has been deprecated - replace with "fallback"');
}
if(options.loader) {
console.warn('loader option has been deprecated - replace with "use"');
}
if(Array.isArray(options) || isString(options) || typeof options.options === "object" || typeof options.query === 'object') {
options = { loader: options };
} else {
validateOptions(path.resolve(__dirname, './schema/loader.json'), options, 'Extract Text Plugin (Loader)');
}
var loader = options.use ||  options.loader;
var before = options.fallback || options.fallbackLoader || [];
if(isString(loader)) {
loader = loader.split("!");
}
if(isString(before)) {
before = before.split("!");
} else if(!Array.isArray(before)) {
before = [before];
}
options = mergeOptions({omit: before.length, remove: true}, options);
delete options.loader;
delete options.use;
delete options.fallback;
delete options.fallbackLoader;
return [this.loader(options)]
.concat(before, loader)
.map(getLoaderObject);
}
ExtractTextPlugin.extract = ExtractTextPlugin.prototype.extract.bind(ExtractTextPlugin);
ExtractTextPlugin.prototype.apply = function(compiler) {
var options = this.options;
compiler.plugin("this-compilation", function(compilation) {
var extractCompilation = new ExtractTextPluginCompilation();
compilation.plugin("normal-module-loader", function(loaderContext, module) {
loaderContext[NS] = function(content, opt) {
if(options.disable)
return false;
if(!Array.isArray(content) && content != null)
throw new Error("Exported value was not extracted as an array: " + JSON.stringify(content));
module[NS] = {
content: content,
options: opt || {}
};
return options.allChunks || module[NS + "/extract"]; // eslint-disable-line no-path-concat
};
});
var filename = this.filename;
var id = this.id;
var extractedChunks, entryChunks, initialChunks;
compilation.plugin("optimize-tree", function(chunks, modules, callback) {
extractedChunks = chunks.map(function() {
return new Chunk();
});
chunks.forEach(function(chunk, i) {
var extractedChunk = extractedChunks[i];
extractedChunk.index = i;
extractedChunk.originalChunk = chunk;
extractedChunk.name = chunk.name;
extractedChunk.entrypoints = chunk.entrypoints;
chunk.chunks.forEach(function(c) {
extractedChunk.addChunk(extractedChunks[chunks.indexOf(c)]);
});
chunk.parents.forEach(function(c) {
extractedChunk.addParent(extractedChunks[chunks.indexOf(c)]);
});
});
async.forEach(chunks, function(chunk, callback) {
var extractedChunk = extractedChunks[chunks.indexOf(chunk)];
var shouldExtract = !!(options.allChunks || isInitialOrHasNoParents(chunk));
async.forEach(chunk.modules.slice(), function(module, callback) {
var meta = module[NS];
if(meta && (!meta.options.id || meta.options.id === id)) {
var wasExtracted = Array.isArray(meta.content);
if(shouldExtract !== wasExtracted) {
module[NS + "/extract"] = shouldExtract; // eslint-disable-line no-path-concat
compilation.rebuildModule(module, function(err) {
if(err) {
compilation.errors.push(err);
return callback();
}
meta = module[NS];
// Error out if content is not an array and is not null
if(!Array.isArray(meta.content) && meta.content != null) {
err = new Error(module.identifier() + " doesn't export content");
compilation.errors.push(err);
return callback();
}
if(meta.content)
extractCompilation.addResultToChunk(module.identifier(), meta.content, module, extractedChunk);
callback();
});
} else {
if(meta.content)
extractCompilation.addResultToChunk(module.identifier(), meta.content, module, extractedChunk);
callback();
}
} else callback();
}, function(err) {
if(err) return callback(err);
callback();
});
}, function(err) {
if(err) return callback(err);
extractedChunks.forEach(function(extractedChunk) {
if(isInitialOrHasNoParents(extractedChunk))
this.mergeNonInitialChunks(extractedChunk);
}, this);
extractedChunks.forEach(function(extractedChunk) {
if(!isInitialOrHasNoParents(extractedChunk)) {
extractedChunk.modules.slice().forEach(function(module) {
extractedChunk.removeModule(module);
});
}
});
compilation.applyPlugins("optimize-extracted-chunks", extractedChunks);
callback();
}.bind(this));
}.bind(this));
compilation.plugin("additional-assets", function(callback) {
extractedChunks.forEach(function(extractedChunk) {
if(extractedChunk.modules.length) {
extractedChunk.modules.sort(function(a, b) {
if(!options.ignoreOrder && isInvalidOrder(a, b)) {
compilation.errors.push(new OrderUndefinedError(a.getOriginalModule()));
compilation.errors.push(new OrderUndefinedError(b.getOriginalModule()));
}
return getOrder(a, b);
});
var chunk = extractedChunk.originalChunk;
var source = this.renderExtractedChunk(extractedChunk);
var getPath = (format) => compilation.getPath(format, {
chunk: chunk
}).replace(/\[(?:(\w+):)?contenthash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, function() {
return loaderUtils.getHashDigest(source.source(), arguments[1], arguments[2], parseInt(arguments[3], 10));
});
var file = (isFunction(filename)) ? filename(getPath) : getPath(filename);
compilation.assets[file] = source;
chunk.files.push(file);
}
}, this);
callback();
}.bind(this));
}.bind(this));
};