Как транспилировать ES модули с помощью Webpack

2 месяца назад·10 мин. на чтение

В экосистеме JavaScript webpack выделяется как основной инструмент для объединения нашего кода. Но прежде чем мы углубимся в то, как webpack это делает, разберемся, что такое ES-модули.

Что такое ES-модули?

ES модули (ESM) являются рекомендуемым способом написания кода как для Node.js, так и для браузера. В настоящее время модули ES поддерживаются всеми основными браузерами и представляют собой официальный стандартный формат для упаковки и повторного использования кода JavaScript в Интернете. Мы можем определять и использовать пакеты в модульной системе с ключевыми словами export и import.

Импорт и экспорт функций

Ниже приведен пример импорта и экспорта функции с синтаксисом ES модулей:
// smallestNumber.js
export function smallestNumber(arr) {
    let smallestNumber = arr[0]
    let smallestIndex = 0
    for(let i = 1; i < arr.length; i++) {
       if(arr[i] < smallestNumber) {
         smallestNumber = arr[i]
         smallestIndex = i 
       }
    }
     return smallestNumber;
}
Обратите внимание, ключевое слово export полезно, поскольку оно позволяет нам сделать нашу функцию smallestNumber доступной для других модулей, которым необходимо ее вызвать. Помимо обычных функций, мы можем экспортировать константы, классы и даже просто переменные. Также обратите внимание, что у нас может быть экспорт по умолчанию с ESM.
// result.js
import { smallestNumber } from './smallestNumber.js';

console.log(smallestNumber([3,2,5,6,0,-1]))

// возвращает -1

Транспилирование кода

В конце концов, мы сосредоточимся на механике процесса транспиляции кода в следующем разделе, но перед этим давайте в первую очередь разберемся в важности этого процесса. Как мы уже упоминали ранее, старые версии JavaScript, такие как ES5, должны иметь возможность запускать наш новый код, а также понимать его синтаксис. Это означает, что должен быть способ полной обратной совместимости языка. Обратная совместимость является одним из наиболее важных аспектов, которые следует учитывать и расставлять приоритеты при добавлении новых функций в язык программирования. Для ESM среда выполнения Node.js может определить систему/формат модуля, который она должна использовать по умолчанию, на основе поля type, если оно установлено в module в package.json, который находится в корне проекта.
// package.json 
{
  "type": "module"
}
Приведенная выше настройка позволяет создавать все файлы, которые находятся на том же уровне структуры папок, что и package.json по умолчанию ESM. В качестве альтернативы мы можем установить тип commonjs, и среда выполнения Node заставит все файлы соответствовать системе Common JS Module по умолчанию. Хотя обычно это поведение по умолчанию, если поле type не указано. По сути, когда модули помечены как модули ECMAScript, среда выполнения Node использует другой шаблон или метод для разрешения импорта файлов. Например, импорт теперь более строгий, что означает, что мы должны добавить полное имя файла с их расширениями для относительных путей или запросов, в зависимости от обстоятельств.

Что такое транспиляция кода?

С введением новой версии JavaScript и изменениями в синтаксисе (также известным как ES2015), введением TypeScript, надмножества JavaScript и других достижений, таких как, например, CoffeeScript, написание JavaScript, который работает везде, уже не так просто, как раньше. Как мы, возможно, уже знаем, разные браузеры имеют разные движки JavaScript, и их различные уровни принятия и поддержки этих новых функций JS могут различаться, потому что они не соответствовали спецификации языка в одинаковое время. Это привело к тому, что код может работать в одном браузере и не работать в другом. Таким образом, суть транспиляции заключается в том, чтобы иметь возможность конвертировать новый синтаксис JS ES2015 в старый синтаксис ES5, чтобы код мог работать в старых браузерах. Например, шаблонные литералы или, оператор слияения null (??) и другие функции ES2015 или ES2015+ по-прежнему не имеют полной поддержки браузеров и серверной среды выполнения, поэтому нам может потребоваться транспилировать наш код для поддержки этих версий. В основном, инструменты, называемые загрузчиками (loaders), такие как Babel, Traceur и т. д., используются в сочетании с webpack для транспиляции кода. На высоком уровне транспиляторы работают на разных языках программирования, считывая исходный код строка за строкой и выдавая эквивалентный результат. Например, мы можем захотеть транспилировать кодовую базу TypeScript в старый добрый JavaScript. В целом, транспиляторы позволяют нам уверенно использовать новые, нестандартизированные возможности JavaScript. В настоящее время лучшим подходом к работе с модулями ES как в среде Node.js, так и в среде браузера является их транспилирование в формат модуля CommonJS с помощью Babel.

