Реализация 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 нужна явная пиннинг-стратегия:

<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 не изменится — браузер будет использовать кэш. Без пиннинга 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-файла и интеграция с деплоем — ещё день-два.