Giter Site home page Giter Site logo

survivejs / webpack-merge Goto Github PK

View Code? Open in Web Editor NEW
2.7K 16.0 118.0 1.76 MB

Merge designed for webpack

Home Page: https://www.npmjs.com/package/webpack-merge

License: MIT License

TypeScript 99.91% JavaScript 0.09%
webpack merging-loaders webpack-specifics

webpack-merge's Introduction

Financial Contributors on Open Collective Test codecov

webpack-merge - Merge designed for Webpack

webpack-merge provides a merge function that concatenates arrays and merges objects creating a new object. If functions are encountered, it will execute them, run the results through the algorithm, and then wrap the returned values within a function again.

This behavior is particularly useful in configuring webpack although it has uses beyond it. Whenever you need to merge configuration objects, webpack-merge can come in handy.

merge(...configuration | [...configuration])

merge is the core, and the most important idea, of the API. Often this is all you need unless you want further customization.

const { merge } = require('webpack-merge');

// Default API
const output = merge(object1, object2, object3, ...);

// You can pass an array of objects directly.
// This works with all available functions.
const output = merge([object1, object2, object3]);

// Keys matching to the right take precedence:
const output = merge(
  { fruit: "apple", color: "red" },
  { fruit: "strawberries" }
);
console.log(output);
// { color: "red", fruit: "strawberries"}

Limitations

Note that Promises are not supported! If you want to return a configuration wrapped within a Promise, merge inside one. Example: Promise.resolve(merge({ ... }, { ... })).

The same goes for configuration level functions as in the example below:

webpack.config.js

const commonConfig = { ... };

const productionConfig = { ... };

const developmentConfig = { ... };

module.exports = (env, args) => {
  switch(args.mode) {
    case 'development':
      return merge(commonConfig, developmentConfig);
    case 'production':
      return merge(commonConfig, productionConfig);
    default:
      throw new Error('No matching configuration was found!');
  }
}

You can choose the configuration you want by using webpack --mode development assuming you are using webpack-cli.

mergeWithCustomize({ customizeArray, customizeObject })(...configuration | [...configuration])

In case you need more flexibility, merge behavior can be customized per field as below:

const { mergeWithCustomize } = require('webpack-merge');

const output = mergeWithCustomize(
  {
    customizeArray(a, b, key) {
      if (key === 'extensions') {
        return _.uniq([...a, ...b]);
      }

      // Fall back to default merging
      return undefined;
    },
    customizeObject(a, b, key) {
      if (key === 'module') {
        // Custom merging
        return _.merge({}, a, b);
      }

      // Fall back to default merging
      return undefined;
    }
  }
)(object1, object2, object3, ...);

For example, if the previous code was invoked with only object1 and object2 with object1 as:

{
    foo1: ['object1'],
    foo2: ['object1'],
    bar1: { object1: {} },
    bar2: { object1: {} },
}

and object2 as:

{
    foo1: ['object2'],
    foo2: ['object2'],
    bar1: { object2: {} },
    bar2: { object2: {} },
}

then customizeArray will be invoked for each property of Array type, i.e:

customizeArray(["object1"], ["object2"], "foo1");
customizeArray(["object1"], ["object2"], "foo2");

and customizeObject will be invoked for each property of Object type, i.e:

customizeObject({ object1: {} }, { object2: {} }, bar1);
customizeObject({ object1: {} }, { object2: {} }, bar2);

customizeArray and customizeObject

customizeArray and customizeObject provide small strategies to for mergeWithCustomize. They support append, prepend, replace, and wildcards for field names.

const { mergeWithCustomize, customizeArray, customizeObject } = require('webpack-merge');

const output = mergeWithCustomize({
  customizeArray: customizeArray({
    'entry.*': 'prepend'
  }),
  customizeObject: customizeObject({
    entry: 'prepend'
  })
})(object1, object2, object3, ...);

unique(<field>, <fields>, field => field)

unique is a strategy used for forcing uniqueness within configuration. It's most useful with plugins when you want to make sure there's only one in place.

The first <field> is the config property to look through for duplicates.

<fields> represents the values that should be unique when you run the field => field function on each duplicate.

When the order of elements of the <field> in the first configuration differs from the order in the second configuration, the latter is preserved.

const { mergeWithCustomize, unique } = require("webpack-merge");

const output = mergeWithCustomize({
  customizeArray: unique(
    "plugins",
    ["HotModuleReplacementPlugin"],
    (plugin) => plugin.constructor && plugin.constructor.name,
  ),
})(
  {
    plugins: [new webpack.HotModuleReplacementPlugin()],
  },
  {
    plugins: [new webpack.HotModuleReplacementPlugin()],
  },
);