Что такое Webpack ?

Webpack — это инструмент сборки, который помогает объединить наш код и его зависимости в один файл JavaScript. Можно также сказать, что webpack — это своего рода сборщик статических модулей для JavaScript-приложений. Это связано с тем, что он применяет такие методы, как встряхивание дерева и компиляция (которая состоит из этапов транспиляции и минификации) к нашему исходному коду. Упаковщики, такие как webpack, работают рука об руку с транспиляторами. Это означает, что это совершенно разные, но дополняющие друг друга наборы инструментов. Поэтому нам нужно настроить webpack для работы с транспилятором — скажем, Babel. Как мы упоминали ранее, транспиляторы либо выполняют работу по компиляции одного языка в другой, либо делают язык обратно совместимым. Webpack довольно хорошо работает с Babel, а также легко настраивается. Например, мы можем настроить Babel для работы с webpack, создав конфигурационный файл webpack (webpack.config.js) использование плагина Babel — по сути, экосистема плагинов webpack — это то, что делает webpack тем, что есть. С другой стороны, Babel может быть сконфигурирован с помощью babel.config.js файл или .babelrc файл.

Почему webpack?

Как вы, возможно, знаете, webpack поддерживает несколько типов модулей из коробки, включая модули CommonJS и ES. Webpack также работает как на клиентском, так и на серверном JavaScript, поэтому с помощью webpack мы также можем легко работать с ресурсами, такими как изображения, шрифты, таблицы стилей и так далее. Он остается действительно мощным инструментом, поскольку автоматически строит и выводит граф зависимостей на основе импорта и экспорта файлов (поскольку, по сути, каждый файл является модулем). Сочетание этого с загрузчиками и плагинами делает webpack отличным инструментом в нашем арсенале. Более подробно о том, как это работает под капотом, можно прочитать в документации. Что касается плагинов, webpack также имеет богатую экосистему плагинов. Плагины поддерживают webpack в выполнении некоторой грязной работы, такой как оптимизация пакетов, управление ресурсами и так далее. Подводя итог, можно сказать, что комплектация с такими инструментами, как webpack, является самым быстрым способом работы или обеспечения обратной совместимости с модулями в наши дни, поскольку ESM постепенно набирает обороты в качестве официального стандарта для повторного использования кода в экосистеме. Webpack также поддерживает загрузчики, которые помогают ему решать, как обрабатывать, объединять и обрабатывать несобственные модули или файлы. Важно отметить, как webpack относится к загрузчикам. Загрузчики оцениваются и выполняются снизу вверх. Таким образом, последний загрузчик выполняется первым, и так далее, и тому подобное, именно в таком порядке. В этой статье мы сосредоточимся на том, как webpack транспилирует или обрабатывает модули ECMAScript.

Использование загрузчиков для транспиляции

Загрузчики преобразуют файлы из одного языка программирования в другой. Например, ts-loader может преобразовывать или транспилировать TypeScript в JavaScript. Обычно мы используем загрузчики в качестве зависимостей разработки. Например, давайте посмотрим, как мы можем использовать ts-loader. Для установки мы можем выполнить следующее:
npm install --save-dev ts-loader
Затем мы можем использовать этот загрузчик, чтобы указать webpack правильно обрабатывать все файлы TypeScript в нашем исходном коде. Ознакомьтесь с примером webpack.config.js файл ниже.
module.exports = {
  module: {
    rules: [
      { test: /.ts$/, use: 'ts-loader' },
    ],
  },
};
Здесь, как мы уже упоминали, мы говорим webpack обрабатывать все пути к файлам, оканчивающиеся на .ts и транспилировать их в синтаксис JavaScript, понятный браузеру и среде выполнения Node. Это означает, что загрузчики также могут работать в среде Node.js и, следовательно, также следовать стандарту разрешения модулей.

Соглашения об именовании загрузчиков

Общий способ именования загрузчиков является последовательным, так как загрузчики именуются своим именем и дефисом — обычно как xxx-loader. Например, babel-loader, ts-loader и так далее. Для нашего примера использования нас особенно интересует ESNext или babel-loader, загрузчик, созданный и поддерживаемый сообществом. Более подробную информацию о загрузчиках можно найти в документации webpack.

