diff --git a/README.md b/README.md index 71b7f5f..5f5df52 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ The first `` is the config property to look through for duplicates. `` represents the values that should be unique when you run the field => field function on each duplicate. +When the order of elements of the `` in the first configuration differs from the order in the second configuration, the latter is preserved. + ```javascript const { mergeWithCustomize, unique } = require("webpack-merge"); diff --git a/src/unique.ts b/src/unique.ts index c7d29fa..e4c5542 100644 --- a/src/unique.ts +++ b/src/unique.ts @@ -3,26 +3,19 @@ function mergeUnique( uniques: string[], getter: (a: object) => string ) { + let uniquesSet = new Set(uniques) return (a: [], b: [], k: string) => - k === key && [ - ...difference(a, b, (item) => uniques.indexOf(getter(item))), - ...b, - ]; -} - -function difference(a: object[], b: object[], cb: (v: object) => number) { - const ret = a.filter((v, i) => { - const foundA = cb(v); - const foundB = cb(b[i] || {}); - - if (foundA >= 0 && foundB >= 0) { - return foundA !== foundB; - } - - return true; - }); - - return ret; + (k === key) && Array.from( + [...a, ...b] + .map((it: object) => ({ key: getter(it), value: it })) + .map(({ key, value }) => ({ key: (uniquesSet.has(key) ? key : value), value: value})) + .reduce( + (m, { key, value}) => { + m.delete(key); // This is required to preserve backward compatible order of elements after a merge. + return m.set(key, value) + }, + new Map()) + .values()) } export default mergeUnique; diff --git a/test/unique.test.ts b/test/unique.test.ts index 80c0284..e03ed27 100644 --- a/test/unique.test.ts +++ b/test/unique.test.ts @@ -59,7 +59,7 @@ describe("Unique", function () { assert.deepStrictEqual(output, expected); }); - it("should not lose any plugins", function () { + it("should not lose any trailing plugins", function () { const output = mergeWithCustomize({ customizeArray: unique( "plugins", @@ -89,6 +89,36 @@ describe("Unique", function () { assert.deepStrictEqual(output, expected); }); + it("should not lose any leading plugins", function () { + const output = mergeWithCustomize({ + customizeArray: unique( + "plugins", + ["HotModuleReplacementPlugin"], + (plugin) => plugin.constructor && plugin.constructor.name + ), + })( + { + plugins: [ + new webpack.DefinePlugin({}), + new webpack.HotModuleReplacementPlugin(), + ], + }, + { + plugins: [new webpack.HotModuleReplacementPlugin()], + } + ); + // The HMR plugin is picked from the last one due to + // default ordering! + const expected = { + plugins: [ + new webpack.DefinePlugin({}), + new webpack.HotModuleReplacementPlugin(), + ], + }; + + assert.deepStrictEqual(output, expected); + }); + it("should check only against named plugins (#125)", function () { const output = mergeWithCustomize({ customizeArray: unique(