Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «Модулі й збирання»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

3 питання

ES-модуль - файл JavaScript з власною областю видимості, який явно експортує те, що дає назовні, й імпортує те, що йому потрібно.

// money.js
export function formatPrice(amount) {
  return `${amount.toFixed(2)} грн`;
}

// app.js
import { formatPrice } from './money.js';
console.log(formatPrice(100));
<script type="module" src="/app.js"></script>

Як було до модулів: усі <script> на сторінці ділили одну глобальну область видимості. Функція formatPrice з одного файлу ставала глобальною і могла перезаписати однойменну з іншого. Порядок підключення скриптів доводилося вибудовувати вручну, а залежності між файлами ніде не були записані.

Чим модулі відрізняються:

  • власна область видимості: змінні верхнього рівня модуля не потрапляють у window;
  • явні залежності: з import видно, звідки що береться, - це читає і людина, і збирач;
  • строгий режим завжди ('use strict' не потрібен);
  • виконуються один раз: скільки б модулів не імпортували money.js, він завантажиться й виконається лише раз, а всі отримають той самий екземпляр;
  • відкладене виконання: <script type="module"> поводиться як defer - виконується після розбору HTML і не блокує його;
  • top-level await дозволений;
  • this на верхньому рівні - undefined, а не window;
  • завантаження з іншого домену підпорядковується CORS.

Імпорти статичні: import - лише на верхньому рівні модуля, шлях - рядок-константа. Завдяки цьому залежності відомі ще до виконання коду - звідси можливість tree shaking. Для завантаження «за потреби» є динамічний import().

Модулі в Laravel-проєкті: resources/js/app.js - точка входу ES-модуля. Vite збирає граф імпортів, а директива @vite в Blade підключає результат як <script type="module">. Пакети з npm імпортуються за назвою (import axios from 'axios') - браузер такого шляху не розуміє, тому розв'язує його збирач (або import map без збирача).

Докладніше в документації: Модулі JavaScript

Іменований експорт - модуль може мати їх скільки завгодно, імпортують їх за точною назвою у фігурних дужках:

// validation.js
export function isEmail(value) { /* ... */ }
export const MAX_LENGTH = 255;

// інший файл
import { isEmail, MAX_LENGTH } from './validation.js';
import { isEmail as checkEmail } from './validation.js';   // перейменування
import * as validation from './validation.js';              // усе як об'єкт-простір імен

Експорт за замовчуванням - один на модуль, імпортується без дужок і під будь-якою назвою:

// Modal.vue, api.js
export default function createApi(baseUrl) { /* ... */ }

import createApi from './api.js';
import makeClient from './api.js';   // теж працює - та сама функція

Модуль може мати й те, й інше: import axios, { AxiosError } from 'axios'.

Аргументи на користь іменованих експортів:

  • однакові назви всюди: не буває ситуації, коли в одному файлі функцію імпортували як createApi, в іншому як apiFactory, - пошук і рефакторинг простіші;
  • помилка в назві помітна одразу: import { isEmial } - збирач чи редактор скаже, що такого експорту немає. З експортом за замовчуванням будь-яка назва «правильна»;
  • автоімпорт у редакторі працює точніше;
  • tree shaking простіше розібратися з окремими експортами, ніж з одним великим об'єктом за замовчуванням.

Через це багато команд мають правило лінтера «лише іменовані експорти» (import/no-default-export).

Коли експорт за замовчуванням природний:

  • файл = одна сутність: компонент Vue (.vue-файли експортують компонент за замовчуванням), сторінка в Next.js, конфіг (vite.config.js - export default defineConfig(...));
  • вимога інструмента, що шукає саме default.

Реекспорт - для «бочок» (index.js, що збирає експорти папки):

export { isEmail, MAX_LENGTH } from './validation.js';
export { default as Modal } from './Modal.vue';
export * from './formatters.js';

Великі «бочки» мають ціну: імпорт однієї функції з index.js змушує збирач обробити всі реекспортовані модулі, а в режимі розробки - ще й завантажити їх.

Докладніше в документації: export

Браузер уміє виконувати ES-модулі, тож чому не віддати йому файли як є? Тому що код, який зручно писати, і код, який швидко завантажується, - різні.

Що робить збирач під час розробки (Vite npm run dev):

  • розв'язує імпорти пакетів: import axios from 'axios' браузер не розуміє - Vite перетворює на шлях до файлу в node_modules;
  • перетворює те, що браузер не вміє: TypeScript, .vue-файли, JSX, Tailwind і PostCSS;
  • гаряча заміна модулів (HMR): змінили компонент - оновився лише він, без перезавантаження сторінки й зі збереженням стану;
  • віддає модулі браузеру майже без обробки, тож сервер стартує за мілісекунди незалежно від розміру проєкту - у цьому головна перевага Vite над старими збирачами.

Що робить збирач для продакшену (npm run build):

  • об'єднує модулі в невелику кількість файлів - сотні дрібних запитів повільніші за кілька великих;
  • мініфікує: прибирає пробіли й коментарі, скорочує імена змінних;
  • tree shaking: викидає код, який ніде не імпортується;
  • розділення коду: спільні залежності - в окремий файл, сторінки й важкі компоненти - в окремі частини, що завантажуються за потреби;
  • хешовані імена файлів (app-3f9a1c.js): вміст змінився - змінилася назва. Тоді файли можна кешувати в браузері «назавжди», а після деплою користувачі гарантовано отримають нову версію;
  • маніфест - відповідність вихідних файлів і зібраних. Саме з нього директива @vite('resources/js/app.js') у Laravel дізнається, який файл підключити.

У Laravel-проєкті:

  • npm run dev - сервер розробки, @vite підключає файли з нього (адресу записано у public/hot);
  • npm run build - збірка в public/build, @vite бере шляхи з public/build/manifest.json;
  • помилка «Unable to locate file in Vite manifest» означає, що збірку не виконано або файл не вказано в input конфігурації laravel-vite-plugin.

Альтернативи: webpack (старіший, гнучкіший, повільніший), esbuild і Rollup (на яких побудовано Vite), Rolldown (новий швидкий збирач для майбутніх версій Vite). Можна й зовсім без збирача - import maps і нативні модулі, - для невеликих проєктів без TypeScript чи .vue-файлів.

Докладніше в документації: Vite: навіщо