Некоторые основные понятия webpack

Чтобы понять, как работает webpack, в этом разделе рассматриваются некоторые высокоуровневые концепции, о которых должны знать читатели. Как мы упоминали ранее, webpack использует граф зависимостей, что означает, что он рекурсивно строит связь, включающую каждый модуль, который нужен приложению или от которого зависит, а затем объединяет все эти модули в выходной файл, готовый к использованию. Это означает, что webpack должен иметь точку входа — и это действительно так. При настройке конфигурации webpack точкой входа является начало проверок webpack пути к файлу, прежде чем он начнет строить внутренний граф зависимостей для нашего приложения. Обратите внимание, что webpack также поддерживает несколько точек входа для нашего приложения.
module.exports = {
  entry: ['./path/to/my/entry1/file1.js', './path/to/my/entry2/file2.js']
};
Чтобы добавить несколько точек входа, мы можем использовать массив. В webpack v5 теперь у нас может быть пустой объект входа. После того, как webpack построит граф зависимостей внутри и завершит процесс объединения, он должен вывести пакет в другой путь к файлу, который нам затем нужно обработать. Это то, что делает output. Он сообщает webpack, какой путь выдавать созданные пакеты и как называются файлы.
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js',
  },
};
Начиная с версии 4.0.0, webpack не требует конфигурационного файла для объединения проекта, хотя и предполагает, что вы определили точку входа в приложение с папкой и путем к файлу как src/index.js, и что вывод упакован в dist/main.js путь к папке, минимизированный, оптимизированный и готовый к продакшену.

Настройка webpack и Babel

В webpack была реализована полная поддержка использования встроенного синтаксиса модуля ES2015. Это означает, что мы можем использовать операторы import и export, не полагаясь на внешние инструменты транспиляции или зависимости, такие как Babel. Тем не менее, все же рекомендуется настроить Babel, на случай, если есть другие, более новые функции ES2015+, которые webpack еще не принял во внимание. В зависимости от форматов модулей webpack проверяет ближайший package.json и обеспечивает соблюдение соответствующих рекомендаций. При использовании webpack для объединения нашего кода обычно рекомендуется придерживаться синтаксиса одного модуля, чтобы позволить webpack правильно обрабатывать собранный вывод и, следовательно, предотвращать нежелательные ошибки. Чтобы начать, нам нужно убедиться, что на наших компьютерах установлен интерфейс командной строки webpack. Затем мы можем использовать команду CLI init, чтобы быстро запустить конфигурацию webpack в соответствии с требованиями нашего проекта. Мы можем сделать это, просто выполнив npx webpack-cli init и соответствующим образом отвечая на запросы. Теперь нам нужно скомпилировать наш код ES2015 в ES5, чтобы мы могли использовать его в различных средах браузера или средах выполнения. Для этого нам нужно установить Babel и все его зависимости, необходимые для webpack. Давайте установим следующее:
  • Babel Core
  • Babel Loader, webpack загрузчик, который взаимодействует с Babel Core
Для установки мы можем выполнить:
npm i webpack webpack-cli webpack-dev-server @babel/core @babel/preset-env babel-loader rimraf  -D
По окончанию установки наш package.json должно выглядеть следующим образом:
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "A demo of webpack with babel",
  "main": "dist/bundle.js",
  "scripts": {
    "build": "node_modules/.bin/webpack --config webpack.config.js --mode=production",
    "watch": "node_modules/.bin/webpack --config webpack.config.js --mode=development -w",
    "prebuild:dev": "rimraf dist"
  },
  "author": "Name",
  "license": "MIT",
  "dependencies": {},
  "devDependencies": {
    "@babel/core": "^7.16.0",
    "@babel/preset-env": "^7.16.4",
    "babel-loader": "^8.2.3",
    "rimraf": "^3.0.2",
    "webpack": "^5.64.3",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.5.0"
  },
  "type": "module"
}
Обратите внимание, что наши зависимости закреплены, чтобы обеспечить согласованность при запуске нашего приложения в будущем. Пакет @babel/preset-env преобразует весь код ES2015–ES2020 в ES5 или практически в любую целевую среду, которую мы укажем в целевых параметрах. Обычно он нам нужен, когда мы намереваемся установить конкретную цель, которая затем позволяет Babel нацелиться на эту конкретную среду. Этот пакет в основном проверяет указанное целевое окружение по своему внутреннему отображению, а затем составляет список плагинов, которые он передает в Babel для транспиляции кода. Это приводит к уменьшению количества пакетов JavaScript. Поэтому, если мы не укажем target, размер выводимого кода будет больше, потому что по умолчанию плагины Babel группируют синтаксические функции ECMAScript в коллекцию связанных функций. Более того, с опцией меньшего размера пакета и большего прироста производительности, мы можем использовать babel/preset-modules, которые в конечном итоге будут объединены в ядро @babel/preset-env.

