2 minutes with Webpack tree-shaking and re-export

Introduction


Let me start. We had a monolithic frontend with a great heritage. Services lived in the same files with the components. Everything was mixed up with the slogan on the facade: “Let everything be at hand - it’s easier to find what you need.” And it doesn’t matter that the file length is 200+, 300+, 500+ or ​​even more lines of code.


goal


Make everything more readable, smaller and faster.


Implementation


Separating everything that is possible into files and the golden bullet here is the principle of sole responsibility. If we have a component and pure functions inside a file, we will separate them.


With the advent of ES6 +, it became possible to use import ... from syntax - this is a great feature, because we can also use export ... from .


Refactoring


Imagine a file with this structure:


// Старая, старая функция, которая живет здесь с начала времён
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)
  );
}

We can split this code into files this way:


Structure:


utils
  multiply.js
  sum.js
  calculateSomethingSpecial.js

and files:


// 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));
}

Now we can import the functions individually. But with extra lines and these long names in imports, it still looks awful.


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

But for this we have a wonderful feature that appeared with the advent of the new JS syntax, which is called re-export (re-export). In the folder, we need to make an index.js file in order to combine all our functions. And now we can rewrite our code this way:


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

Slightly podshamanim App.js:


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

Done.


Testing.


Now let's check how our Webpack compiles build for production. Let's create a small React application to test how it works. We will check whether we download only what we need, or everything that is indicated in index.js from the utils folder .


// Переписанный App.js
import React from "react";
import ReactDOM from "react-dom";
import { sum } from "./utils";
import "./styles.css";
function App() {
  return (
    

Re-export example

{sum(5, 10)}

); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

Application:



Production version of the application:



// Код чанка 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

As you can see above, we only loaded the sum function from utils .
Let's check again, and this time we will use multiply .


Application:



Production version of the application:



// Код чанка 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

Here we do not even see the functions inside the code, because Webpack compiled our value even before the deployment.


Final test


So, let's run our final test and use all the features at once to see if everything works.


// Финальный 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 (
    

Re-export example

Special:

{special.map((specialItem, index) => (
Result #{index} {specialItem}
))}

{multiply(5, 10)}

{sum(5, 10)}

); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

Application:



Production version of the application:



// Код чанка 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

Excellent! Everything works as expected. You can try any step by simply using the link to codesandbox , and you can always deploy directly to netlify from there .


Conclusion


Use the separation of the code into smaller parts, try to get rid of too complex files, functions, components. You will help both the future yourself and your team. Smaller files are faster to read, easier to understand, easier to maintain, faster to compile, easier to cache, faster to download, etc.


Thanks for reading! Clean code and luscious refactoring!


Also popular now: