2013-12-03 16:27:15 +08:00
/ *
MIT License http : //www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @ sokra
* /
2017-01-04 21:50:00 +08:00
"use strict" ;
let nextIdent = 0 ;
class CommonsChunkPlugin {
constructor ( options ) {
if ( arguments . length > 1 ) {
2017-01-05 03:34:12 +08:00
throw new Error ( ` Deprecation notice: CommonsChunkPlugin now only takes a single argument. Either an options
2017-01-10 19:15:41 +08:00
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 ` );
2017-01-04 21:50:00 +08:00
}
2017-01-10 19:15:41 +08:00
2017-02-19 11:30:53 +08:00
const normalizedOptions = this . normalizeOptions ( options ) ;
this . chunkNames = normalizedOptions . chunkNames ;
this . filenameTemplate = this . filenameTemplate ;
this . minChunks = normalizedOptions . minChunks ;
this . selectedChunks = normalizedOptions . selectedChunks ;
this . async = normalizedOptions . async ;
this . minSize = normalizedOptions . minSize ;
this . ident = normalizedOptions . ident ;
}
normalizeOptions ( options ) {
if ( Array . isArray ( options ) ) {
return {
chunkNames : options ,
} ;
}
if ( typeof options === "string" ) {
return {
chunkNames : [ options ] ,
2017-01-04 21:50:00 +08:00
} ;
}
2017-02-19 11:30:53 +08:00
// if "children" is set, set selectedChunks to false instead of specified chunks
// TODO: why? :P
const selectedChunks = options . children ? false : options . chunks ;
const chunkNames = options . name ? [ options . name ] : options . names ;
return {
chunkNames : chunkNames ,
filenameTemplate : options . filename ,
minChunks : options . minChunks ,
selectedChunks : selectedChunks ,
async : options . async ,
minSize : options . minSize ,
ident : _ _filename + ( nextIdent ++ ) ,
} ;
2016-01-22 12:01:00 +08:00
}
2017-02-19 11:30:53 +08:00
2017-02-19 11:31:35 +08:00
getCommonChunks ( allChunks , compilation ) {
const asyncOrNoSelectedChunk = this . selectedChunks === false || this . async ;
// we have specified chunk names
if ( this . chunkNames ) {
// map chunks by chunkName for quick access
const optimizedChunkMap = allChunks . reduce ( ( map , chunk ) => {
map . set ( chunk . name , chunk ) ;
return map ;
} , new Map ( ) ) ;
// Ensure we have a chunk per specified chunk name.
// Reuse existing chunks if possible
return this . chunkNames . map ( chunkName => {
if ( optimizedChunkMap . has ( chunkName ) ) {
return optimizedChunkMap . 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 ;
}
// that is not supposed to happen, lets throw
throw new Error ( "Invalid chunkNames argument" ) ;
}
2017-02-19 12:59:00 +08:00
getUsedChunks ( compilation , allChunks , commonChunk , commonChunks , currentIndex , selectedChunks , isAsync ) {
const asyncOrNoSelectedChunk = selectedChunks === false || isAsync ;
if ( Array . isArray ( selectedChunks ) ) {
return allChunks . filter ( chunk => {
const notCommmonChunk = chunk !== commonChunk ;
const isSelectedChunk = selectedChunks . indexOf ( chunk . name ) > - 1 ;
return notCommmonChunk && isSelectedChunk ;
} ) ;
}
if ( asyncOrNoSelectedChunk ) {
// nothing to do here
if ( ! commonChunk . chunks ) {
return [ ] ;
}
return commonChunk . chunks . filter ( ( chunk ) => {
// we can only move modules from this chunk if the "commonChunk" is the only parent
return isAsync || chunk . parents . length === 1 ;
} ) ;
}
// this is an entry point - bad
if ( commonChunk . parents . length > 0 ) {
compilation . errors . push ( new Error ( "CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (" + commonChunk . name + ")" ) ) ;
return ;
}
return allChunks . filter ( ( chunk ) => {
const found = commonChunks . indexOf ( chunk ) ;
if ( found >= currentIndex ) return false ;
return chunk . hasRuntime ( ) ;
} ) ;
}
2017-02-19 12:59:31 +08:00
createAsyncChunk ( compilation , asyncOption , commonChunk ) {
const asyncChunk = compilation . addChunk ( typeof asyncOption === "string" ? asyncOption : undefined ) ;
asyncChunk . chunkReason = "async commons chunk" ;
asyncChunk . extraAsync = true ;
asyncChunk . addParent ( commonChunk ) ;
return asyncChunk ;
}
2017-02-19 13:36:11 +08:00
// 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 , commonChunk , usedChunksLength ) {
if ( typeof minChunks === "function" ) {
return minChunks ;
}
const minCount = ( minChunks || Math . max ( 2 , usedChunksLength ) ) ;
const isUsedAtLeastMinTimes = ( module , count ) => count >= minCount ;
return isUsedAtLeastMinTimes ;
}
getReallyUsedModules ( minChunks , usedChunks , commonChunk ) {
if ( minChunks === Infinity ) {
return [ ] ;
}
// count how many chunks contain a module
const commonModulesToCountMap = usedChunks . reduce ( ( map , chunk ) => {
for ( let module of chunk . modules ) {
let 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 , commonChunk , 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 , commonChunk ) ;
} ) . map ( entry => entry [ 0 ] ) ;
}
getModulesSize ( modules ) {
return modules . reduce ( ( count , module ) => count + module . size ( ) , 0 ) ;
}
2017-01-04 21:50:00 +08:00
apply ( compiler ) {
const filenameTemplate = this . filenameTemplate ;
const asyncOption = this . async ;
const minSize = this . minSize ;
2017-02-19 13:36:11 +08:00
const minChunks = this . minChunks ;
2017-01-24 18:47:12 +08:00
const ident = this . ident ;
2017-01-04 21:50:00 +08:00
compiler . plugin ( "this-compilation" , ( compilation ) => {
2017-01-24 19:09:46 +08:00
compilation . plugin ( [ "optimize-chunks" , "optimize-extracted-chunks" ] , ( chunks ) => {
2017-01-04 21:50:00 +08:00
// only optimize once
2017-01-24 18:47:12 +08:00
if ( compilation [ ident ] ) return ;
compilation [ ident ] = true ;
2015-11-21 04:29:32 +08:00
2017-02-19 13:00:07 +08:00
const commonChunks = this . getCommonChunks ( chunks , compilation ) ;
2017-02-19 11:31:35 +08:00
2017-02-19 12:59:00 +08:00
commonChunks . forEach ( ( commonChunk , idx ) => {
const usedChunks = this . getUsedChunks ( compilation , chunks , commonChunk , commonChunks , idx , this . selectedChunks , this . async ) ;
// bail as this is an erronous state
if ( ! usedChunks ) {
return ;
2017-01-04 21:50:00 +08:00
}
2017-02-19 12:59:31 +08:00
2017-01-04 22:56:46 +08:00
let asyncChunk ;
2017-01-04 21:50:00 +08:00
if ( asyncOption ) {
2017-02-19 12:59:31 +08:00
asyncChunk = this . createAsyncChunk ( compilation , this . async , commonChunk ) ;
2017-01-04 21:50:00 +08:00
commonChunk . addChunk ( asyncChunk ) ;
commonChunk = asyncChunk ;
}
2017-02-19 13:36:11 +08:00
const reallyUsedModules = this . getReallyUsedModules ( minChunks , usedChunks , commonChunk ) ;
// check if the extracted modules would be big enough to be extraced
2017-01-04 21:50:00 +08:00
if ( minSize ) {
2017-02-19 13:36:11 +08:00
const modulesSize = this . getModulesSize ( reallyUsedModules ) ;
// if too small, bail
if ( modulesSize < minSize )
2015-01-12 06:15:11 +08:00
return ;
2017-01-04 21:50:00 +08:00
}
2017-02-19 13:36:11 +08:00
2017-01-24 18:47:12 +08:00
const reallyUsedChunks = new Set ( ) ;
2017-01-04 21:50:00 +08:00
reallyUsedModules . forEach ( ( module ) => {
usedChunks . forEach ( ( chunk ) => {
if ( module . removeChunk ( chunk ) ) {
2017-01-24 18:47:12 +08:00
reallyUsedChunks . add ( chunk ) ;
2017-01-04 21:50:00 +08:00
}
2015-01-12 06:15:11 +08:00
} ) ;
2017-01-04 21:50:00 +08:00
commonChunk . addModule ( module ) ;
module . addChunk ( commonChunk ) ;
2015-01-12 06:15:11 +08:00
} ) ;
2017-01-04 21:50:00 +08:00
if ( asyncOption ) {
2017-02-19 10:18:01 +08:00
for ( let chunk of reallyUsedChunks ) {
2017-01-24 18:47:12 +08:00
if ( chunk . isInitial ( ) ) continue ;
2017-01-04 21:50:00 +08:00
chunk . blocks . forEach ( ( block ) => {
block . chunks . unshift ( commonChunk ) ;
commonChunk . addBlock ( block ) ;
} ) ;
2017-01-24 18:47:12 +08:00
}
asyncChunk . origins = Array . from ( reallyUsedChunks ) . map ( ( chunk ) => {
2017-01-24 19:09:46 +08:00
return chunk . origins . map ( ( origin ) => {
2017-01-04 21:50:00 +08:00
const newOrigin = Object . create ( origin ) ;
newOrigin . reasons = ( origin . reasons || [ ] ) . slice ( ) ;
newOrigin . reasons . push ( "async commons" ) ;
return newOrigin ;
} ) ;
} ) . reduce ( ( arr , a ) => {
arr . push . apply ( arr , a ) ;
return arr ;
} , [ ] ) ;
} else {
usedChunks . forEach ( ( chunk ) => {
chunk . parents = [ commonChunk ] ;
chunk . entrypoints . forEach ( ( ep ) => {
ep . insertChunk ( commonChunk , chunk ) ;
} ) ;
commonChunk . addChunk ( chunk ) ;
2016-07-13 17:03:14 +08:00
} ) ;
2017-01-04 21:50:00 +08:00
}
if ( filenameTemplate )
commonChunk . filenameTemplate = filenameTemplate ;
2017-01-24 19:09:46 +08:00
} ) ;
2017-01-04 21:50:00 +08:00
return true ;
} ) ;
2013-12-03 16:27:15 +08:00
} ) ;
2017-01-04 21:50:00 +08:00
}
}
module . exports = CommonsChunkPlugin ;