Настройка webpack.config.js

Теперь давайте перейдем к настройке нашего файла webpack. Создайте новый webpack.config.js в корне нашего проекта.
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default  {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js',
    },
    experiments: {
        outputModule: true,
    },
    plugins: [
       //empty pluggins array
    ],
    module: {
         // https://webpack.js.org/loaders/babel-loader/#root
        rules: [
            {
                test: /.m?js$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
            }
        ],
    },
    devtool: 'source-map'
}
В приведенном выше конфиге мы установили для поля outputModule: true, что необходимо, если мы намерены использовать webpack для компиляции публичной библиотеки, предназначенной для использования другими. babel-loader загружает код ES2015+ и транспилирует его в ES5 с помощью Babel. Как вы также можете видеть в конфигурационном файле, у нас есть свойство module, которое имеет свойство rules, содержащее массив для настройки отдельных загрузчиков, которые могут нам понадобиться для нашей конфигурации webpack. Здесь мы добавили загрузчик webpack и установили необходимые параметры в соответствии с требованиями нашего проекта. Обратите внимание, что test представляет собой регулярное выражение, соответствующее абсолютному пути к каждому файлу и проверяющее наличие расширений файлов. В приведенном выше примере мы проверяем, заканчивается ли наш файл на .mjs или .js расширение.

Использование (и неиспользование) .babelrc файл

Теперь мы можем сконфигурировать Babel, создав .babelrc , также в корне нашего проекта. Содержимое файла показано ниже:
 {
    "presets": [
      [
        "@babel/preset-env",
        {
          "targets": {
            "esmodules": true
          }
        }
      ]
    ]
  }
В случае, если мы не хотим использовать .babelrc, мы также можем добавить пресеты в объект options внутри массива rules, например:
module: {
  rules: [
    {
      test: /.m?js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env', "es2015", "es2016"],
        }
      }
    }
  ]
}

Настройка пресетов и добавление плагинов

Нам нужно установить пресеты так, чтобы функции ES2015 в нашем коде могли быть преобразованы в ES5. В массиве options мы также можем добавить плагины, которые мы хотим добавить в нашу конфигурацию. Например, мы можем добавить эту plugins: ['@babel/plugin-transform-runtime'], которая устанавливает среду выполнения Babel, которая отключает автоматическое внедрение среды выполнения для каждого файла, чтобы предотвратить раздувание. Чтобы включить это в наш код, нам нужно установить их, выполнив:
npm install -D @babel/plugin-transform-runtime
Мы также должны добавить @babel/runtime в качестве зависимости, выполнив npm install @babel/runtime. Также обратите внимание, что в объекте rules мы указываем webpack исключить файлы в папке node_modules со свойством exclude. Это сделано для того, чтобы у нас был более быстрый процесс объединения, так как мы не хотим объединять нашу node_modules папку. Также бывают случаи, когда мы хотим, чтобы webpack обрабатывал модули ES 2015. Чтобы разрешить это, нам нужно использовать плагин ModuleConcatenationPlugin. Этот плагин включает некоторую форму поведения конкатенации в webpack, называемую поднятием области, что является возможной благодаря синтаксису ESM. По умолчанию этот плагин уже включен в рабочем режиме и отключен в противном случае.

Итоги

Чтобы избежать проблем с совместимостью, нам нужно транспилировать наш код из ES2015 в ES5. Здесь на помощь приходит webpack. Webpack объединяет наш код и выводит транспилированную версию в целевой файл, как указано в конфигурационном файле. В нашем конфигурационном файле webpack правила модуля позволяют нам указывать различные загрузчики, что является простым способом отображения загрузчиков. В этом посте мы использовали только загрузчик Babel, но есть много других загрузчиков, которые мы также можем использовать в экосистеме. Что касается улучшений и изменений в последнем выпуске webpack 5, теперь есть встроенная поддержка асинхронных модулей. Как следует из названия, асинхронные модули основаны на Promise и поэтому не разрешаются синхронно. Импорт асинхронных модулей через require() теперь также будет возвращать Promise, который разрешается в их экспорт.

