You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

469 lines
10 KiB

4 years ago
  1. [![build status](https://secure.travis-ci.org/survivejs/webpack-merge.svg)](http://travis-ci.org/survivejs/webpack-merge) [![codecov](https://codecov.io/gh/survivejs/webpack-merge/branch/master/graph/badge.svg)](https://codecov.io/gh/survivejs/webpack-merge)
  2. # webpack-merge - Merge designed for Webpack
  3. **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.
  4. 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.
  5. There's also a webpack specific merge variant known as `merge.smart` that's able to take webpack specifics into account (i.e., it can flatten loader definitions).
  6. ## Standard Merging
  7. ### **`merge(...configuration | [...configuration])`**
  8. `merge` is the core, and the most important idea, of the API. Often this is all you need unless you want further customization.
  9. ```javascript
  10. // Default API
  11. var output = merge(object1, object2, object3, ...);
  12. // You can pass an array of objects directly.
  13. // This works with all available functions.
  14. var output = merge([object1, object2, object3]);
  15. // Please note that where keys match,
  16. // the objects to the right take precedence:
  17. var output = merge(
  18. { fruit: "apple", color: "red" },
  19. { fruit: "strawberries" }
  20. );
  21. console.log(output);
  22. // { color: "red", fruit: "strawberries"}
  23. ```
  24. ### **`merge({ customizeArray, customizeObject })(...configuration | [...configuration])`**
  25. `merge` behavior can be customized per field through a curried customization API.
  26. ```javascript
  27. // Customizing array/object behavior
  28. var output = merge(
  29. {
  30. customizeArray(a, b, key) {
  31. if (key === 'extensions') {
  32. return _.uniq([...a, ...b]);
  33. }
  34. // Fall back to default merging
  35. return undefined;
  36. },
  37. customizeObject(a, b, key) {
  38. if (key === 'module') {
  39. // Custom merging
  40. return _.merge({}, a, b);
  41. }
  42. // Fall back to default merging
  43. return undefined;
  44. }
  45. }
  46. )(object1, object2, object3, ...);
  47. ```
  48. For example, if the previous code was invoked with only `object1` and `object2`
  49. with `object1` as:
  50. ```
  51. {
  52. foo1: ['object1'],
  53. foo2: ['object1'],
  54. bar1: { object1: {} },
  55. bar2: { object1: {} },
  56. }
  57. ```
  58. and `object2` as:
  59. ```
  60. {
  61. foo1: ['object2'],
  62. foo2: ['object2'],
  63. bar1: { object2: {} },
  64. bar2: { object2: {} },
  65. }
  66. ```
  67. then `customizeArray` will be invoked for each property of `Array` type, i.e:
  68. ```
  69. customizeArray(['object1'], ['object2'], 'foo1');
  70. customizeArray(['object1'], ['object2'], 'foo2');
  71. ```
  72. and `customizeObject` will be invoked for each property of `Object` type, i.e:
  73. ```
  74. customizeObject({ object1: {} }, { object2: {} }, bar1);
  75. customizeObject({ object1: {} }, { object2: {} }, bar2);
  76. ```
  77. ### **`merge.unique(<field>, <fields>, field => field)`**
  78. The first <field> is the config property to look through for duplicates.
  79. <fields> represents the values that should be unique when you run the field => field function on each duplicate.
  80. ```javascript
  81. const output = merge({
  82. customizeArray: merge.unique(
  83. 'plugins',
  84. ['HotModuleReplacementPlugin'],
  85. plugin => plugin.constructor && plugin.constructor.name
  86. )
  87. })({
  88. plugins: [
  89. new webpack.HotModuleReplacementPlugin()
  90. ]
  91. }, {
  92. plugins: [
  93. new webpack.HotModuleReplacementPlugin()
  94. ]
  95. });
  96. // Output contains only single HotModuleReplacementPlugin now.
  97. ```
  98. ## Merging with Strategies
  99. ### **`merge.strategy({ <field>: '<prepend|append|replace>''})(...configuration | [...configuration])`**
  100. Given you may want to configure merging behavior per field, there's a strategy variant:
  101. ```javascript
  102. // Merging with a specific merge strategy
  103. var output = merge.strategy(
  104. {
  105. entry: 'prepend', // or 'replace', defaults to 'append'
  106. 'module.rules': 'prepend'
  107. }
  108. )(object1, object2, object3, ...);
  109. ```
  110. ### **`merge.smartStrategy({ <key>: '<prepend|append|replace>''})(...configuration | [...configuration])`**
  111. The same idea works with smart merging too (described below in greater detail).
  112. ```javascript
  113. var output = merge.smartStrategy(
  114. {
  115. entry: 'prepend', // or 'replace'
  116. 'module.rules': 'prepend'
  117. }
  118. )(object1, object2, object3, ...);
  119. ```
  120. ## Smart Merging
  121. ### **`merge.smart(...configuration | [...configuration])`**
  122. *webpack-merge* tries to be smart about merging loaders when `merge.smart` is used. Loaders with matching tests will be merged into a single loader value.
  123. Note that the logic picks up webpack 2 `rules` kind of syntax as well. The examples below have been written in webpack 1 syntax.
  124. **package.json**
  125. ```json5
  126. {
  127. "scripts": {
  128. "start": "webpack-dev-server",
  129. "build": "webpack"
  130. },
  131. // ...
  132. }
  133. ```
  134. **webpack.config.js**
  135. ```javascript
  136. var path = require('path');
  137. var merge = require('webpack-merge');
  138. var TARGET = process.env.npm_lifecycle_event;
  139. var common = {
  140. entry: path.join(__dirname, 'app'),
  141. ...
  142. module: {
  143. loaders: [
  144. {
  145. test: /\.css$/,
  146. loaders: ['style', 'css'],
  147. },
  148. ],
  149. },
  150. };
  151. if(TARGET === 'start') {
  152. module.exports = merge(common, {
  153. module: {
  154. // loaders will get concatenated!
  155. loaders: [
  156. {
  157. test: /\.jsx?$/,
  158. loader: 'babel?stage=1',
  159. include: path.join(ROOT_PATH, 'app'),
  160. },
  161. ],
  162. },
  163. ...
  164. });
  165. }
  166. if(TARGET === 'build') {
  167. module.exports = merge(common, {
  168. ...
  169. });
  170. }
  171. ...
  172. ```
  173. **Loader string values `loader: 'babel'` override each other.**
  174. ```javascript
  175. merge.smart({
  176. loaders: [{
  177. test: /\.js$/,
  178. loader: 'babel'
  179. }]
  180. }, {
  181. loaders: [{
  182. test: /\.js$/,
  183. loader: 'coffee'
  184. }]
  185. });
  186. // will become
  187. {
  188. loaders: [{
  189. test: /\.js$/,
  190. loader: 'coffee'
  191. }]
  192. }
  193. ```
  194. **Loader array values `loaders: ['babel']` will be merged, without duplication.**
  195. ```javascript
  196. merge.smart({
  197. loaders: [{
  198. test: /\.js$/,
  199. loaders: ['babel']
  200. }]
  201. }, {
  202. loaders: [{
  203. test: /\.js$/,
  204. loaders: ['coffee']
  205. }]
  206. });
  207. // will become
  208. {
  209. loaders: [{
  210. test: /\.js$/,
  211. // appended because Webpack evaluated these from right to left
  212. // this way you can specialize behavior and build the loader chain
  213. loaders: ['babel', 'coffee']
  214. }]
  215. }
  216. ```
  217. **Loader array values `loaders: ['babel']` can be reordered by including
  218. original loaders.**
  219. ```javascript
  220. merge.smart({
  221. loaders: [{
  222. test: /\.js$/,
  223. loaders: ['babel']
  224. }]
  225. }, {
  226. loaders: [{
  227. test: /\.js$/,
  228. loaders: ['react-hot', 'babel']
  229. }]
  230. });
  231. // will become
  232. {
  233. loaders: [{
  234. test: /\.js$/,
  235. // order of second argument is respected
  236. loaders: ['react-hot', 'babel']
  237. }]
  238. }
  239. ```
  240. This also works in reverse - the existing order will be maintained if possible:
  241. ```javascript
  242. merge.smart({
  243. loaders: [{
  244. test: /\.css$/,
  245. use: [
  246. { loader: 'css-loader', options: { myOptions: true } },
  247. { loader: 'style-loader' }
  248. ]
  249. }]
  250. }, {
  251. loaders: [{
  252. test: /\.css$/,
  253. use: [
  254. { loader: 'style-loader', options: { someSetting: true } }
  255. ]
  256. }]
  257. });
  258. // will become
  259. {
  260. loaders: [{
  261. test: /\.css$/,
  262. use: [
  263. { loader: 'css-loader', options: { myOptions: true } },
  264. { loader: 'style-loader', options: { someSetting: true } }
  265. ]
  266. }]
  267. }
  268. ```
  269. In the case of an order conflict, the second order wins:
  270. ```javascript
  271. merge.smart({
  272. loaders: [{
  273. test: /\.css$/,
  274. use: [
  275. { loader: 'css-loader' },
  276. { loader: 'style-loader' }
  277. ]
  278. }]
  279. }, {
  280. loaders: [{
  281. test: /\.css$/,
  282. use: [
  283. { loader: 'style-loader' },
  284. { loader: 'css-loader' }
  285. ]
  286. }]
  287. });
  288. // will become
  289. {
  290. loaders: [{
  291. test: /\.css$/,
  292. use: [
  293. { loader: 'style-loader' }
  294. { loader: 'css-loader' },
  295. ]
  296. }]
  297. }
  298. ```
  299. **Loader query strings `loaders: ['babel?plugins[]=object-assign']` will be overridden.**
  300. ```javascript
  301. merge.smart({
  302. loaders: [{
  303. test: /\.js$/,
  304. loaders: ['babel?plugins[]=object-assign']
  305. }]
  306. }, {
  307. loaders: [{
  308. test: /\.js$/,
  309. loaders: ['babel', 'coffee']
  310. }]
  311. });
  312. // will become
  313. {
  314. loaders: [{
  315. test: /\.js$/,
  316. loaders: ['babel', 'coffee']
  317. }]
  318. }
  319. ```
  320. **Loader arrays in source values will have loader strings merged into them.**
  321. ```javascript
  322. merge.smart({
  323. loaders: [{
  324. test: /\.js$/,
  325. loader: 'babel'
  326. }]
  327. }, {
  328. loaders: [{
  329. test: /\.js$/,
  330. loaders: ['coffee']
  331. }]
  332. });
  333. // will become
  334. {
  335. loaders: [{
  336. test: /\.js$/,
  337. // appended because Webpack evaluated these from right to left!
  338. loaders: ['babel', 'coffee']
  339. }]
  340. }
  341. ```
  342. **Loader strings in source values will always override.**
  343. ```javascript
  344. merge.smart({
  345. loaders: [{
  346. test: /\.js$/,
  347. loaders: ['babel']
  348. }]
  349. }, {
  350. loaders: [{
  351. test: /\.js$/,
  352. loader: 'coffee'
  353. }]
  354. });
  355. // will become
  356. {
  357. loaders: [{
  358. test: /\.js$/,
  359. loader: 'coffee'
  360. }]
  361. }
  362. ```
  363. ## Multiple Merging
  364. ### **`merge.multiple(...configuration | [...configuration])`**
  365. Sometimes you may need to support multiple targets, *webpack-merge* will accept an object where each key represents the target configuration. The output becomes an *array* of configurations where matching keys are merged and non-matching keys are added.
  366. ```javascript
  367. var path = require('path');
  368. var baseConfig = {
  369. server: {
  370. target: 'node',
  371. output: {
  372. path: path.resolve(__dirname, 'dist'),
  373. filename: 'lib.node.js'
  374. }
  375. },
  376. client: {
  377. output: {
  378. path: path.resolve(__dirname, 'dist'),
  379. filename: 'lib.js'
  380. }
  381. }
  382. };
  383. // specialized configuration
  384. var production = {
  385. client: {
  386. output: {
  387. path: path.resolve(__dirname, 'dist'),
  388. filename: '[name].[hash].js'
  389. }
  390. }
  391. }
  392. module.exports = merge.multiple(baseConfig, production)
  393. ```
  394. > Check out [SurviveJS - Webpack and React](http://survivejs.com/) to dig deeper into the topic.
  395. ## Development
  396. 1. `npm i`
  397. 1. `npm run build`
  398. 1. `npm run watch`
  399. Before contributing, please open an issue where to discuss.
  400. ## License
  401. *webpack-merge* is available under MIT. See LICENSE for more details.