Привет, Хабр!

Когда-то были времена, когда весь JavaScript-код приложения складывался в один огромный файл. Это было не только неудобно, но и было множество ошибок из-за глобального пространства имен и сложностей с зависимостями. Тогда появилась необходимость в модульности.

Многие при первой встречи с такими проблемами пытались разделить код на отдельные файлы и подключать через тег <script>. Но такое решение было очень далеко от идеала. Потом были попытки использовать различные библиотеки и инструменты, такие как RequireJS или Browserify, но каждый из них имел свои недостатки и ограничения.

Все изменилось с приходом ES6, который ввел нативную поддержку модулей и один из этих модулей - Rollup. Сегодня мы его и рассмотрим в статье.

Установим и настроим

Создадим директорию для проекта и переходим в неё:

mkdir my-rollup-project && cd my-rollup-project

Инициализируем с помощью NPM:

npm init -y

Установим Rollup как зависимость:

npm install rollup --save-dev

Создадим файл rollup.config.js в корневой директории проекта:

export default {
  input: 'src/main.js',
  output: {
    file: 'bundle.js',
    format: 'cjs'
  }
};

Добавим скрипт сборки в package.json:

"scripts": {
  "build": "rollup -c"
}

Для запуска сборки:

npm run build

Добавление CSS

Для работы с CSS потребуется плагин. Установим rollup-plugin-postcss:

npm install rollup-plugin-postcss --save-dev

Добавим этот плагин в rollup.config.js:

import postcss from 'rollup-plugin-postcss';

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife',
  },
  plugins: [
    postcss({
      extensions: ['.css'],
    }),
  ],
};

Теперь можно создать файл стилей styles.css в папке src и импортировать его в index.js:

/* src/styles.css */
body {
  background-color: #f0f0f0;
}
// src/index.js
import './styles.css';
console.log('Привет, Хабр!');

Работа с изображениями

Для включения изображений в бандл используем плагин rollup-plugin-img:

Обновим rollup.config.js, добавив этот плагин:npm install rollup-plugin-img --save-dev

Обновим rollup.config.js, добавив этот плагин:

import postcss from 'rollup-plugin-postcss';
import img from 'rollup-plugin-img';

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife',
  },
  plugins: [
    postcss({
      extensions: ['.css'],
    }),
    img({
      limit: 10000 // размер файла в байтах для инлайнинга изображений
    }),
  ],
};

Теперь можно добавить изображение в папку src и использовать его в CSS или JavaScript. Например, в styles.css:

body {
  background-image: url('./background.png');
  background-size: cover;
}

Или напрямую в index.js:

import background from './background.png';

document.body.style.backgroundImage = `url('${background}')`;

Также установим некоторые нужные плагины

@rollup/plugin-node-resolve: позволяет Rollup подключать внешние модули из node_modules:

npm install --save-dev @rollup/plugin-node-resolve

Включим в конфигурационный файл:

import resolve from '@rollup/plugin-node-resolve';
export default {
  //...
  plugins: [resolve()]
};

Абсолютно аналогично можно установить:

@rollup/plugin-commonjs: преобразует модули CommonJS в формат ES6, который может обрабатывать Rollup.

@rollup/plugin-json: позволяет Rollup импортировать данные из JSON-файлов.

@rollup/plugin-babel: интегрирует Babel для транспиляции кода в старые версии JavaScript.

Для Babel нужно будет создать.babelrc с настройками Babel:

{
  "presets": [
    ["@babel/preset-env", {
      "modules": false
    }]
  ]
}

@rollup/plugin-terser: минифицирует итоговый бандл для продакшена.

Основные функции Rollup

Tree-Shaking

Tree-Shaking – это процесс устранения кода, который фактически не используется в проекте.

Tree-Shaking работает, анализируя кодовые файлы, необходимые для запуска приложения, и включая только код, который действительно используется приложением. Этот процесс создаёт структуру данных, представляющую намерения вашего кода, состоящую из узлов, которые представляют код (функции, утверждения и т.д.). Эти узлы формируют структуру, похожую на дерево, и после её создания можно определить, какой код действительно используется приложением .

Tree-Shaking в JS работает благодаря использованию ES6 модулей, потому что они статичны в своей структуре. Каждый файл, проанализированный в процессе Tree-Shaking, будет иметь различные виды операторов экспорта как узел верхнего уровня создаваемой структуры данных. В файлах, которые импортируют эти экспортированные члены, использование импорта может быть отслежено.

Рассмотрим пример использования Tree-Shaking с помощью Rollup, демонстрирующий как неиспользуемый код может быть исключен из итогового бандла.

Предположим, есть два файла модулей: math.js и main.js. В math.js определены несколько функций, но main.js использует только одну из них.

math.js:

export const sum = (a, b) => a + b;
export const multiply = (a, b) => a * b;
export const subtract = (a, b) => a - b;

main.js:

import { sum } from './math.js';

console.log(sum(1, 2)); // Ожидается вывод: 3

Если мы используем Rollup для сборки, он анализирует зависимости и понимает, что только функция sum используется в main.js. Функции multiply и subtract не используются и, следовательно, могут быть исключены из финального бандла благодаря механизму Tree-Shaking.