Что такое каррирование? Функциональное программирование

2 года назад·5 мин. на чтение

В этой статье на простых и доступных примерах рассмотрим одну из концепций функционального программирования - Каррирование.

Это серия статей о функциональном программировании:
  1. Парадигмы программирования
  2. Композиция
  3. Функторы
  4. Каррирование (рассматривается в этой статье)
  5. Чистые функции
  6. Функции первого класса

Что такое каррирование?

Каррированная функция — это функция, которая продолжает возвращать функции до тех пор, пока не будут отправлены все ее параметры.

Как работает каррирование?

Предположим, есть функция сложения add.
const add = (a, b) => a + b
Простейшая реализация каррирования — заставить функцию возвращать функцию и т. д.
const add = (a) => (b) => a + b
Эту функцию можно использовать так.
const addOne = add(1) // addOne = (b) => 1 + b
Представим, что у нас есть функция curry, которая принимает функцию и каррирует ее.
const add = curry((a, b) => a + b)
Как мы видим, curry — это функция, которая использует другую функцию для ленивой обработки параметров. Итак, теперь мы можем вызвать ее следующим образом.
const addOne = add(1) // addOne = (b) => 1 + b
Итак, сначала мы создали addOne, передав 1 в качестве первого параметра (a) каррированной функции добавления. Что привело к другой функции, которая ожидает остальные параметры, где логика добавления не будет выполняться, пока не будут предоставлены все параметры.
addOne(2) // 3
Теперь, передавая 2 (как b) в addOne выполняет логику 1 + 2.

Пояснение к функции curry

Функция curry принимает функцию и делает ее параметры ленивыми, другими словами, вы предоставляете эти параметры по мере необходимости. Так же, как addOne. Вы по-прежнему можете вызывать каррированную версию функции добавления следующим образом.
const three = add(1, 2)
Таким образом, он либо принимает аргументы по частям, либо все аргументы сразу.

Для чего нужно каррировать функции?

Каррирование делает код:
  • Чище
  • Уменьшает количество повторяющихся параметров и делает код более лаконичным
  • Более компонуемым
  • Переиспользуемым

Почему каррирование делает код лучше?

Некоторые функции принимают конфигурацию в качестве входных данных

Если у нас есть функции, которые принимают конфигурацию (начальные настройки), нам лучше их каррировать, потому что эти конфигурации, вероятно, будут повторяться снова и снова. Предположим, что у нас есть функция перевода, которая принимает локаль и текст для перевода.
const translate = (locale, text) => { /* логика перевода */ }
Использование будет выглядеть так.
translate('ru', 'Hello')
translate('ru', 'Goodbye')
translate('ru', 'How are you?')
Каждый раз, когда мы вызываем translate, мы должны указывать язык и текст. Что является избыточным для предоставления локали при каждом вызове. Но вместо этого давайте каррируем translate следующим образом.
const translate = curry(
  (locale, text) => { /* логика перевода */ }
)

const translateToRu = translate('ru')
Теперь translateToRu имеет ru в качестве locale, предоставленного каррированной функции translate, и ожидает текста. Мы можем использовать это так.
translateToRu('Hello')
translateToRu('Goodbye')
translateToRu('How are you?')
Каррирование действительно внесло улучшения, нам не нужно каждый раз указывать локаль. Вместо этого каррированная функция translateToRu содержит locale из-за каррирования. После каррирования - в этом конкретном примере код стал:
  • чище
  • менее многословным и менее избыточным.
Потому что мы отделили конфигурацию от фактических данных. Что очень удобно во многих случаях.

На практике

На практике у нас есть динамическая локаль (у каждого пользователя свой язык), может быть fr, en, de или что-то еще. Поэтому вместо этого лучше переименовать translateToRu в translateTo, где translateTo может быть загружен с любой локалью. Теперь у нас есть translate, который принимает locale как конфигурацию и text как данные. Благодаря тому, что translate каррирован, мы смогли отделить параметры конфигурации от данных.

Зачем отделять конфигурации от данных?