// Output contains only single HotModuleReplacementPlugin now and it's
// going to be the last plugin instance.

mergeWithRules

To support advanced merging needs (i.e. merging within loaders), mergeWithRules includes additional syntax that allows you to match fields and apply strategies to match. Consider the full example below:

const a = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [{ loader: "style-loader" }, { loader: "sass-loader" }],
      },
    ],
  },
};
const b = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader",
            options: {
              modules: true,
            },
          },
        ],
      },
    ],
  },
};
const result = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader",
            options: {
              modules: true,
            },
          },
          { loader: "sass-loader" },
        ],
      },
    ],
  },
};

assert.deepStrictEqual(
  mergeWithRules({
    module: {
      rules: {
        test: "match",
        use: {
          loader: "match",
          options: "replace",
        },
      },
    },
  })(a, b),
  result,
);

The way it works is that you should annotate fields to match using match (or CustomizeRule.Match if you are using TypeScript) matching your configuration structure and then use specific strategies to define how particular fields should be transformed. If a match doesn't exist above a rule, then it will apply the rule automatically.

Supported annotations:

  • match (CustomizeRule.Match) - Optional matcher that scopes merging behavior to a specific part based on similarity (think DOM or jQuery selectors)
  • append (CustomizeRule.Append) - Appends items
  • prepend (CustomizeRule.Prepend) - Prepends items
  • replace (CustomizeRule.Replace) - Replaces items
  • merge (CustomizeRule.Merge) - Merges objects (shallow merge)

Using with TypeScript

webpack-merge supports TypeScript out of the box. You should pass Configuration type from webpack to it as follows:

import { Configuration } from "webpack";
import { merge } from "webpack-merge";

const config = merge<Configuration>({...}, {...});

...

Development

  1. nvm use
  2. npm i
  3. npm run build -- --watch in one terminal
  4. npm t -- --watch in another one

Before contributing, please open an issue where to discuss.

Further Information and Support

Check out SurviveJS - Webpack 5 to dig deeper into webpack. The free book uses webpack-merge extensively and shows you how to compose your configuration to keep it maintainable.

I am also available as a consultant in case you require specific assistance. I can contribute particularly in terms of improving maintainability of the setup while speeding it up and pointing out better practices. In addition to improving developer productivity, the work has impact on the end users of the product in terms of reduced application size and loading times.

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

webpack-merge is available under MIT. See LICENSE for more details.

webpack-merge's People

Contributors

aaronjensen avatar adrienharnay avatar amy-lynn avatar bebraw avatar blackrabbit99 avatar burhanuday avatar chinesedfan avatar choffmeister avatar danielruf avatar davegomez avatar dependabot[bot] avatar desfero avatar flaviorocks avatar gavin-gong avatar greengremlin avatar herecydev avatar jstri avatar just-jeb avatar kherock avatar knpwrs avatar kristoforsalmin avatar lucretiel avatar masterodin avatar monkeywithacupcake avatar montogeek avatar nageshlop avatar rexskz avatar sapegin avatar trombka avatar whoaa512 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webpack-merge's Issues

lodash require

Hi!

It is possible to just require the lodash functions that this package actually needs?

Thanks!

Default to array prepend?

In Webpack plugin order is significant. This means the first matched plugin wins. Currently webpack-merge concatenates arrays in the order it finds them. So if you have merge(common, prod), it will pick up plugins from common first and prod after that.

This can be problematic if you are trying to apply a plugin like DedupePlugin at prod as it would have to be before other plugins.

Maybe we should reverse this order? So that instead of appending, we would prepend arrays. That way the last merged array would win and it would become easier to control ordering like this.

Any thoughts? Can you see any problems with this change? It would be a major version bump in any case.

Loaders merge behavior with "include"

I found issue with merging multiple loaders (in 2.6.1) with same test and different include fields:

rule 1: { test: 'js', } // <-- no include field
....
rule 10: { test: 'js', include: 'verySpecialDir', 'loaders only for this dir'}
rule 11: { test: 'js', include: 'anotherSpecialDir', 'different loaders'}

merge.smart will return:
{ test: 'js', include: 'verySpecialDir', 'loaders only for this dir' merged with 'global loaders'}
{ test: 'js', include: 'anotherSpecialDir', 'different loaders'}

