2 минуты с Webpack tree-shaking и re-export +11


Вступление


Позвольте мне начать. У нас был монолитный фронтэнд с большим наследием. Сервисы жили в одних файлах с компонентами. Всё было вперемешку и с лозунгом на фасаде: “Пусть всё будет под рукой?–?так легче найти, что надо". И не важно, что длина файла 200+, 300+, 500+ или даже больше строк кода.


Цель


Cделать всё читабельнее, меньше и быстрее.


Реализация


Разделить всё, что возможно на файлы и золотая пуля здесь это принцип единственной ответственности. Если у нас есть компонент и чистые функции внутри файла, мы их разделим.


С приходом ES6+, стало возможно использовать import … from синтакс – это отличная фича, ведь мы можем также использовать export … from.


Рефакторинг


Представьте себе файл с такой структурой:


// Старая, старая функция, которая живет здесь с начала времён
function multiply (a, b) {
  return a * b;
}
function sum (a, b) {
  return a + b;
}
function calculateSomethingSpecial(data) {
  return data.map(
    dataItem => sum(dataItem.param1, dataItem.param2)
  );
}

Мы можем разделить этот код на файлы таким образом:


Структура:


utils
  multiply.js
  sum.js
  calculateSomethingSpecial.js

и файлы:


// multiply.js

export default function multiply (a, b) {
  return a * b;
}
or
const multiply (a, b) => a * b;
// Синтаксис по желанию – эта статья не о нём.

// sum.js

export default function sum (a, b) {
  return a + b;
}

// calculateSomethingSpecial.js

import sum from "./sum";

export default function calculateSomethingSpecial(data) {
  return data.map(
    dataItem => sum(dataItem.param1, dataItem.param2));
}

Теперь мы можем импортировать функции по отдельности. Но с дополнительными строками и этими длинных именами в импортах это всё ещё выглядит ужасно.


// App.js

import multiply from '../utils/multiply';
import sum from '../utils/sum';
import calculateSomethingSpecial from '../utils/calculateSomethingSpecial';
...

А вот для этого у нас есть прекрасная фишка, которая появилась с приходом нового синтаксиса JS, который зовется реэкспортом (re-export). В папке мы должны сделать index.js файл, чтобы объединить все наши функции. И теперь мы можем переписать наш код таким образом:


// utils/index.js

export { default as sum } from './sum';
export { default as multiply } from './multiply';
export { default as calculateSomethingSpecial } from './calculateSomethingSpecial';

Чуть подшаманим App.js:


// App.js

import { multiply, sum, calculateSomethingSpecial } from '../utils';

Готово.


Тестирование.


Теперь давайте проверим, как наш Webpack скомпилирует build для продакшна. Давайте создадим небольшое приложение на React, чтобы проверить, как всё работает. Проверим загружаем ли мы только то, что нам нужно, или все что указано в index.js из папки utils.


// Переписанный App.js

