6 - Просто файлы или модули? import / export

Оценить качество материала и подачу материала автором видео:

Front-end

Трудоустройтесь middle front-end разработчиком на React JS (TypeScript) за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Front-end

Back-end

Трудоустройтесь middle back-end разработчиком за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Back-end

Карьерный бустер

Получите коммерческий опыт на реальных стартапах, прокачайте tech & soft навыки, научитесь работать в команде, проходить собеседования и получите первую работу в IT!

Перейти на курс
Карьерный бустер

Основы Front-end

Сделайте первый шаг в IT, освоив базовые знания разработки и научившись создавать небольшие проекты на JavaScript

Перейти на курс
Основы Front-end

Основы Back-end

Сделайте первый шаг в IT, освоив базовые знания разработки. Без опыта. Без математики. Только практика: JavaScript, SQL, Node JS, база данных

Перейти на курс
Основы Back-end

Модули, импорты, экспорты

Автор конспекта: Арина Василевская

🔗

Полезные ссылки:

Начало работы

Для начала создадим папку с названием, например lesson-05 и в ней файлы index.html, main.js, player.js, analytics.js

    • index.html
    • main.js
    • player.js
    • analytics.js
    • Содержимое index.html. Здесь в body, подключаем наш main.js
    index.html
    <!doctype html>
    <html lang="en">
      <body>
        <script src="main.js"></script>
      </body>
    </html>
    • В main.js запускаем функции:
    main.js
    runPlayer()
    run()
    • Эти функции объявлены в двух других файлах:
    player.js
    function runPlayer() {
      console.log("run player")
    }
    analytics.js
    function run() {
      console.log("run analytics")
    }
    • Запустим наш проект. Нажимаем правой кнопкой мыши по index.html и выбираем open in и браузер.
    • В консоли браузера мы видим ошибку:

    errorPlayer

    Сообщение об ошибке связано с тем, что main.js не знает о функции runPlayer. Причина в том, что два оставшихся скрипта пока не были подключены. Проверив вкладку Network в инструментах разработчика (F12), мы увидим, что загружаются лишь два файла:

    loadingMain

    Классическое подключение script

    Подключим недостающие файлы таким же образом, как и main.js

    index.html
    <body>
      <script src="main.js"></script>
      <script src="./analytics.js"></script>
      <script src="./player.js"></script>
    </body>

    ❗ Видим, что ошибка в консоли браузера по-прежнему сохранилась.

    Но в Network мы видим, что загрузились все файлы:

    scriptsNetwork

    Проблема возникает из-за порядка подключения скриптов: main.js запускается раньше, чем player.js, analytics.js, поэтому он не находит их функции.

    Решение: изменить порядок подключения. Сначала подключаются скрипты без зависимостей,а уже потом — те, которые от них зависят:

    index.html
    <body>
      <script src="./analytics.js"></script>
      <script src="./player.js"></script>
      <script src="main.js"></script>
    </body>

    🎉 Теперь в консоли браузера у нас все отображается верно:

    consolePlayerRun

    Теперь попробуем добавить новый файл в наш проект plugin.js:

    plugin.js
    function run() {
      console.log("run plugin")
    }
     
    run()
    console.log("plugin file initialized")

    Подключаем. Положим его над main.js:

    index.html
    <body>
      <script src="./analytics.js"></script>
      <script src="./player.js"></script>
      <script src="./plugin.js"></script>
      <script src="main.js"></script>
    </body>

    Хотя файл analytics.js также загружен, но в консоли браузера мы не видим run analytics:

    runPluginError

    Так как все функции из подключённых скриптов попадают в глобальную область видимости(window), то возможны конфликты имён.

    • В analytics.js есть функция run.

    • Потом мы подключили plugin.js, в котором также есть свой run.

    • При подключении plugin.js его версия перезаписывает предыдущую.

    • В итоге при вызове run() мы запускаем последний загруженный вариант → ошибка.

    Решение: определить plugin раньше, чем analytics

    index.html
    <body>
      <script src="./plugin.js"></script>
      <script src="./analytics.js"></script>
      <script src="./player.js"></script>
      <script src="main.js"></script>
    </body>

    Теперь все отображается верно!

    Основные проблемы данного метода подключения

    1. Порядок подключения

    • Если main.js подключить до файлов-зависимостей (analytics.js, player.js), функции из них будут неопределёнными (undefined).

    • Сначала должны загружаться зависимости, а уже потом основной скрипт.

    2. Глобальная область видимости

    • Все функции и переменные автоматически попадают в глобальный объект window.

    • Это вызывает конфликты имён и переопределение функций.

    3. Неочевидные зависимости

    • Трудно понять, откуда берётся та или иная функция.

    • В больших проектах это усложняет поддержку и делает поведение кода непредсказуемым.

    Переход к модулям

    Современный стандарт решает проблемы классического подключения script. В браузере нативно поддерживаются ECMAScript Modules.

    💡

    Модуль – это просто файл. Один скрипт – это один модуль.

    🔗 Больше информации:

    В index.html оставим только наш главный файл main.js, но укажем тип подключения module:

    index.html
    <body>
      <script type="module" src="main.js"></script>
    </body>

    Теперь в JS-файлах доступны import и export.

    Import и Export

    • import позволяет импортировать функциональность из других модулей.
    • export отмечает переменные и функции, которые должны быть доступны вне текущего модуля.
    🔗

    Подробнее про import и export можно почитать в статье Import-Export

    Экспорт по дефолту

    Чтобы импортировать функцию, ее нужно сначала экспортировать. Поэтому мы экспортируем нашу функцию runPlayer.

    player.js
    function runPlayer() {
      console.log("run player")
    }
     
    export default runPlayer
    📌

    Этот тип экспорта называется дефолтным (export default) или экспортом по умолчанию. В каждом файле может быть только один export default. При импорте такого элемента не нужны фигурные скобки ({}), а имя импортируемого объекта можно выбрать любое — оно не обязано совпадать с названием в исходном файле.

    В файле main.js импортируем runPlayer из player.js.

    main.js
    import runPlayer from "./player.js"
     
    runPlayer()
    run()

    Именованный экспорт

    Теперь попробуем другой способ экспорта на нашей функции run

    analytics.js
    export function run() {
      console.log("run analytics")
    }
    📌

    Этот тип экспорта называется именованным (export). В одном файле можно создавать несколько именованных экспортов. При импорте обязательно использовать фигурные скобки ({}).Имена экспортируемых элементов должны совпадать с названиями в исходном файле. Этот тип является более строгим и в большинстве случаев лучше использовать именно его.

    В файле main.js импортируем функцию run из analytics.js.

    main.js
    import runPlayer from "./player.js"
    import { run } from "./analytics.js"
     
    runPlayer()
    run()

    Импорт модуля без экспорта

    Иногда модуль нужен не для того, чтобы отдавать наружу функции или переменные, а только для выполнения побочных эффектов (side effects) — например, вывод в консоль, показ alert, инициализация плагина и т.п. В таком случае:

    • Модуль ничего не экспортирует.
    • В main.js его можно подключить простым импортом:
    main.js
    import "./plugin.js"
    //...
    • Такой импорт просто загружает и выполняет код из файла.
    • Всё, что модуль делает «сам по себе» (console.log, alert и др.) — произойдёт автоматически.

    Загрузка модулей

    Перейдем в Network и посмотрим, что все наши файлы загрузились:

    AllFilesNetwork

    Как это произошло?

    • Браузер загрузил main.js.
    • При выполнении видит, что в файле есть импорты.
    • Загружает все необходимые зависимости (импортируемые файлы).
    • Только после этого начинает выполнять код main.js.

    💡 Это решает проблему порядка подключения — зависимости подгружаются автоматически.

    ❗Важный момент

    Даже если в main.js есть код до строк с import, браузер сначала загрузит и выполнит все зависимости! Код внутри импортируемых модулей выполнится до любого кода, написанного в main.js после импортов.

    Пример. В player.js напишем:

    player.js
    function runPlayer() {
      console.log("run player")
    }
    console.log("player.js loaded")

    В main.js:

    main.js
    console.log("main.js loaded")
    import runPlayer from "./player.js"
    import { run } from "./analytics.js"
     
    runPlayer()
    run()

    В консоли браузера мы все равно первым увидим player.js loaded

    playerLoadFirst

    Конфликты имён и псевдонимы

    Для того чтобы продемонстрировать эту проблему, давай в player.js создадим функцию run и экспортируем ее.

    player.js
    export function runPlayer() {
      console.log("run player")
    }
    export function run() {
      console.log("run playlist")
    }
    console.log("player.js loaded")

    В main.js импортируем run из player.js

    main.js
    import { runPlayer, run } from "./player.js"
    import { run } from "./analytics.js"
    import "./plugin.js"
     
    runPlayer()
    run()
     
    console.log("main.js loaded")
    • Уже на этапе написания кода IDE подсвечивает ошибку: имена конфликтуют.
    • В браузере при запуске получаем аналогичную ошибку.

    runDeclared

    Решение — псевдонимы (as) Чтобы избежать конфликта, можно задать одному из импортов другое имя:

    main.js
    import { runPlayer, run } from "./player.js"
    import { run as analyticsRun } from "./analytics.js"
    import "./plugin.js"
     
    runPlayer()
    analyticsRun()
    run()
     
    console.log("main.js loaded")

    ✅ Теперь:

    • runPlayer() запускает плеер,
    • analyticsRun() запускает аналитику,
    • run() из player.js работает без конфликтов,
    • плюс выполняются побочные эффекты из plugin.js.

    Организация модулей по папкам

    Мы хотим разложить файлы по папкам:

    • player.js → в папку /player,
    • analytics.js остаётся рядом с main.js.

    Структура папок теперь выглядит таким образом:

      • player.js
    • index.html
    • main.js
    • analytics.js
  • После переноса IDE начнёт ругаться: файл player.js больше не лежит в той же папке, что и main.js. Поэтому импорт нужно поправить:

    main.js
    import { runPlayer, run } from "./player/player.js"
     
    //...

    Здесь мы явно говорим: Перейди в папку player и возьми там файл player.js

    Импорты внутри модулей

    Представим, что плееру нужна аналитика:

    • Логично, чтобы main.js не импортировал всё подряд.
    • Пусть сам player.js импортирует analytics.js и вызывает её функции.

    Пример:

    /player/player.js
    import { run as analyticsRun } from "../analytics.js"
     
    export function runPlayer() {
      console.log("run player")
      analyticsRun()
    }
     
    export function run() {
      console.log("run playlist")
    }

    Относительные пути импорта:

    • ./ — означает текущая папка.
    • ../ — означает на уровень выше.

    Поэтому в player.js, чтобы достучаться до analytics.js, который лежит на уровень выше, мы пишем:

    /player/player.js
    import { run as analyticsRun } from "../analytics.js"

    А запись ./../analytics.js будет избыточной: точку (./) можно убрать, так как мы и так поднимаемся на уровень вверх.

    Наш React и Vite проект

    Теперь ты можешь понять больше информации из этих записей:

    main.tsx
    import { createRoot } from "react-dom/client" // именованный экспорт
    import "./index.css" // side-effect: CSS подключается сборщиком
    import App from "./App.tsx" // экспорт по дефолту
     
    const rootEl = document.getElementById("root")
    const reactRoot = createRoot(rootEl!) // 💡
    reactRoot.render(<App />)

    Обрати внимание, что при импорте createRoot нет точек, это значит, что модуль подтягивается из папки node_modules. Здесь мы не указываем путь к файлу — createRoot берётся из установленного пакета react-dom. Так работают все зависимости, которые мы ставим через npm install.

    Зарефакторим наш App.tsx, чтобы экспорт был именованным(уберем export default):

    App.tsx
    export function App() {
      const tracks = [
        {
          id: 1,
          title: "Musicfun soundtrack",
          url: "https://musicfun.it-incubator.app/api/samurai-way-soundtrack.mp3",
        },
    //...
     
    //...
    }

    Теперь в main.tsx:

    main.tsx
    import { createRoot } from "react-dom/client" // именованный экспорт
    import "./index.css" // side-effect: CSS подключается сборщиком
    import { App } from "./App.tsx" // именованный экспорт
     
    //...

    Импорт всего модуля

    Иногда удобно не перечислять каждый экспорт по отдельности, а забрать весь модуль целиком. В App.tsx добавим еще один экспорт:

    App.tsx
    export function App() {
      //...
    }
     
    export const name = "AppFileComponent"

    Импорт всего модуля в main.tsx:

    main.tsx
    import { createRoot } from "react-dom/client"
    import "./index.css"
    import * as appModule from "./App.tsx" //
     
    const rootEl = document.getElementById("root")
    const reactRoot = createRoot(rootEl!) // 💡
    reactRoot.render(<appModule.App />)
     
    console.log(appModule.name)

    Здесь мы импортировали всё содержимое App.tsx в объект appModule с помощью * as. Все экспорты становятся свойствами этого объекта: appModule.App, appModule.name

    Вывод и сравнительная таблица

    • Для учебных и маленьких проектов можно использовать <script>, но нужно следить за порядком и глобальными переменными.
    • Для современных приложений лучше использовать ES-модули: они решают проблемы зависимостей, конфликтов имён и делают проект более структурированным и предсказуемым.
    🔹 Критерий⚠️ Классическое подключение <script>✅ Современные модули (type="module")
    Порядок выполненияВажно: скрипты работают сверху вниз,
    зависимости могут быть не найдены
    Порядок подгрузки и выполнения определяет сам браузер — зависимости гарантированно подключаются первыми
    Глобальная областьВсе функции/переменные попадают в window → возможны конфликтыКаждое имя «закрыто» в модуле, доступ только через import
    Подключение зависимостейНужно вручную следить за очередностью <script>Автоматически подгружаются все указанные в import файлы
    Читаемость и поддержкаНеочевидно, откуда берётся функция → код труднее поддерживатьЗависимости явно прописаны в начале файла через import
    Экспорт функционалаНет экспорта — всё глобальноеПоддерживаются export default, именованный export, import * as, импорт без экспорта
    МасштабируемостьПлохо подходит для больших проектовУдобно для сложных приложений, легко реорганизовывать файлы
    СовместимостьРаботает везде, даже в старых браузерахПоддерживается в современных браузерах; для старых используется сборщик (например, Vite/Webpack)

    🏠 Домашнее задание

    Задание 1

    Создание простой структуры проекта ⭐

    Создай проект "Музыкальный плеер" со следующей структурой:

    text
    music-player/
    ├── index.html
    ├── main.js
    ├── player.js
    ├── songs.js
    └── display.js

    Что нужно сделать:

    1. Создай папку music-player
    2. Создай все файлы из структуры выше
    3. В index.html подключи только main.js с типом module
    index.html
    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
        />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
      </head>
      <body></body>
    </html>

    Задание 2

    Создание простых функций 🎵

    1. Файл songs.js - список песен

    Создай массив песен и экспортируй его по дефолту:

    songs.js
    const songs = ["Песня 1 - Исполнитель А", "Песня 2 - Исполнитель Б", "Песня 3 - Исполнитель В"]

    2. Файл player.js - функции плеера

    Добавь именованный экспорт в функции управления плеером

    player.js
    function play() {
      console.log("▶️ Воспроизведение началось")
    }
     
    function pause() {
      console.log("⏸️ Воспроизведение приостановлено")
    }
     
    function stop() {
      console.log("⏹️ Воспроизведение остановлено")
    }

    3. Файл display.js - функции отображения

    Добавь именованный экспорт в функции для показа информации

    display.js
    function showSong(songName) {
      console.log(`🎵 Сейчас играет: ${songName}`)
    }
     
    function showPlaylist(songs) {
      console.log("📝 Плейлист:")
      songs.forEach((song, index) => {
        console.log(index + ". " + song)
      })
    }

    4. Файл main.js - главный файл

    Импортируй все функции и протестируй их работу:

    main.js
    console.log("🎶 Добро пожаловать в музыкальный плеер!")
     
    // Показываем весь плейлист
    showPlaylist(songs)
     
    // Включаем первую песню
    showSong(songs[0])
    play()
     
    // Пауза
    pause()
     
    // Включаем вторую песню
    showSong(songs[1])
    play()
     
    // Останавливаем
    stop()

    Проверь себя ✅

    Открой проект в браузере и убедись, что:

    1. ✅ Страница загружается без ошибок
    2. ✅ В консоли выводится приветствие плеера
    3. ✅ Показывается список всех песен
    4. ✅ Показываются сообщения о воспроизведении/паузе/остановке
    5. ✅ Все импорты работают корректно

    Ожидаемый результат в консоли:

    lesson-6-result-2

    Задание 3

    Псевдонимы

    В файле player.js добавь функцию с таким же именем как и в display.js:

    player.js
    export function showSong(songName) {
      console.log(`🎤 Плеер показывает: ${songName}`)
    }

    Теперь в main.js импортируй обе функции showSong, используя псевдонимы и вызовите обе функции одна за другой

    main.js
    // Ваш код

    В итоге вы должны получить результат как в консоли

    lesson-6-result-3

    Задание 4

    Импорт всего модуля

    Импортируйте весь модуль player.js как player чтобы в файле main.js к нему можно было обращаться через точку

    main.js
    player.play()
    player.pause()
    player.stop()
    player.showSong(songs[0])
    player.showSong(songs[1])

    В итоге вы должны получить результат как и в предыдущем задании 🚀

    Задание 5

    Side-effect модуль 🎨

    Создай файл theme.js, который будет автоматически менять цвет фона страницы

    theme.js
    // Этот файл ничего не экспортирует, только выполняет код
    console.log("🎨 Тема загружена")
    document.body.style.backgroundColor = "#1a1a2e"
    document.body.style.color = "#ffffff"

    Импортируй его в main.js таким образом, чтобы цвет браузера сменился на темный и в консоли ты увидел 🎨 Тема загружена

    lesson-6-result-5

    Задание 6

    Организация по папкам 📁

    Реорганизуй проект, создав папки:

    text
    music-player/
    ├── index.html
    ├── main.js
    ├── modules/
    │   ├── player.js
    │   ├── songs.js
    │   └── display.js
    └── styles/
        └── theme.js

    Обнови пути импортов в main.js самостоятельно чтобы в результате получить такой же результат как и в предыдущем задании

    Дополнительное задание (по желанию) 🌟

    Попробуй сломать проект и понять, почему:

    1. Убери type="module" из HTML - что произойдет?
    2. Измени порядок импортов в main.js - повлияет ли это на работу?
    3. Удали export из одного файла - какая будет ошибка?
    4. Создай циклическую зависимость (файл А импортирует Б, а Б импортирует А) - что случится?

    Боевой маршрут (React Путь Самурая: без альтернатив)

    Видеоурок - 7 видео из 30