@@ -975,6 +975,18 @@ class WebpackCLI implements IWebpackCLI {
975975 description : "Stop webpack-cli process with non-zero exit code on warnings from webpack" ,
976976 helpLevel : "minimum" ,
977977 } ,
978+ {
979+ name : "extends" ,
980+ alias : "e" ,
981+ configs : [
982+ {
983+ type : "string" ,
984+ } ,
985+ ] ,
986+ multiple : true ,
987+ description : "Extend webpack configuration" ,
988+ helpLevel : "minimum" ,
989+ } ,
978990 ] ;
979991
980992 const minimumHelpFlags = [
@@ -1814,6 +1826,7 @@ class WebpackCLI implements IWebpackCLI {
18141826 return { options, path : configPath } ;
18151827 } ;
18161828
1829+ // TODO better name and better type
18171830 const config : WebpackCLIConfig = {
18181831 options : { } as WebpackConfiguration ,
18191832 path : new WeakMap ( ) ,
@@ -1850,10 +1863,10 @@ class WebpackCLI implements IWebpackCLI {
18501863
18511864 if ( isArray ) {
18521865 ( loadedConfig . options as ConfigOptions [ ] ) . forEach ( ( options ) => {
1853- config . path . set ( options , loadedConfig . path ) ;
1866+ config . path . set ( options , [ loadedConfig . path ] ) ;
18541867 } ) ;
18551868 } else {
1856- config . path . set ( loadedConfig . options , loadedConfig . path ) ;
1869+ config . path . set ( loadedConfig . options , [ loadedConfig . path ] ) ;
18571870 }
18581871 } ) ;
18591872
@@ -1892,10 +1905,10 @@ class WebpackCLI implements IWebpackCLI {
18921905
18931906 if ( Array . isArray ( config . options ) ) {
18941907 config . options . forEach ( ( item ) => {
1895- config . path . set ( item , loadedConfig . path ) ;
1908+ config . path . set ( item , [ loadedConfig . path ] ) ;
18961909 } ) ;
18971910 } else {
1898- config . path . set ( loadedConfig . options , loadedConfig . path ) ;
1911+ config . path . set ( loadedConfig . options , [ loadedConfig . path ] ) ;
18991912 }
19001913 }
19011914 }
@@ -1929,6 +1942,92 @@ class WebpackCLI implements IWebpackCLI {
19291942 }
19301943 }
19311944
1945+ const resolveExtends = async (
1946+ config : WebpackConfiguration ,
1947+ configPaths : WebpackCLIConfig [ "path" ] ,
1948+ extendsPaths : string [ ] ,
1949+ ) : Promise < WebpackConfiguration > => {
1950+ delete config . extends ;
1951+
1952+ const loadedConfigs = await Promise . all (
1953+ extendsPaths . map ( ( extendsPath ) =>
1954+ loadConfigByPath ( path . resolve ( extendsPath ) , options . argv ) ,
1955+ ) ,
1956+ ) ;
1957+
1958+ const merge = await this . tryRequireThenImport < typeof webpackMerge > ( "webpack-merge" ) ;
1959+ const loadedOptions = loadedConfigs . flatMap ( ( config ) => config . options ) ;
1960+
1961+ if ( loadedOptions . length > 0 ) {
1962+ const prevPaths = configPaths . get ( config ) ;
1963+ const loadedPaths = loadedConfigs . flatMap ( ( config ) => config . path ) ;
1964+
1965+ if ( prevPaths ) {
1966+ const intersection = loadedPaths . filter ( ( element ) => prevPaths . includes ( element ) ) ;
1967+
1968+ if ( intersection . length > 0 ) {
1969+ this . logger . error ( `Recursive configuration detected, exiting.` ) ;
1970+ process . exit ( 2 ) ;
1971+ }
1972+ }
1973+
1974+ config = merge (
1975+ ...( loadedOptions as [ WebpackConfiguration , ...WebpackConfiguration [ ] ] ) ,
1976+ config ,
1977+ ) ;
1978+
1979+ if ( prevPaths ) {
1980+ configPaths . set ( config , [ ...prevPaths , ...loadedPaths ] ) ;
1981+ }
1982+ }
1983+
1984+ if ( config . extends ) {
1985+ const extendsPaths = typeof config . extends === "string" ? [ config . extends ] : config . extends ;
1986+
1987+ config = await resolveExtends ( config , configPaths , extendsPaths ) ;
1988+ }
1989+
1990+ return config ;
1991+ } ;
1992+
1993+ // The `extends` param in CLI gets priority over extends in config file
1994+ if ( options . extends && options . extends . length > 0 ) {
1995+ const extendsPaths = options . extends ;
1996+
1997+ if ( Array . isArray ( config . options ) ) {
1998+ config . options = await Promise . all (
1999+ config . options . map ( ( options ) => resolveExtends ( options , config . path , extendsPaths ) ) ,
2000+ ) ;
2001+ } else {
2002+ // load the config from the extends option
2003+ config . options = await resolveExtends ( config . options , config . path , extendsPaths ) ;
2004+ }
2005+ }
2006+ // if no extends option is passed, check if the config file has extends
2007+ else if ( Array . isArray ( config . options ) && config . options . some ( ( options ) => options . extends ) ) {
2008+ config . options = await Promise . all (
2009+ config . options . map ( ( options ) => {
2010+ if ( options . extends ) {
2011+ return resolveExtends (
2012+ options ,
2013+ config . path ,
2014+ typeof options . extends === "string" ? [ options . extends ] : options . extends ,
2015+ ) ;
2016+ } else {
2017+ return options ;
2018+ }
2019+ } ) ,
2020+ ) ;
2021+ } else if ( ! Array . isArray ( config . options ) && config . options . extends ) {
2022+ config . options = await resolveExtends (
2023+ config . options ,
2024+ config . path ,
2025+ typeof config . options . extends === "string"
2026+ ? [ config . options . extends ]
2027+ : config . options . extends ,
2028+ ) ;
2029+ }
2030+
19322031 if ( options . merge ) {
19332032 const merge = await this . tryRequireThenImport < typeof webpackMerge > ( "webpack-merge" ) ;
19342033
@@ -1946,11 +2045,13 @@ class WebpackCLI implements IWebpackCLI {
19462045 const configPath = config . path . get ( options ) ;
19472046 const mergedOptions = merge ( accumulator , options ) ;
19482047
1949- mergedConfigPaths . push ( configPath as string ) ;
2048+ if ( configPath ) {
2049+ mergedConfigPaths . push ( ...configPath ) ;
2050+ }
19502051
19512052 return mergedOptions ;
19522053 } , { } ) ;
1953- config . path . set ( config . options , mergedConfigPaths as unknown as string ) ;
2054+ config . path . set ( config . options , mergedConfigPaths ) ;
19542055 }
19552056
19562057 return config ;
0 commit comments