Реалізація Import Maps для управління модулями без бандлера

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Import Maps для управління модулями без бандлера
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Import Maps для управління модулями без бандлера

Import Maps — браузерний стандарт, що дозволяє управляти розв'язанням ES-модулів без Webpack, Rollup або Vite. Замість бандлингу в один файл браузер сам розв'язує import 'react' у конкретний URL за картою. Специфікація стабільна з Chrome 89, Firefox 108, Safari 16.4.

Базовий принцип

<!-- Без Import Maps — імпорт завершиться помилкою -->
<script type="module">
  import React from 'react'; // ❌ bare specifier, браузер не знає URL
</script>

<!-- З Import Maps — працює -->
<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/[email protected]",
    "react-dom/client": "https://esm.sh/[email protected]/client",
    "react/": "https://esm.sh/[email protected]/"
  }
}
</script>

<script type="module">
  import React from 'react';        // ✅
  import { createRoot } from 'react-dom/client'; // ✅
</script>

<script type="importmap"> має бути оголошений перед будь-яким <script type="module">. Один importmap на сторінку.

Реальний сценарій: мультистраничний сайт без бандлера

Типова ситуація: маркетинговий сайт з кількома інтерактивними віджетами. Немає потреби в складному pipeline — тільки загальний vendor-кеш для React та кілька компонентів.

<!-- layouts/base.html -->
<script type="importmap">
{
  "imports": {
    "react":             "https://esm.sh/[email protected]?dev",
    "react-dom":         "https://esm.sh/[email protected]?dev",
    "react-dom/client":  "https://esm.sh/[email protected]/client?dev",
    "@/":                "/static/js/",
    "htm/react":         "https://esm.sh/[email protected]/react"
  },
  "scopes": {
    "/admin/": {
      "react": "https://esm.sh/[email protected]"
    }
  }
}
</script>

scopes дозволяє встановити різні версії бібліотек для різних шляхів — актуально при поступовій міграції.

Модульна структура без бандлера

/static/js/
├── components/
│   ├── Counter.js
│   ├── Tabs.js
│   └── SearchWidget.js
├── utils/
│   ├── api.js
│   └── format.js
└── app.js
// /static/js/components/Counter.js
import React, { useState } from 'react';

export function Counter({ initial = 0 }) {
  const [count, setCount] = useState(initial);
  return React.createElement('div', null,
    React.createElement('button', { onClick: () => setCount(c => c - 1) }, '−'),
    React.createElement('span', { style: { padding: '0 12px' } }, count),
    React.createElement('button', { onClick: () => setCount(c => c + 1) }, '+'),
  );
}

Без JSX — використовуємо React.createElement або htm (tagged template literals як альтернатива JSX без компілятора):

// /static/js/components/SearchWidget.js
import { html } from 'htm/react';
import React, { useState, useCallback } from 'react';
import { debounce } from '@/utils/debounce.js';

export function SearchWidget({ endpoint }) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const search = useCallback(
    debounce(async (q) => {
      if (q.length < 2) return;
      const res = await fetch(`${endpoint}?q=${encodeURIComponent(q)}`);
      setResults(await res.json());
    }, 300),
    [endpoint]
  );

  return html`
    <div class="search">
      <input
        type="search"
        value=${query}
        onInput=${e => { setQuery(e.target.value); search(e.target.value); }}
        placeholder="Пошук..."
      />
      <ul>
        ${results.map(r => html`<li key=${r.id}>${r.title}</li>`)}
      </ul>
    </div>
  `;
}

Підключення на сторінках

<!-- /pages/catalog.html -->
<div id="search-root" data-endpoint="/api/search"></div>

<script type="module">
  import { createRoot } from 'react-dom/client';
  import { SearchWidget } from '@/components/SearchWidget.js';

  const root = document.getElementById('search-root');
  const endpoint = root.dataset.endpoint;

  createRoot(root).render(
    React.createElement(SearchWidget, { endpoint })
  );
</script>

Управління версіями та продуктивність

CDN esm.sh та jsDelivr кешують модулі aggressively. Для production потребується явна pinning-стратегія:

<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/stable/[email protected]/es2022/react.mjs",
    "react-dom/client": "https://esm.sh/stable/[email protected]/es2022/client.mjs"
  }
}
</script>

Шлях /stable/ на esm.sh гарантує, що URL не змінюється — браузер буде використовувати кеш. Без pinningu esm.sh/react@18 може оновитися та інвалідувати кеш.

Для self-hosted: вендорні модулі кладемо в /static/vendor/ та версіонуємо за хешем вмісту файлу:

<script type="importmap">
{
  "imports": {
    "react": "/static/vendor/react.18.3.1.a4f2c1.mjs",
    "react-dom/client": "/static/vendor/react-dom-client.18.3.1.b7e3a2.mjs"
  }
}
</script>

Генерація Import Map із lock-файлу

Для автоматизації — невеликий Node.js скрипт, який генерує importmap з package.json:

// scripts/generate-importmap.js
import { readFileSync, writeFileSync } from 'fs';

const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
const CDN_BASE = 'https://esm.sh';

const imports = {};
for (const [name, version] of Object.entries(pkg.importmap ?? {})) {
  const v = version.replace(/[\^~]/, '');
  imports[name] = `${CDN_BASE}/${name}@${v}`;
  imports[`${name}/`] = `${CDN_BASE}/${name}@${v}/`;
}

const map = JSON.stringify({ imports }, null, 2);

// Вставляємо в base-шаблон
let template = readFileSync('./templates/base.html', 'utf8');
template = template.replace(
  /<script type="importmap">[\s\S]*?<\/script>/,
  `<script type="importmap">\n${map}\n</script>`
);
writeFileSync('./templates/base.html', template);
console.log('Import map оновлён');

Обмеження та коли не варто використовувати

Import Maps не підходять, якщо потрібні: tree-shaking (кожен модуль завантажується цілком), TypeScript без трансп іляції (браузер не обробляє розширення .ts), підтримка Safari до 16.4 та Firefox до 108.

Для простих сайтів без складного стеку — чудова альтернатива Webpack з мінімальним tooling. Для додатків з тисячами компонентів, типізацією та hot reload — бандлер все рівно переважніше.

Терміни

Настройка Import Maps для існуючого проекту — один день. Розробка генератора importmap з lock-файлу та інтеграція з деплоєм — ще день-два.