Home > Enterprise >  Complex sorting conditions in JS
Complex sorting conditions in JS

Time:02-04

I have a list of files that I'd like to sort primarily by name, but if the names (minus hash and extension) match, I'd like to sort by extension. The extensions (and order) are as follows: .js, .js.map, .esm.js, .esm.js.map, .css, and .css.map.

The issue I'm running up against (despite having a working solution) is that this becomes very complex very quickly. It's quite hard (IMO) to follow what this is doing, so I'm hoping I'm just being silly and there's a simpler, more scalable way to go about doing this sort of thing?

I very well might be able to simplify the code below, only just got it working (assuming there's no edge cases I haven't missed), but I'm more looking for frameworks/patterns for doing sorting of this kind. Devolving into a bunch of nested if conditions (as I might do if a couple more extensions were tossed into the mix) seems not great.

Current code:

files.sort((a, b) => {
    const assetName = ({ name }) => name.match(/([^.]*)/);

    if (assetName(a)?.[1] === assetName(b)?.[1]) {
        if (/\.css\.map$/.test(a.name) && /\.css$/.test(b.name)) return 1;
        if (/\.css(\.map)?$/.test(b.name)) return -1;

        if (
            /(?<!\.esm)\.js$/.test(a.name) ||
            (/(?<!\.esm)\.js(\.map)?$/.test(a.name) && /\.esm\.js(\.map)?/.test(b.name))
        ) {
            return -1;
        }
    }
    return a.name < b.name ? -1 : 1;
});

Example data in desired output order:

bundle.abcde.js
bundle.abcde.js.map
bundle.12345.esm.js
bundle.12345.esm.js.map
bundle.abc12.css
bundle.abc12.css.map

Thanks!

CodePudding user response:

One way to simplify your code is to create an array of objects that define the sort order of each extension and use the array index in the sort comparison. For example:

const extSortOrder = [
  { ext: /\.css$/, order: 1 },
  { ext: /\.css\.map$/, order: 2 },
  { ext: /\.js$/, order: 3 },
  { ext: /\.js\.map$/, order: 4 },
  { ext: /\.esm\.js$/, order: 5 },
  { ext: /\.esm\.js\.map$/, order: 6 }
];

files.sort((a, b) => {
    const assetName = ({ name }) => name.match(/([^.]*)/);
    const aSortOrder = extSortOrder.findIndex(e => e.ext.test(a.name));
    const bSortOrder = extSortOrder.findIndex(e => e.ext.test(b.name));

    if (assetName(a)?.[1] === assetName(b)?.[1]) {
        return aSortOrder - bSortOrder;
    }
    return a.name < b.name ? -1 : 1;
});

CodePudding user response:

If you know the extension order desired, and you don't have a huge number of possible extensions, you can put them into an array and sort based off of that array.

A potential complication is the overlap of extensions. A filename that ends in esm.js also ends in .js - for the general case, how would you determine which one takes priority? It'd be good if you could unambiguously separate the extension part from the rest of the filename - for example, one approach would be to configure things such that the final part of the filenames have at least 4 characters, which can be distinguished from the extension part that has 3 or fewer.

const extensions = '.js, .js.map, .esm.js, .esm.js.map, .css, .css.map'.split(', ');

const files = `bundle.12345.esm.js
bundle.abcde.js
bundle.abc12.css
bundle.abcde.js.map
bundle.abc12.css.map
bundle.12345.esm.js.map`.split('\n');
const getExt = filename => filename.match(/(?:\.[^\.]{1,3}) $/)[0];
files.sort((a, b) =>
  extensions.indexOf(getExt(a)) -
  extensions.indexOf(getExt(b))
);
console.log(files);

  • Related