Многие компоненты и функции нуждаются в использовании некоторой функциональности (в нашем случае, translateTo), но не должны или не могут знать о части конфигурации (locale). Эти компоненты или функции имеют только часть данных (text). Таким образом, эти функции смогут использовать эту функцию без необходимости знать о части конфигурации. Таким образом, этот компонент или функция будут меньше связаны с системой, что сделает компоненты более компонуемыми и более удобными в сопровождении.

Когда применять каррирование?

Когда мы знаем, что в функции есть конфигурация и есть данные, лучше их каррировать. Каррирование даст нам возможность их разделить. И это признак зрелого дизайна системы. Потому что одним из основных столпов качества кода является разделение задач. Даже если функции нужны все параметры для правильной работы, мы все равно лучше знаем, когда передавать параметры и на каком уровне приложения.

Связь между замыканием и каррированием

Замыкание - это функция, возвращаемая «родительской» функцией и имеющая доступ к внутреннему состоянию родительской функции. Каррирование всегда приводит к замыканию. Потому что каждая функция, возвращаемая каррированной функцией, будет снабжена внутренним состоянием родителей.

Примеры каррирования

Перед тем как продолжить

Добавим некоторые утилиты, чтобы мы могли перейти к примерам. Прототип массива имеет такие утилиты, как filter, map и другие. Но они не поддерживают каррирование, потому что используют запись через точку (.). Итак, давайте конвертируем их в каррируемый формат.
const filter = (fn, list) => list.filter(fn)
const map = (fn, list) => list.map(fn)
const startsWith = (starter, s) => s.startsWith(starter)
Теперь мы можем использовать их так.
const lessThan21 = user => user.age < 21

// Вместо такого использования...
const filteredUsers = users.filter(lessThan21 )

// ...будем использовать такое
const filteredUsers = filter(lessThan21, users)
Мы исключили запись через точку и передали обработанные данные в качестве последнего параметра. Затем мы их каррируем. Функция curry будет принимать функцию и возвращать каррированную функцию.
const filter = curry((fn, list) => list.filter(fn))
const map = curry((fn, list) => list.map(fn))
const startsWith = curry((starter, s) => s.startsWith(starter))

Пример 1

Дан список чисел, нужно увеличить все числа на 1. Вход: [1, 2, 3, 4, 5] Выход: [2, 3, 4, 5, 6] Реализация:
// каррированная функция add была определена ранее
const addOne = add(1)
const incrementNumbers = map(addOne)
const incrementedNumbers = incrementNumbers(numbers)

Пример 2

Дана строка, оставить все слова, начинающиеся с буквы c. Вход: "currying javascript function” Выход: “currying” Реализация:
const startsWithC = startsWith('c')
const filterStartsWithC = filter(startsWithC)
const filteredWords = filterStartsWithC(words)

Пример 3

Дан список диапазонов и список чисел. Создайте массив функций, которые могут фильтровать числа на основе предоставленных диапазонов.
const ranges = [
  { min: 10, max: 100 }, 
  { min: 100, max: 500 }, 
  { min: 500, max: 999 }
]
const numbers = [30, 50, 110, 200, 650, 700, 1000]
// 30 и 50 в первом диапазоне
// 110 и 200 во втором диапазоне
// 650 и 700 в третьем диапазоне
// 1000 не принадлежит ни одному диапазону
Выход: массив функций. Каждая функция может принимать числа и возвращать отфильтрованные числа, которые находятся в заданном диапазоне.
const isInRange = curry(
  (range, val) => val > range.min && val < range.max
)

const filters = ranges.map((range) => filter(isInRange(range)))
В этом примере есть двойное каррирование, filter и isInRange. filters теперь представляют собой список функций, каждая из которых ожидает numbers для обработки.
Для лучшего понимания можно развернуть каррирование и вместо этого использовать обычные функции.
const isInRange = (range, val) => val > range.min && val < range.max

const filters = ranges.map(
  (range) => (numbers) => numbers.filter(
    number => isInRange(range, number)
  )
)

Итоги

Каррирование просто делает параметры ленивыми. Когда функция продолжает возвращать функцию до тех пор, пока все ее аргументы не будут выполнены, она вычисляет и возвращает результат. Мы также увидели, как это делает наш код чище, более оаконичным, более компонуемым и даже более пригодным для повторного использования на практических примерах. И это тоже является примером принципа разделения ответственности.