Динамический import() в JavaScript представляет собой
выражение, возвращающее Promise, который резолвится в
модульное пространство (module namespace object). В отличие от
статического import, эта форма выполняется во время
исполнения и позволяет:
Базовая форма:
const module = await import(&
Возвращаемый объект содержит все экспортированные сущности:
module.add(2, 3);
module.default;
Поведение динамического import() на уровне AST
SWC оперирует абстрактным синтаксическим деревом (AST) и рассматривает
import() как CallExpression с
Import в качестве callee-типа:
import('./module.js')
В AST:
-
type: CallExpression
-
callee: Import
-
arguments: [StringLiteral]
Ключевой момент: в отличие от require, это не синхронный
вызов, а отдельная семантическая конструкция языка, требующая сохранения
асинхронного контекста.
Режимы обработки модулей в SWC
Поведение динамического import() в SWC напрямую зависит от
конфигурации модуля:
-
module.type = “es6” — сохранение import() без
изменений
-
module.type = “commonjs” — трансформация в совместимую
форму
-
module.type = “umd” — аналогично CommonJS с дополнительной
обёрткой
-
module.type = “nodenext” — гибридное поведение, близкое к
Node.js ESM
Конфигурация SWC:
{
"jsc": {
"parser": {
"syntax": "ecmascript"
},
"transform": {},
"target": "es2020"
},
"module": {
"type": "commonjs"
}
}
Трансформация import() в CommonJS
Наиболее важная трансформация происходит при генерации CommonJS-кода.
Исходный код:
async function load() {
const mod = await import('./utils.js');
return mod.sum(2, 2);
}
SWC преобразует динамический import() в Promise-обёртку над
require:
function load() {
return Promise.resolve().then(function () {
return require('./utils.js');
}).then(function (mod) {
return mod.sum(2, 2);
});
}
Смысл трансформации
CommonJS не поддерживает асинхронную загрузку модулей как часть языка.
Поэтому SWC эмулирует поведение:
-
require() выполняется синхронно;
-
Promise.resolve().then() создаёт асинхронный контекст;
-
результат приводится к интерфейсу ESM namespace object.
Обработка default и named exports
При трансформации важно сохранить совместимость структуры экспортов.
Исходный ESM:
// utils.js
export default function sum(a, b) {
return a + b;
}
export const version = 1;
Использование:
const mod = await import('./utils.js');
После трансформации в CommonJS:
function load() {
return Promise.resolve().then(function () {
return require('./utils.js');
}).then(function (mod) {
return mod;
});
}
Доступ:
mod.default;
mod.version;
SWC не изменяет структуру экспортов, но обеспечивает корректную упаковку
результата require.
Ленивые зависимости и chunk-поведение
SWC сам по себе не является бандлером, поэтому:
-
он не создаёт чанки;
-
он не выполняет code splitting;
-
он трансформирует синтаксис на уровне файла.
Однако при использовании SWC внутри сборщиков (Rspack, Webpack loaders,
Next.js pipeline) динамический import() становится точкой
разбиения на чанки.
Пример поведения в связке со сборщиком:
button.oncl ick = () => import('./chart.js');
На уровне SWC:
button.oncl ick = function () {
return Promise.resolve().then(function () {
return require('./chart.js');
});
};
На уровне бандлера:
-
создаётся отдельный chunk
chart.js;
-
require заменяется на runtime chunk loader;
-
добавляется mapping модулей.
Влияние target на трансформацию
SWC учитывает целевой уровень ECMAScript.
target = ES2020 и выше
{
"jsc": {
"target": "es2020"
}
}
Результат:
-
import() остаётся без изменений;
-
трансформация не применяется.
target ниже ES2020 (при CJS)
Если включён CommonJS:
-
происходит понижение до Promise + require.
Особенности Node.js интероперабельности
В Node.js существуют две модели модулей:
-
ESM (
import/export)
-
CommonJS (
require/module.exports)
SWC при трансформации динамического import() в CommonJS
должен учитывать:
-
синхронность
require;
-
кеширование модулей Node;
-
различие между
module.exports и export
default.
Пример потенциального конфликта:
const mod = await import('./cjs-module.js');
Если модуль CommonJS:
module.exports = function () {};
Результат SWC:
mod.default; // обёртка может отсутствовать
В зависимости от окружения может потребоваться interop-обёртка, но SWC
базово не эмулирует полноценный ESM-CJS interop runtime, оставляя это
сборщику.
Ленивые цепочки и последовательное импортирование
Динамический import() часто используется в цепочках:
async function bootstrap() {
const router = await import('./router.js');
const store = await import('./store.js');
const app = await import('./app.js');
app.init(store, router);
}
После SWC-трансформации (CJS):
function bootstrap() {
return Promise.resolve()
.then(function () {
return require('./router.js');
})
.then(function (router) {
return require('./store.js').then(function (store) {
return { router: router, store: store };
});
})
.then(function (data) {
return require('./app.js').then(function (app) {
return app.init(data.store, data.router);
});
});
}
Структура Promise сохраняется, но последовательность становится
вложенной при транспиляции.
Ошибки загрузки и обработка исключений
Динамический import() в оригинале:
try {
const mod = await import('./missing.js');
} catch (e) {
console.error(e);
}
SWC-трансформация сохраняет поведение через rejection Promise:
function load() {
return Promise.resolve()
.then(function () {
return require('./missing.js');
})
.catch(function (e) {
console.error(e);
});
}
Любая ошибка require превращается в rejection цепочки.
Ограничения трансформации SWC
При обработке import() существуют системные ограничения:
-
отсутствует полноценная симуляция ESM runtime;
-
нет автоматического code splitting;
-
не моделируется асинхронная загрузка файлов;
-
interop между ESM и CJS частично зависит от окружения;
-
поведение может отличаться от webpack/Vite/Rollup.
SWC выполняет именно синтаксическую трансформацию, а не семантический
bundling.
Встраивание в пайплайн трансформации
В типичной архитектуре SWC используется как этап компиляции:
TypeScript/ESNext
↓
SWC
↓
JavaScript (ESM/CJS)
↓
Bundler / Runtime (Rspack, Webpack, Node)
На этапе SWC:
-
анализируется
ImportExpression;
-
определяется target module system;
-
генерируется Promise-обёртка или сохраняется ESM.
Оптимизационные аспекты
Динамический import() часто рассматривается как сигнал для
ленивой загрузки. Однако SWC не оптимизирует:
-
порядок загрузки;
-
объединение чанков;
-
дедупликацию модулей;
-
предзагрузку зависимостей.
Оптимизация возможна только на уровне bundler/runtime.
Совместимость с TypeScript
TypeScript поддерживает:
const mod = await import('./utils');
SWC при трансформации TypeScript:
-
удаляет типы;
-
сохраняет
import() как синтаксическую конструкцию;
-
применяет module transform по конфигурации.
Типовая аннотация:
type Utils = typeof import('./utils');
SWC не влияет на типовую систему, но сохраняет корректность
runtime-структуры.
Итоговая модель поведения
Поведение динамического import() в SWC можно свести к трём
сценариям:
-
ESM output — конструкция сохраняется без изменений
-
CommonJS output —
import() превращается в
Promise-обёртку над require()
-
Bundler pipeline — SWC оставляет сигнальную
конструкцию, а разбиение выполняется выше уровнем
В результате import() сохраняет асинхронную природу, но
конкретная реализация загрузки зависит от выбранной модульной системы и
окружения выполнения.