aboutsummaryrefslogtreecommitdiff
path: root/node_modules/webpack/lib/optimize/CommonsChunkPlugin.js
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-05-03 15:35:00 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-05-03 15:35:00 +0200
commitde98e0b232509d5f40c135d540a70e415272ff85 (patch)
treea79222a5b58484ab3b80d18efcaaa7ccc4769b33 /node_modules/webpack/lib/optimize/CommonsChunkPlugin.js
parente0c9d480a73fa629c1e4a47d3e721f1d2d345406 (diff)
node_modules
Diffstat (limited to 'node_modules/webpack/lib/optimize/CommonsChunkPlugin.js')
-rw-r--r--node_modules/webpack/lib/optimize/CommonsChunkPlugin.js362
1 files changed, 362 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/optimize/CommonsChunkPlugin.js b/node_modules/webpack/lib/optimize/CommonsChunkPlugin.js
new file mode 100644
index 000000000..d7c4849fe
--- /dev/null
+++ b/node_modules/webpack/lib/optimize/CommonsChunkPlugin.js
@@ -0,0 +1,362 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+"use strict";
+let nextIdent = 0;
+class CommonsChunkPlugin {
+ constructor(options) {
+ if(arguments.length > 1) {
+ throw new Error(`Deprecation notice: CommonsChunkPlugin now only takes a single argument. Either an options
+object *or* the name of the chunk.
+Example: if your old code looked like this:
+ new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js')
+You would change it to:
+ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.bundle.js' })
+The available options are:
+ name: string
+ names: string[]
+ filename: string
+ minChunks: number
+ chunks: string[]
+ children: boolean
+ async: boolean
+ minSize: number`);
+ }
+
+ const normalizedOptions = this.normalizeOptions(options);
+
+ this.chunkNames = normalizedOptions.chunkNames;
+ this.filenameTemplate = normalizedOptions.filenameTemplate;
+ this.minChunks = normalizedOptions.minChunks;
+ this.selectedChunks = normalizedOptions.selectedChunks;
+ this.children = normalizedOptions.children;
+ this.async = normalizedOptions.async;
+ this.minSize = normalizedOptions.minSize;
+ this.ident = __filename + (nextIdent++);
+ }
+
+ normalizeOptions(options) {
+ if(Array.isArray(options)) {
+ return {
+ chunkNames: options,
+ };
+ }
+
+ if(typeof options === "string") {
+ return {
+ chunkNames: [options],
+ };
+ }
+
+ // options.children and options.chunk may not be used together
+ if(options.children && options.chunks) {
+ throw new Error("You can't and it does not make any sense to use \"children\" and \"chunk\" options together.");
+ }
+
+ /**
+ * options.async and options.filename are also not possible together
+ * as filename specifies how the chunk is called but "async" implies
+ * that webpack will take care of loading this file.
+ */
+ if(options.async && options.filename) {
+ throw new Error(`You can not specify a filename if you use the \"async\" option.
+You can however specify the name of the async chunk by passing the desired string as the \"async\" option.`);
+ }
+
+ /**
+ * Make sure this is either an array or undefined.
+ * "name" can be a string and
+ * "names" a string or an array
+ */
+ const chunkNames = options.name || options.names ? [].concat(options.name || options.names) : undefined;
+ return {
+ chunkNames: chunkNames,
+ filenameTemplate: options.filename,
+ minChunks: options.minChunks,
+ selectedChunks: options.chunks,
+ children: options.children,
+ async: options.async,
+ minSize: options.minSize
+ };
+ }
+
+ apply(compiler) {
+ compiler.plugin("this-compilation", (compilation) => {
+ compilation.plugin(["optimize-chunks", "optimize-extracted-chunks"], (chunks) => {
+ // only optimize once
+ if(compilation[this.ident]) return;
+ compilation[this.ident] = true;
+
+ /**
+ * Creates a list of "common"" chunks based on the options.
+ * The list is made up of preexisting or newly created chunks.
+ * - If chunk has the name as specified in the chunkNames it is put in the list
+ * - If no chunk with the name as given in chunkNames exists a new chunk is created and added to the list
+ *
+ * These chunks are the "targets" for extracted modules.
+ */
+ const targetChunks = this.getTargetChunks(chunks, compilation, this.chunkNames, this.children, this.async);
+
+ // iterate over all our new chunks
+ targetChunks.forEach((targetChunk, idx) => {
+
+ /**
+ * These chunks are subject to get "common" modules extracted and moved to the common chunk
+ */
+ const affectedChunks = this.getAffectedChunks(compilation, chunks, targetChunk, targetChunks, idx, this.selectedChunks, this.async, this.children);
+
+ // bail if no chunk is affected
+ if(!affectedChunks) {
+ return;
+ }
+
+ // If we are async create an async chunk now
+ // override the "commonChunk" with the newly created async one and use it as commonChunk from now on
+ let asyncChunk;
+ if(this.async) {
+ asyncChunk = this.createAsyncChunk(compilation, this.async, targetChunk);
+ targetChunk = asyncChunk;
+ }
+
+ /**
+ * Check which modules are "common" and could be extracted to a "common" chunk
+ */
+ const extractableModules = this.getExtractableModules(this.minChunks, affectedChunks, targetChunk);
+
+ // If the minSize option is set check if the size extracted from the chunk is reached
+ // else bail out here.
+ // As all modules/commons are interlinked with each other, common modules would be extracted
+ // if we reach this mark at a later common chunk. (quirky I guess).
+ if(this.minSize) {
+ const modulesSize = this.calculateModulesSize(extractableModules);
+ // if too small, bail
+ if(modulesSize < this.minSize)
+ return;
+ }
+
+ // Remove modules that are moved to commons chunk from their original chunks
+ // return all chunks that are affected by having modules removed - we need them later (apparently)
+ const chunksWithExtractedModules = this.extractModulesAndReturnAffectedChunks(extractableModules, affectedChunks);
+
+ // connect all extracted modules with the common chunk
+ this.addExtractedModulesToTargetChunk(targetChunk, extractableModules);
+
+ // set filenameTemplate for chunk
+ if(this.filenameTemplate)
+ targetChunk.filenameTemplate = this.filenameTemplate;
+
+ // if we are async connect the blocks of the "reallyUsedChunk" - the ones that had modules removed -
+ // with the commonChunk and get the origins for the asyncChunk (remember "asyncChunk === commonChunk" at this moment).
+ // bail out
+ if(this.async) {
+ this.moveExtractedChunkBlocksToTargetChunk(chunksWithExtractedModules, targetChunk);
+ asyncChunk.origins = this.extractOriginsOfChunksWithExtractedModules(chunksWithExtractedModules);
+ return;
+ }
+
+ // we are not in "async" mode
+ // connect used chunks with commonChunk - shouldnt this be reallyUsedChunks here?
+ this.makeTargetChunkParentOfAffectedChunks(affectedChunks, targetChunk);
+ });
+ return true;
+ });
+ });
+ }
+
+ getTargetChunks(allChunks, compilation, chunkNames, children, asyncOption) {
+ const asyncOrNoSelectedChunk = children || asyncOption;
+
+ // we have specified chunk names
+ if(chunkNames) {
+ // map chunks by chunkName for quick access
+ const allChunksNameMap = allChunks.reduce((map, chunk) => {
+ if(chunk.name) {
+ map.set(chunk.name, chunk);
+ }
+ return map;
+ }, new Map());
+
+ // Ensure we have a chunk per specified chunk name.
+ // Reuse existing chunks if possible
+ return chunkNames.map(chunkName => {
+ if(allChunksNameMap.has(chunkName)) {
+ return allChunksNameMap.get(chunkName);
+ }
+ // add the filtered chunks to the compilation
+ return compilation.addChunk(chunkName);
+ });
+ }
+
+ // we dont have named chunks specified, so we just take all of them
+ if(asyncOrNoSelectedChunk) {
+ return allChunks;
+ }
+
+ /**
+ * No chunk name(s) was specified nor is this an async/children commons chunk
+ */
+ throw new Error(`You did not specify any valid target chunk settings.
+Take a look at the "name"/"names" or async/children option.`);
+ }
+
+ getAffectedChunks(compilation, allChunks, targetChunk, targetChunks, currentIndex, selectedChunks, asyncOption, children) {
+ const asyncOrNoSelectedChunk = children || asyncOption;
+
+ if(Array.isArray(selectedChunks)) {
+ return allChunks.filter(chunk => {
+ const notCommmonChunk = chunk !== targetChunk;
+ const isSelectedChunk = selectedChunks.indexOf(chunk.name) > -1;
+ return notCommmonChunk && isSelectedChunk;
+ });
+ }
+
+ if(asyncOrNoSelectedChunk) {
+ // nothing to do here
+ if(!targetChunk.chunks) {
+ return [];
+ }
+
+ return targetChunk.chunks.filter((chunk) => {
+ // we can only move modules from this chunk if the "commonChunk" is the only parent
+ return asyncOption || chunk.parents.length === 1;
+ });
+ }
+
+ /**
+ * past this point only entry chunks are allowed to become commonChunks
+ */
+ if(targetChunk.parents.length > 0) {
+ compilation.errors.push(new Error("CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (" + targetChunk.name + ")"));
+ return;
+ }
+
+ /**
+ * If we find a "targetchunk" that is also a normal chunk (meaning it is probably specified as an entry)
+ * and the current target chunk comes after that and the found chunk has a runtime*
+ * make that chunk be an 'affected' chunk of the current target chunk.
+ *
+ * To understand what that means take a look at the "examples/chunkhash", this basically will
+ * result in the runtime to be extracted to the current target chunk.
+ *
+ * *runtime: the "runtime" is the "webpack"-block you may have seen in the bundles that resolves modules etc.
+ */
+ return allChunks.filter((chunk) => {
+ const found = targetChunks.indexOf(chunk);
+ if(found >= currentIndex) return false;
+ return chunk.hasRuntime();
+ });
+ }
+
+ createAsyncChunk(compilation, asyncOption, targetChunk) {
+ const asyncChunk = compilation.addChunk(typeof asyncOption === "string" ? asyncOption : undefined);
+ asyncChunk.chunkReason = "async commons chunk";
+ asyncChunk.extraAsync = true;
+ asyncChunk.addParent(targetChunk);
+ targetChunk.addChunk(asyncChunk);
+ return asyncChunk;
+ }
+
+ // If minChunks is a function use that
+ // otherwhise check if a module is used at least minChunks or 2 or usedChunks.length time
+ getModuleFilter(minChunks, targetChunk, usedChunksLength) {
+ if(typeof minChunks === "function") {
+ return minChunks;
+ }
+ const minCount = (minChunks || Math.max(2, usedChunksLength));
+ const isUsedAtLeastMinTimes = (module, count) => count >= minCount;
+ return isUsedAtLeastMinTimes;
+ }
+
+ getExtractableModules(minChunks, usedChunks, targetChunk) {
+ if(minChunks === Infinity) {
+ return [];
+ }
+
+ // count how many chunks contain a module
+ const commonModulesToCountMap = usedChunks.reduce((map, chunk) => {
+ for(let module of chunk.modules) {
+ const count = map.has(module) ? map.get(module) : 0;
+ map.set(module, count + 1);
+ }
+ return map;
+ }, new Map());
+
+ // filter by minChunks
+ const moduleFilterCount = this.getModuleFilter(minChunks, targetChunk, usedChunks.length);
+ // filter by condition
+ const moduleFilterCondition = (module, chunk) => {
+ if(!module.chunkCondition) {
+ return true;
+ }
+ return module.chunkCondition(chunk);
+ };
+
+ return Array.from(commonModulesToCountMap).filter(entry => {
+ const module = entry[0];
+ const count = entry[1];
+ // if the module passes both filters, keep it.
+ return moduleFilterCount(module, count) && moduleFilterCondition(module, targetChunk);
+ }).map(entry => entry[0]);
+ }
+
+ calculateModulesSize(modules) {
+ return modules.reduce((totalSize, module) => totalSize + module.size(), 0);
+ }
+
+ extractModulesAndReturnAffectedChunks(reallyUsedModules, usedChunks) {
+ return reallyUsedModules.reduce((affectedChunksSet, module) => {
+ for(let chunk of usedChunks) {
+ // removeChunk returns true if the chunk was contained and succesfully removed
+ // false if the module did not have a connection to the chunk in question
+ if(module.removeChunk(chunk)) {
+ affectedChunksSet.add(chunk);
+ }
+ }
+ return affectedChunksSet;
+ }, new Set());
+ }
+
+ addExtractedModulesToTargetChunk(chunk, modules) {
+ for(let module of modules) {
+ chunk.addModule(module);
+ module.addChunk(chunk);
+ }
+ }
+
+ makeTargetChunkParentOfAffectedChunks(usedChunks, commonChunk) {
+ for(let chunk of usedChunks) {
+ // set commonChunk as new sole parent
+ chunk.parents = [commonChunk];
+ // add chunk to commonChunk
+ commonChunk.addChunk(chunk);
+
+ for(let entrypoint of chunk.entrypoints) {
+ entrypoint.insertChunk(commonChunk, chunk);
+ }
+ }
+ }
+
+ moveExtractedChunkBlocksToTargetChunk(chunks, targetChunk) {
+ for(let chunk of chunks) {
+ for(let block of chunk.blocks) {
+ block.chunks.unshift(targetChunk);
+ targetChunk.addBlock(block);
+ }
+ }
+ }
+
+ extractOriginsOfChunksWithExtractedModules(chunks) {
+ const origins = [];
+ for(let chunk of chunks) {
+ for(let origin of chunk.origins) {
+ const newOrigin = Object.create(origin);
+ newOrigin.reasons = (origin.reasons || []).concat("async commons");
+ origins.push(newOrigin);
+ }
+ }
+ return origins;
+ }
+}
+
+module.exports = CommonsChunkPlugin;