Знайомимось з tree shaking

5 хв. читання

Коли JS-застосунок досягає певних розмірів, доцільно розділити його код на модулі. Мінус такого підходу у тому, що можна імпортувати код, який насправді не використовується. На допомогу приходить Tree shaking — метод оптимізації пакетів вашого коду. Його суть: вилучити невикористовуваний код з кінцевого файлу.

Уявіть, що у нас є файл з деякими корисними математичними операціями, які ми, можливо, захочемо використовувати в основному скрипті.

export function add(a, b) {
    console.log("add");
    return a + b;
}

export function minus(a, b) {
    console.log("minus");
    return a - b;
}

export function multiply(a, b) {
    console.log("multiply");
    return a * b;
}

export function divide(a, b) {
    console.log("divide");
    return a / b;
}

Припустимо, що в основному скрипті ми імпортуємо та застосовуємо з перелічених лише функцію add().

import { add } from "./mathUtils";

add(1, 2);

Якщо для збірки ми використовуємо такий інструмент, як Webpack, то усі функції з файлу mathUtils.js опиняться у кінцевому пакеті (навіть якщо ми імпортували тільки add()).

Заглянемо всередину bundle.js, щоб переконатися у цьому:

/***/ "./src/mathUtils.js":
/*!**************************!*\\
  !*** ./src/mathUtils.js ***!
  \\**************************/
/*! exports provided: add, minus, multiply, divide */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\"add\\", function() { return add; });\
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\"minus\\", function() { return minus; });\
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\"multiply\\", function() { return multiply; });\
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \\"divide\\", function() { return divide; });\
function add(a, b) {\
    console.log(\\"add\\");\
    return a + b;\
}\
\
function minus(a, b) {\
    console.log(\\"minus\\");\
    return a - b;\
}\
\
function multiply(a, b) {\
    console.log(\\"multiply\\");\
    return a * b;\
}\
\
function divide(a, b) {\
    console.log(\\"divide\\");\
    return a / b;\
}\
\
\
//# sourceURL=webpack:///./src/mathUtils.js?");

/***/ })

Із Tree shaking у кінцевий пакет потрапить лише той код, що дійсно використовувався або був імпортований.

Як працює Tree shaking?

Хоча концепція Tree shaking існує з 90-х років, у Javascript вона проявила себе лише з появою модулів ES6. Усе тому, що tree shaking може працювати лише якщо модулі «статичні».

До появи ES6 були CommonJS модулі, які використовували синтаксис require(). Вони були «динамічними», тобто ми могли імпортувати нові модулі в умовних виразах.

var myDynamicModule;

if (condition) {
    myDynamicModule = require("foo");
} else {
    myDynamicModule = require("bar");
}

Саме «динамічна» природа CommonJS модулів перешкоджала застосуванню tree shaking, тому що неможливо було визначити потрібний модуль до запуску коду.

В ES6 було представлено новий, повністю статичний синтаксис модулів. З import ми більше не можемо імпортувати модуль динамічно.

Тобто такий код не працюватиме:

if (condition) {
    import foo from "foo";
} else {
    import bar from "bar";
}

Треба визначити усі імпорти глобально, поза умовними виразами.

import foo from "foo";
import bar from "bar";

if (condition) {
    // якась робота з foo
} else {
    // якась робота з bar
}

Серед переваг нового синтаксису — можливість використовувати Tree shaking, адже будь-який «імпорт» можна визначити без попереднього запуску коду.

Що «струшує» tree shaking?

Реалізація tree shaking у Webpack дозволяє максимально позбутися невикористаного коду. Наприклад, код, який ми імпортували з import, але не використовували, вилучається.

import { add, multiply } from "./mathUtils";

add(1, 2);

У прикладі вище функція multiply() ніколи не використовується, тому її буде видалено з кінцевого пакета.

Навіть окремі властивості імпортованих об'єктів видаляються, якщо до них не зверталися.

Файл myInfo.js:

export const myInfo = {
    name: "Ire Aderinokun",
    birthday: "2 March"
}

Файл index.js:

import { myInfo } from "./myInfo.js";

console.log(myInfo.name);

У наведеному прикладі властивість birthday не потрапить у кінцевий пакет, тому що ніколи не використовується.

Однак з tree shaking ви не позбудетесь усього непотрібного коду. У статті ми не торкатимемось цих подробиць. Варто лише пам'ятати, що використання tree shaking не розв'язує проблему зайвого коду повністю.

Як щодо «побічних ефектів»?

Побічним ефектом (side effect) називають код, який виконує деякі дії при імпорті, не обов'язково пов'язані з експортом. Хорошим прикладом побічного ефекту є поліфіл. Як правило, поліфіли не передбачають експорт в основному скрипті, а, швидше, стосуються проєкту загалом.

Tree shaking не може визначити, які скрипти є побічними ефектами автоматично, тому важливо визначити їх вручну. Продемонструємо це нижче.

Як використовувати tree shaking?

Як правило, tree shaking реалізується через бандлер коду. Наприклад, якщо ви використовуєте Webpack, ви легко можете вказати mode як production у файлі webpack.config.js. Разом з іншими оптимізаціями ви активуєте tree shaking.

module.exports = {
    ...,
    mode: "production",
    ...,
};

Щоб позначити конкретні файли як побічні ефекти, необхідно додати їх до файлу package.json.

{
    ...,
    "sideEffects": [
        "./src/polyfill.js"
    ],
    ...,
}

Більше інформації про реалізацію tree shaking у webpack можна знайти у документації Webpack.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація