Вступление


Позвольте мне начать. У нас был монолитный фронтэнд с большим наследием. Сервисы жили в одних файлах с компонентами. Всё было вперемешку и с лозунгом на фасаде: “Пусть всё будет под рукой?–?так легче найти, что надо". И не важно, что длина файла 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.


Заключение


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


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

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


  1. JustDont
    18.06.2019 15:42

    А если у вас в вебпаке не одна entry point, а несколько, и зависимости у них разные — на выходе вы получите тыкву (все зависимости будут в каждом бандле). Приветики вам от могучего tree shaking.


    1. Ckomop0x Автор
      18.06.2019 16:31

      Да, есть такая слабость у Webpack, но даже такие собранные entry point будут легче, чем тяжелые бандлы, в которых склеиваются не только отдельные функции, а целые файлы-помойки с наборами методов, хелперов и всего такого.


      1. JustDont
        18.06.2019 16:34

        Это не слабость. Это баг 2017 года. Который по текущий момент open и ни на кого не назначен.

        При этом я бы не сказал, что это фатально (всегда можно билдить N проектов каждый с 1 entrypoint вместо 1 проекта с N entrypoint), но если про него не знать, и построить себе полный CI пайплайн под множественные entrypoint, а потом уже обнаружить, что по факту хвалёный tree shaking в этом случае не работает — вот в таком сценарии может быть очень много боли.


  1. vintage
    18.06.2019 17:54

    Ну и в продолжение нельзя не упомянуть эту статью: https://habr.com/ru/post/456288/#importyeksporty


  1. sultan99
    19.06.2019 15:38

    Можно дальше пойти, в купе использовать cdn вместо отдельных чанков по вендерам.