At first, rule 1 merged with rule 10, because of rule 10 looks like it extend rule 1 with include field. Next, rule 10 not merged with rule 11 - they had different include fields. As result, global rule applied only for files in one dir, not for all js.

Yes, it's expected behavior (one rule extends another), but it's looks not really 'smart'. Currently, I use slightly different (and useless for webpack) test fields to avoid merge, but it's very bad idea, I think.

Write a customizeArray is slightly better option, but it's very tricky and unclear solution at my sight.

May be smart.merge should not merge include fields (exclude, and other scope modificators) by default, and treat loaders with different includes as different objects?

Merging config with multiple targets

I'm after using multiple targets to have a server side config and a client side config packaged. Where one targets node and the other uses the default web target.

webpack.base.js

let clientConfig = {
  entry: "./src/website/js/App.tsx"
}

let serverConfig = {
  entry: "./src/server/server.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },
  target: "node"
};
}

webpack.config.js

let clientConfig = {
  devtool: "source-map"
};

module.exports = webpackMerge(config, [clientConfig]);

Before the merge the json looks healthy:

[ { entry: './src/website/js/App.tsx' },
  { entry: './src/server/server.js',
    output: { path: 'C:\\Dev\\dist', filename: '[name].js' },
    target: 'node' } ]

But the output from the merge looks like it's become a single object and hence now fails with the typical errors 'output.filename' is required ...

{ '0':
   { entry: './src/website/js/App.tsx',
     devtool: 'source-map',
     module: { rules: [Object] },
     output: { filename: '[name].js' },
     plugins: [ [Object], [Object] ] },
  '1':
   { entry: './src/server/server.js',
     output: { path: 'C:\\Dev\\dist', filename: '[name].js' },
     target: 'node' } }

'import' and 'export' may only appear at the top level

I'm getting the following error when I try webpack-merge 0.15 or 0.16. I don't see this error on 0.14.1. Any ideas what's going on?

ERROR in ./src/js/client.jsx
Module build failed: SyntaxError: c:/GitHub/web/front-end/src/js/client.jsx: 'import' and 'export' may only appear at the top level (3:0)