import React from "react";
import ReactDOM from "react-dom";
import { sum } from "./utils";
import "./styles.css";
function App() {
  return (
    <div className="App">
      <h1>Re-export example</h1>
      <p>{sum(5, 10)}</p>
    </div>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Приложение:



Продакшн версия приложения:



// Код чанка main.js из прода

(window.webpackJsonp = window.webpackJsonp || []).push([[0], {
    10: function(e, n, t) {
        "use strict";
        t.r(n);
        // Вот наша фунция **sum** внутри компонента
        var r = t(0)
          , a = t.n(r)
          , c = t(2)
          , o = t.n(c);
        function l(e, n) {
            return e + n
        }
t(9);
        var u = document.getElementById("root");
        o.a.render(a.a.createElement(function() {
            return a.a.createElement("div", {
                className: "App"
            }, a.a.createElement("h1", null, "Re-export example"), a.a.createElement("p", null, l(5, 10)))
        }, null), u)
    },
    3: function(e, n, t) {
        e.exports = t(10)
    },
    9: function(e, n, t) {}
}, [[3, 1, 2]]]);
//# sourceMappingURL=main.e2563e9c.chunk.js.map

Как видно выше, мы загрузили только функцию sum из utils.
Давайте проверим еще раз, и на этот раз мы будем использовать multiply.


Приложение:



Продакшн версия приложения:



// Код чанка main.js из прода

(window.webpackJsonp = window.webpackJsonp || []).push([[0], {
    10: function(e, n, t) {
        "use strict";
        t.r(n);
        var a = t(0)
          , r = t.n(a)
          , c = t(2)
          , l = t.n(c);
        t(9);
        var o = document.getElementById("root");
        l.a.render(r.a.createElement(function() {
            return r.a.createElement("div", {
                className: "App"
        // В конце строки мы видим вызов функции React создать элемент с нашим значением
            }, r.a.createElement("h1", null, "Re-export example"), r.a.createElement("p", null, 50))
        }, null), o)
    },
    3: function(e, n, t) {
        e.exports = t(10)
    },
    9: function(e, n, t) {}
}, [[3, 1, 2]]]);
//# sourceMappingURL=main.5db15096.chunk.js.map

Здесь мы даже не видим функции внутри кода, потому что Webpack скомпилировал наше значение ещё перед деплоем.


Финальный тест


Итак, давайте проведем наш последний тест и используем все функции сразу, чтобы проверить, все ли работает.


// Финальный App.js

import React from "react";
import ReactDOM from "react-dom";
import { multiply, sum, calculateSomethingSpecial } from "./utils";
import "./styles.css";
function App() {
  const specialData = [
    {
      param1: 100,
      param2: 99
    },
    {
      param1: 2,
      param2: 31
    }
  ];
  const special = calculateSomethingSpecial(specialData);
return (
    <div className="App">
      <h1>Re-export example</h1>
      <p>Special: </p>
        <div>
         {special.map((specialItem, index) => (
           <div key={index}>
             Result #{index} {specialItem}
           </div>
         ))}
        </div>
      <p>{multiply(5, 10)}</p>
      <p>{sum(5, 10)}</p>
    </div>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Приложение:



Продакшн версия приложения:



// Код чанка main.js из прода

(window.webpackJsonp = window.webpackJsonp || []).push([[0], {
    10: function(e, n, a) {
        "use strict";
        a.r(n);
        var t = a(0)
          , r = a.n(t)
          , l = a(2)
          , p = a.n(l);
        // Вот наша функция **sum**
        function c(e, n) {
            return e + n
        }
        a(9);
        var u = document.getElementById("root");
        p.a.render(r.a.createElement(function() {
            var e = [{
                param1: 100,
                param2: 99
            }, {
                param1: 2,
                param2: 31 
            // А вот наша комбинированная функция **calculateSomethingSpecial**
            }].map(function(e) {
                // здесь мы вызываем **sum** внутри сабфункции
                return c(e.param1, e.param2)
            });
            return r.a.createElement("div", {
                className: "App"
            }, r.a.createElement("h1", null, "Re-export example"), r.a.createElement("p", null, "Special: "), r.a.createElement("div", null, e.map(function(e, n) {
                return r.a.createElement("div", {
                    key: n
                }, "Result #", n, " ", e) 
             // Вот наш результат из **multiply**
            })), r.a.createElement("p", null, 50), 
                // а здесь мы вызываем **sum** как есть
 r.a.createElement("p", null, c(5, 10)))
        }, null), u)
    },
    3: function(e, n, a) {
        e.exports = a(10)
    },
    9: function(e, n, a) {}
}, [[3, 1, 2]]]);
vie

Отлично! Все работает как положено. Вы можете попробовать любой этап, просто воспользовавшись ссылкой на codesandbox, и всегда можно прямо оттуда развернуть на netlify.


Заключение


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


Спасибо за прочтение! Чистого кода и притяного рефакторинга!




К сожалению, не доступен сервер mySQL