Для запуска Tree-Shaking с Rollup, можно юзать следующий конфигурационный файл Rollup rollup.config.js:

export default {
  input: 'main.js', // точка входа вашего приложения
  output: {
    file: 'bundle.js', // имя итогового файла
    format: 'es' // формат модулей в итоговом бандле
  }
};

Затем, запускаем Rollup с этой конфигурацией, и он сгенерирует bundle.js, содержащий только используемый код. В результате размер итогового файла будет меньше, и приложение загрузится быстрее.

Code Splitting

Code-Splitting позволяет разделить код на разные части на основе различных точек входа и динамических импортов, используя механизм импорта формата вывода вместо пользовательского кода загрузчика . Это позволяет динамически загружать части приложения по мере необходимости.

Для демонстрации Code Splitting с помощью Rollup, рассмотрим пример, где у нас есть две точки входа в приложение и динамический импорт модуля.

Структура проекта:

  • src/

    • main1.js (точка входа 1)

    • main2.js (точка входа 2)

    • shared.js (модуль, используемый обеими точками входа)

    • dynamic.js (модуль, импортируемый динамически)

shared.js:

// экспортируем функцию, общую для main1.js и main2.js
export const sharedFunction = () => console.log('Это общая функция');

dynamic.js:

// экспортируем функцию для динамического импорта
export const dynamicFunction = () => console.log('Это динамическая функция');

main1.js:

import { sharedFunction } from './shared.js';

// использование общей функции
sharedFunction();

// динамический импорт модуля
import('./dynamic.js').then((module) => {
  module.dynamicFunction();
});

main2.js:

import { sharedFunction } from './shared.js';

// использование общей функции
sharedFunction();

// предположим, здесь есть дополнительный код специфичный для main2.js

Для поддержки Code Splitting в Rollup конфигурации нужно определить обе точки входа и настроить выходные параметры для генерации нескольких чанков:

rollup.config.js:

export default {
  input: ['src/main1.js', 'src/main2.js'],
  output: {
    dir: 'output',
    format: 'esm',
    chunkFileNames: '[name]-[hash].js'
  },
  plugins: [
    // здесь можно юзать плагины, например @rollup/plugin-node-resolve для разрешения модулей из node_modules
  ]
};

input определяется как массив с двумя точками входа. Rollup будет анализировать зависимости, обнаруживать общие модули и динамические импорты, и соответственно разделять код на чанки. В результате, shared.js будет вынесен в отдельный чанк, так как используется обеими точками входа, в то время как dynamic.js будет загружен динамически только при необходимости.

Настройка Rollup для работы с TypeScript, React

Для начала работы с TypeScript и React необходимо установить соответствующие пакеты и зависимости:

npm i --save-dev typescript react react-dom @types/react

Для интеграции TypeScript в проект с Rollup юзаем плагин @rollup/plugin-typescript. Простейший файл конфигурации rollup.config.js может выглядеть так:

import typescript from '@rollup/plugin-typescript';

export default {
  input: 'src/index.tsx', // точка входа приложения
  output: {
    dir: 'dist', // каталог для сгенерированных файлов
    format: 'esm', // формат модуля ES
  },
  plugins: [typescript()],
};

Эта конфигурация указывает Rollup на обработку файлов TypeScript и сборку их в модули ES6.

Файл tsconfig.json содержит настройки компилятора TypeScript. Пример базовой конфигурации:

{
  "compilerOptions": {
    "outDir": "./dist",
    "module": "ESNext",
    "target": "es5",
    "jsx": "react",
    "declaration": true,
    "declarationDir": "./dist"
  },
  "include": ["src/**/*"]
}

Здесь будет поддержка JSX, генерацию деклараций типов и компиляцию в ES5 для лучшей совместимости.

Для работы с React компонентами потребуется настроить Babel вместе с Rollup для транспиляции JSX:

npm install --save-dev @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript

Также нужно создать .babelrc или babel.config.json файл с соответствующими пресетами:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}

Это позволяет Babel корректно обрабатывать файлы .tsx и .ts, содержащие JSX.


Больше практических инструментов эксперты из OTUS рассматривают в рамках онлайн-курсов. С полным каталогом курсов можно ознакомиться по ссылке, а также не забывайте о нашем календаре бесплатных мероприятий.

Комментарии (3)


  1. yar3333
    10.04.2024 15:42
    +4

    Спасибо за статью:) Для тех, кто был шокирован тем, сколько всего нужно установить, чтобы использовать НАТИВНЫЕ модули: rollup - это бандлер, который заточен на стандарт ES6. Чем он лучше webpack-а, с учётом того, что последний уже в дореволюционной 2-ой версии поддерживал ES6, остаётся загадкой.


    1. skymal4ik
      10.04.2024 15:42
      +2

      Я в числе тех, кто шокирован. Уж точно не ожидал, что для использования нативного es6 нужны будут npm и Babylon. Хорошо хоть 19 нода и pm2 не нужны :)


    1. 19Zb84
      10.04.2024 15:42
      +2

      1. es6 можно использовать без бандлеров вообще. (я так чаще делаю, чем не делаю)

      2. esbuild единственый адекватный бандлер. Все работает из коробки

      3. Пока это самый быстрый бандлер из всех


        webpack очень медленный и громоздкий.