1 | /* REACT HOT LOADER */ if (module.hot) { (function () { var ReactHotAPI = require("c:\GitHub\web\front-end\node_modules\react-hot-api\modules\index.js"), RootInstanceProvider = require("c:\GitHub\web\front-end\node_modules\react-hot-loader\RootInstanceProvider.js"), ReactMount = require("react/lib/ReactMount"), React = require("react"); module.makeHot = module.hot.data ? module.hot.data.makeHot : ReactHotAPI(function () { return RootInstanceProvider.getRootInstances(ReactMount); }, React); })(); } try { (function () {
2 |

3 | import React from 'react';
| ^
4 | import { render } from 'react-dom';
5 | import { Router, Route, IndexRedirect, browserHistory } from 'react-router';
6 | import 'babel-polyfill';`

Allow inline strategies

I had a quick chat with Tobias about this. As it happens, it would be valuable to be able to define strategies inline so you could achieve something like this:

merge(
  require("webpack-preset-development"),
  require("webpack-preset-css-modules"),
  require("webpack-preset-separate-css")
)

Inline definitions would encode the information per preset since it's information we don't care about if we are merging presets.

Possible APIs:

const webpackPresetCSSModules = {
  module: {
    rules: {
      __append: [ ... ]
    }
  }
};

const webpackPresetSeparateCSS = {
  module: {
    rules: {
      __update: function(rules) {...}
    }
  }
};

React Immutability Helpers


I guess finding the right API is the hardest problem. Minimum implementation would convert the definition to the current API (adapter).

Feedback welcome!

Support for "enforce" when using merge.smart

Hi :)

Since webpack 2, preLoaders have been removed in favour of the "enforce" rule, see: https://webpack.js.org/configuration/module/#rule-enforce

It seems to me that webpack merge doesn't correctly support it. It should not merge rules with different enforce rules.

Sample here:

var merge = require('webpack-merge')

const a = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        exclude: /node_modules/
      },
    ]
  }
}

const b = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
    ]
  }
}

const merged = merge.smart(a, b)

function stringifyFilter(key,value) {
    if (value instanceof RegExp) {
        return value.toString();
    }

    return value;
}

console.log(JSON.stringify(merged, stringifyFilter, "    "));

Output:

{
    "module": {
        "rules": [
            {
                "test": "/\\.vue$/",
                "loader": "vue-loader",
                "enforce": "pre",
                "exclude": "/node_modules/"
            }
        ]
    }
}

.smart() cause unwanted loader sequence

as loaders are run with the loaders array ascendingtly, smart merging reverse the config merging order, that would break some loader overload functionality.

Cant get to work with `exclude` with `merge.smart`

Test case:

merge.smart({
  module: {
    loaders: [
      {
        test: /\.js$/, 
        exclude: /node_modules/,
        loaders: ['babel']
      }
    ]
  }
}, { 
  module: { 
    loaders: [
      {
        test: /\.js$/, 
        exclude: /node_modules/,
        loaders: ['coffee', 'thing']
      }
    ]
  }
});

module.loaders =

[
  {
    test: /\.js$/,
    exclude: /node_modules/,
    loaders: [ 'coffee', 'thing' ]
  },
  {
    test: /\.js$/,
    exclude: /node_modules/,
    loaders: [ 'babel' ]
  }
]

Suggestion: Enforce exact include/exclude matching to merge loader configs

Here's a simplified example to illustrate my problem:

Common config:

rules: [{
  test: /\.scss$/
  include: [/node_modules/],
  use: [ExtractTextPlugin.loader(), 'css-loader', 'sass-loader']
}, { // everything else
  test: /\.scss$/
  use: ['css-loader', 'postcss-loader', 'sass-loader']
}]

Dev config:

// strategy 'prepend'
rules: [{
  test: /\.scss$/
  use: ['style-loader']
}]

Prod config:

// strategy 'prepend'
rules: [{
  test: /\.scss$/
  use: [ExtractTextPlugin.loader()]
}]

Since the use entry doesn't have an include/exclude field to match against, it merges with the first config with a matching test, the dev config results in

rules: [{
  test: /\.scss$/
  include: [/node_modules/],
  use: ['style-loader', ExtractTextPlugin.loader(), 'css-loader', 'sass-loader']
}, { // everything else
  test: /\.scss$/
  use: ['css-loader', 'postcss-loader', 'sass-loader']
}]

I can't think of a good use case where include or exclude is applied afterward. Intuitively, they seem to be tied to the identity of a rule as much as the test property is. Yes, it isn't as DRY, but if the configs are stored in their own file it makes it very clear what rule is being overridden.

EDIT:
Apparently the example above doesn't actually work for the config I'm trying to make, but I think it's still useful. I am only just now realizing webpack appends when multiple loaders match the criteria!

Do not use individual lodash packages

├─┬ [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ └── [email protected]

Consider using:

Per method packages have been discontinued, https://github.com/lodash/lodash/wiki/Roadmap.

Provide unique enum out of the box?

Something like

const IGNORE_PLUGINS = [
	'OccurrenceOrderPlugin',
	'DedupePlugin',
	'UglifyJsPlugin',
	'HotModuleReplacementPlugin',
];

This would work well with merge.unique.

Webpack 2 Compat

Is this compatible with webpack 2? Specifically, they're called "module.rules" now, instead of "module.loaders". Will this work with the new configuration?

Add support for merging the `output` as well

It'd be nice if, in addition to loaders, you could merge the output field as well. Typically, you will want to have a separate output directory, but keep the bundle name and other stuff the same.

// prod.config.js

const config = require('./common.config.js')
const merge = require('webpack-merge')

module.exports = merge(config, {
    output: {path: "dist.prod"}  // preserves output.filename and out output keys.
})

Version 2.1.1 breaks webpack-offline-plugin

Updated to 2.1.1 and getting error in webpack-offline-plugin now..

TypeError: #<Object> is not a function
    at Array.map (native)
    at OfflinePlugin.validatePaths (C:\projects\dayapp\node_modules\offline-plugin\lib\index.js:671:21)
    at OfflinePlugin.setAssets (C:\projects\dayapp\node_modules\offline-plugin\lib\index.js:339:29)
    at Compiler.<anonymous> (C:\projects\dayapp\node_modules\offline-plugin\lib\index.js:288:18)
...

Reverting to 2.1.0 and error goes away.

Webpack2: prepend use array configuration

System info
[email protected]
Chrome Version 53.0.2785.116 m
Node 6.3.1
NPM 3.10.3
Windows 7 Professional

// Common config
module: {
	rules: [
		{
			test: /\.js$/,
			exclude: /(node_modules|lib)/,
			use: [
				{
					loader: 'babel-loader',
					options: {
						cacheDirectory: true
					}
				}
			]
		}
	]
}
...
// Prod config
module: {
	rules: [
		{
			test: /\.js$/,
			use: [
				{
					loader: 'string-replace-loader',
					options: {
						multiple: [
							{
								search: /["']ngInject["'];*/,
								replace: '',
								flags: 'g'
							}
						]
					}
				},
				'ng-annotate-loader'
			]
		}
	]
}

The following is the desired output:

module: {
	rules: [
		{
			test: /\.js$/,
			exclude: /(node_modules|lib)/,
			use: [
				{
					loader: 'string-replace-loader',
					options: {
						multiple: [
							{
								search: /["']ngInject["'];*/,
								replace: '',
								flags: 'g'
							}
						]
					}
				},
				'ng-annotate-loader',
				{
					loader: 'babel-loader',
					options: {
						cacheDirectory: true
					}
				}
			]
		}
	]
}

I'm not 100% sure what the correct settings are to achieve this. Could you advise? I've tried a couple of options. ie.

let mergeOutput = webpackMerge.smartStrategy(
        {
            // rules: 'prepend',
            'module.rules.use': 'prepend',
            plugins: 'append'
        }
    )(common, prod);

RangeError: Maximum call stack size exceeded

Hello!

The latest release 2.1.0 introduces a bug for us, rolling back to 2.0.0 is our current work around.

» yarn start 
yarn start v0.17.8
$ webpack-dev-server 
/Users/ambethia/Desktop/foo/node_modules/webpack-merge/lib/join-arrays.js:64
function clonePrototype(o) {
                       ^

RangeError: Maximum call stack size exceeded
    at clonePrototype (/Users/ambethia/Desktop/foo/node_modules/webpack-merge/lib/join-arrays.js:64:24)
    at baseClone (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:844:23)
    at /Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:897:30
    at arrayEach (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:140:9)
    at baseClone (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:891:3)
    at cloneDeepWith (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:1409:10)
    at /Users/ambethia/Desktop/foo/node_modules/webpack-merge/lib/join-arrays.js:73:19
    at Array.forEach (native)
    at clonePrototype (/Users/ambethia/Desktop/foo/node_modules/webpack-merge/lib/join-arrays.js:72:33)
    at baseClone (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:844:23)
    at /Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:897:30
    at arrayEach (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:140:9)
    at baseClone (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:891:3)
    at /Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:897:30
    at arrayEach (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:140:9)
    at baseClone (/Users/ambethia/Desktop/foo/node_modules/lodash.clonedeepwith/index.js:891:3)
error Command failed with exit code 1.

Here's an example of our webpack config as well:

https://github.com/tiy-tpa-fee/majestic-that/blob/master/webpack.config.js

Node v7.1.0 on OS X Sierra

Merge json files into 1 file

Hi guys,
How can I merge json files into 1 new json file (in another folder)? The problem is I have to do it in a Angular CLI package.
Thank you!

Stange merge behavior for the version 2.0.0

I've installed version 2.0.0 and the merge function returns the last object in parameters without any merging behavior applied. I just wonder is it a bug or something have changed in the API. For the previous version it worked for me. Here is an example To illustrate my issue:

module.exports = merge (baseConfig, {
    devtool: false,
    entry: {
        app: [
            'babel-polyfill',
            APP_PATH
        ]
    }
})

module.exports is filled with the last object

Unexpected Webpack2 `use` merge

Given the following merge:

merge.smart({
  rules: [{
    test: /\.js$/,
    use: 'babel'
  }]
}, {
  rules: [{
    test: /\.js$/,
    use: 'coffee'
  }]
});

I would expect the following according to the what the Webpack1 examples seem to suggest in the README:

{
  rules: [{
    test: /\.js$/,
    use: 'coffee'
  }]
}

Instead I get:

{
  rules: [{
    test: /\.js$/,
    use: ['babel','coffee']
  }]
}

There doesn't seem to be a way to overwrite a string value in use as it always gets converted to an array and merged.

Future - Change default behavior of `merge`

To quote @sapegin, a better merge default might be to "not add duplicates but also do not remove existing items."

Something along:

const merge = mergeBase({
    customizeArray: (a, b) => a.concat(differenceWith(b, a, isEqual)),
});

Error with empty loaders

Hi, seems like I have an error:

$ coffee 
coffee> merge = require 'webpack-merge'
Expression assignment to _ now disabled.
{ [Function] smart: [Function: webpackMerge] }
coffee> merge {module: loaders: [1]}, {module: loaders: [2]}
{ module: { loaders: [ 1, 2 ] } }
coffee> merge {module: loaders: [1]}, {module: loaders: []}
{ module: { loaders: [] } }

Is it expected behaviour?

UPD "webpack-merge": "0.15.0"

As of 0.8.3 webpackMerge mutates the inputs

This is a new behavior that didn't occur in 0.7.x; I'm not sure its a "bug" but it should at least be documented. The webpackMerge function mutates the inputs, as evidenced by the unit test below. It's a problem because I've been using webpackMerge in series to build multiple distinct configurations, for example in webpack.config.js:

module.exports = [
  webpackMerge({}, defaultConfig, commonConfig, clientConfig),
  webpackMerge({}, defaultConfig, commonConfig, serverConfig)
]

The failing test:

  it('should not mutate inputs', function () {
    const a = {
      output: {
        filename: 'bundle.js'
      }
    };
    const b = {
      output: {
        path: 'path/b'
      }
    };

    const aClone = JSON.parse(JSON.stringify(a));
    merge({}, a, b);

    assert.deepEqual(a, aClone); // this fails with actual: -    "path": "path/b"
  });

Incorrect behaviour of merge.smart

let a = { loaders:
   [ { test: /\.css$/,
       loaders: ['style', 'css'],
       include: '/path' } ] };

let b = { loaders:
   [ { test: /\.js$/,
       exclude: /node_modules/,
       loader: 'babel-loader' },
     { test: /\.css$/, loaders:['style', 'css'] },
     { test: /\.scss$/, loaders: [Object] },
     { test: /\.woff$/,
       loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=[path][name].[ext]' },
     { test: /\.woff2$/,
       loader: 'url-loader?limit=10000&mimetype=application/font-woff2&name=[path][name].[ext]' },
     { test: /\.(eot|ttf|svg|gif|png)$/, loader: 'file-loader' } ] }

merge.smart(a,b);

Result:

{ loaders:
   [ { test: /\.css$/,
       loaders: [Object],
       include: '/path' },
     { test: /\.js$/,
       exclude: /node_modules/,
       loader: 'babel-loader' },
     { test: /\.css$/, loaders: ['style', 'css'] },
     { test: /\.scss$/, loaders: [Object] },
     { test: /\.woff$/,
       loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=[path][name].[ext]' },
     { test: /\.woff2$/,
       loader: 'url-loader?limit=10000&mimetype=application/font-woff2&name=[path][name].[ext]' },
     { test: /\.(eot|ttf|svg|gif|png)$/, loader: 'file-loader' } ] }

I expect that there hasn't to be any duplications of test keys.

How to merge webpack.config.js

Is this the right package to merge an existing webpack.config.js:

var path = require('path');
var webpack = require('webpack');

module.exports = {
    entry: [
        './src/js/app'
    ],
    devtool: 'eval-source-map',
    output: {
        path: __dirname,
        filename: 'app.js',
        publicPath: '/js/'
    },
    module: {
        loaders: [{
            test: /\.js$/,
            loaders: ['babel'],
            include: path.join(__dirname, 'src/js')
        }]
    }
};

with

'use strict';

var path = require('path');
var webpack = require('webpack');
var js_dist = path.join(__dirname, './dist');

module.exports = [{
  name: 'chartComponent',
  entry: {
    line: './line.jsx',
  },
  output: {
    path: js_dist,
    filename: '[name].js'
  },
  module: {
    loaders: [
      {
        test: [/\.jsx$/],
        loaders: ["jsx-loader?insertPragma=React.DOM&harmony"],
      }
    ],
  },
  resolve: {
    extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx']
  }
}];

or better just to manually do it?

Webpack 2 support

My loaders (now rules) aren't being merged. I'm assuming this tool doesn't support Webpack 2 configs just yet?

Smart merge causes plugin like CopyWebpackPlugin to be removed

Upgrade to webpack-merge 2.2.0, and it caused the CopyWebpackPlugin to fail.

Looking at the merged "plugins" array in 2.0.0, the CopyWebpackPlugin is listed as { apply: [Function: apply] }, where other plugins look like: UglifyJsPlugin { options: { sourceMap: true, compress: [Object], mangle: [Object], output: [Object] } }

In 2.2.0. the reference to the CopyWebpackPlugin function is removed from the array.

Should a plugin like this be something the smart merging should handle?

Let's call it 1.0? Any features missing?

Hi,

I added a couple of missing features in 0.18.0 (mainly merge.smartStrategy and support for nested fields at merge.strategy). It's starting to feel like 1.0 to me.

Do you think there's anything particular missing? If I don't hear anything in a week or two, I'll publish the current as 1.0.

The interesting thing is that I've started to find the basic merging mechanism useful beyond webpack (Karma, table configuration). Maybe 1.0 would be a good chance to rename the package to reflect this fact?

Removed dependency `changelog` still shows up in NPM

hello,

been using webpack-merge for a project at work (thanks for writing it!). however, when attempting to shrinkwrap my package that uses webpack-merge, it complains about not having the dependencies for the changelog module (a module previous included in webpack-merge).

while the package is no longer included via package.json, it seems that the tarball of this library (found here) does include changelog inside the node_modules folder. i suspect that you may have this folder sitting around in your local directory, and it got bundled up by npm when publishing to the registry.

any chance we can update the npm tarball to remove that dependency?

Don't concat loaders if the first matching entry's include/exclude doesn't match

Since the title may be a bit confusing, here's an example:

loaders: [
    {
      test: /\.scss$/,
      include: [path.resolve('./app')],
      loaders: ['raw-loader', 'sass-loader?sourceMap']
    }, {
      test: /\.scss$/,
      exclude: [path.resolve('./app')],
      loaders: ['style-loader', 'css-loader?sourceMap', 'sass-loader?sourceMap']
    }
];

(smart) merged with

loaders: [
    {
      test: /\.scss$/,
      exclude: [path.resolve('./app')],
      loader: extractCssLoader // 
    }
];

produces

loaders: [
    {
      test: /\.scss$/,
      include: [path.resolve('./app')],
      loaders: ['raw-loader', 'sass-loader?sourceMap']
    }, {
      test: /\.scss$/,
      exclude: [path.resolve('./app')],
      loaders: ['style-loader', 'css-loader?sourceMap', 'sass-loader?sourceMap']
    },     {
      test: /\.scss$/,
      exclude: [path.resolve('./app')],
      loader: extractCssLoader
    }
];

This is because it gives up and concats the loader after realizing the exclude field doesn't match with the first loader entry (containing an include field). It should be merged the second scss loader entry.

I'm currently working through a PR that should address this and #38.

Overriding with an empty array is unexpected behaviour

If I do

var a = {
	plugins: [ pluginA ]
}

var b = {
	plugins: [ ]
}

var c = merge(a, b)

c.plugins will be empty. I expect merge to concatenates the arrays. So I would expect c.plugins to be [pluginA].

I see that this is in the documentation but it really feels like unexpected behaviour.

Future - Revise API

It could be a nice move to go "all in" with the customization API and decrease the API surface a notch in favor of a good extension point. Example:

merge(
  merge.unique(
    'plugins',
    IGNORE_PLUGINS,
    plugin => plugin.constructor && plugin.constructor.name
  ),
  merge.strategy({
    entry: 'prepend', // or 'replace', defaults to 'append'
    'module.loaders': 'prepend'
  }),
  merge.smart()
)(...);

Composed from top to bottom (could go from bottom to top Redux style too). First returning function wins.

The old merge would remain the same. It's the extra functionality that would have more room to grow. It actually goes a bit like this internally already.

The only problem is going to be the smart variant of strategy and particularly its prepend functionality. Strategies could become more granular like this to work around the problem:

merge(
  merge.unique(
    'plugins',
    IGNORE_PLUGINS,
    plugin => plugin.constructor && plugin.constructor.name
  ),
  merge.strategy('entry', 'prepend'),
  merge.smartStrategy('module.loaders', 'prepend')
)(...);

Figure out a neater way to compose functions (postcss)

Loaders like postcss-loader define a part of their configuration through functions. You might have

postcss: function () {
  return [precss, autoprefixer];
}

The problem is that it's not possible to split this up neatly yet. Ideally you should be able to have

postcss: function () {
  return [precss];
}

and

postcss: function () {
  return [autoprefixer];
}

The question is, how to compose this through webpack-merge?

Allow for a replace strategy

Improve the strategy function to allow for replacing a setting instead of merging.

const baseConfig = {
  entry: {
    tracking: './tracking',
    main: './production-index',
  },
  resolve: {
    extensions: [ '.web.js' '.js' ],
  },
  plugins: [
    new NoErrorsPlugin(),
  ],
};

module.export = merge.strategy({ entry: 'replace', 'resolve.extensions': 'replace' })(baseConfig, {
  entry: {
    main: './development-index',
  },
  resolve: {
    extensions: [ '.dev.web.js', '.web.js', '.dev.js', '.js' ] 
  },
  plugins: [
    new HotModuleReplacementPlugin(),
  ],
});

// Output
{
  entry: {
    main: './development-index',
  },
  resolve: {
    extensions: [ '.dev.web.js', '.web.js', '.dev.js', '.js' ] 
  },
  plugins: [
    new NoErrorsPlugin(),
    new HotModuleReplacementPlugin(),
  ],
}

Customize plugins merging

I want to ignore some plugins when merging, like webpack.HotModuleReplacementPlugin.

Example:

const webpack = require('webpack');
const merge = require('webpack-merge');

const config1 = {
	plugins: [
		new webpack.DefinePlugin({
			'process.env': {
				NODE_ENV: JSON.stringify('development'),
			},
		}),
		new webpack.HotModuleReplacementPlugin(),
	],
};

const config2 = {
	plugins: [
		new webpack.DefinePlugin({
			'process.env.ELASTICSEARCH_URL': JSON.stringify(process.env.ELASTICSEARCH_URL),
			'process.env': {
				NODE_ENV: JSON.stringify('development'),
			},
			__CLIENT__: true,
		}),
		new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
		new webpack.HotModuleReplacementPlugin(),
	],
};

const merged = merge(config1, config2);
console.log(merged);

Current behavior:

{
	plugins: [
		DefinePlugin { definitions: [Object] },
		HotModuleReplacementPlugin { multiStep: undefined, fullBuildTimeout: 200 },
		DefinePlugin { definitions: [Object] },
		IgnorePlugin { resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ },
		HotModuleReplacementPlugin { multiStep: undefined, fullBuildTimeout: 200 }
	]
}

Desired behavior:

{
	plugins: [
		DefinePlugin { definitions: [Object] },
		HotModuleReplacementPlugin { multiStep: undefined, fullBuildTimeout: 200 },
		DefinePlugin { definitions: [Object] },  // Two different instances of DefinePlugin are preserved
		IgnorePlugin { resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ },
		// Second HotModuleReplacementPlugin was ignored
	]
}

List of ignored plugins should be configurable. I also don’t have access to config2, since it's user config and I can’t predict it’s content.

Plugins overwrite each other when same plugin used

Given this example:

const a = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery'
    })
  ]
};

const b = {
  plugins: [
    new webpack.ProvidePlugin({
      Promise: 'bluebird'
    })
  ]
};

const c = merge(a,b);

It would seem that the c only has the plugins defined in b since they are of the same plugin provider.

Would there be a way to have them becoming the following result?

const c = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery'
    }),
    new webpack.ProvidePlugin({
      Promise: 'bluebird'
    })
  ]
};

or merged to:

const c = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      Promise: 'bluebird'
    })
  ]
};

Why can't I set a common include/exclude for a loader?

Below are excerpts from my configs, actual merge.smart output and my expectation of the output.

Could you please explain how I would be able to achieve my desired outcome?

// Common.config.js
{
    // ...
    module: {
        loaders: [
            {
                test: /\.js$/,
                include: [
                    path.resolve(rootDir, 'apps'),
                    path.resolve(rootDir, 'lib'),
                    path.resolve(rootDir, 'thirdparty'),
                ],
                exclude: /node_modules/,
                loaders: ['babel'],
            }
        ],
    },
    // ...
}
// production.config.js
{
    // ...
    module: {
        loaders: [
            {
                test: /\.js$/,
                loaders: ['strip?strip[]=debug'],
            }
        ],
    },
    // ...
}
// Actual merge.smart output
{
    // ...
    module: {
        loaders: [
            {
                test: /\.js$/,
                loaders: ['strip?{"strip":["debug"]}', 'babel']
            },
        ],
    },
    // ...
}
// Expected merge.smart output
{
    // ...
    module: {
        loaders: [
            {
                test: /\.js$/,
                include: [
                    '/resolved/path/to/apps',
                    '/resolved/path/to/lib',
                    '/resolved/path/to/thirdparty',
                ],
                exclude: /node_modules/,
                loaders: ['strip?{"strip":["debug"]}', 'babel']
            },
        ],
    },
    // ...
}

is it intentional that webpack-merge mutates its arguments?

e.g. if I do this:

console.log('before: ', baseCommon.module.loaders.length);

const result = merge(docsCommon, baseCommon, base, {
  entry: ['webpack-hot-middleware/client'],
  module: { loaders, preLoaders },
  plugins
});

console.log('after: ', baseCommon.module.loaders.length);

I get:

before: 11
after: 13

Could the README be updated to explain what this does?

jlpsm5d

Some users may be a bit confused regarding what this package actually does. The readme suggests that "Normal merge function isn't that useful with webpack configuration as it will override object keys and arrays by default.", but if you don't know what the normal merge function is in context of WebPack then it's difficult to know what this does.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.