Параллельная обработка файлов
## Архитектурная модель параллельной обработки в SWC-экосистеме
SWC реализует высокопроизводительную компиляцию JavaScript и TypeScript за счёт использования Rust и многопоточности на уровне ядра. В отличие от традиционных JavaScript-трансформеров, где параллелизм часто достигается вручную через `worker_threads`, SWC переносит значительную часть вычислительной нагрузки в нативный слой, где планирование потоков выполняется эффективнее и ближе к системному уровню.
Параллельная обработка файлов в SWC строится на двух уровнях:
* внутренний параллелизм Rust runtime (thread pool)
* внешняя координация задач в Node.js (или любом другом рантайме)
* пакетная обработка файлов через API `@swc/core`
Ключевая особенность заключается в том, что каждый файл компилируется как независимая единица, что делает задачу идеально параллелизуемой (embarrassingly parallel problem).
## Базовая модель пакетной компиляции
При работе с набором файлов основной задачей становится минимизация накладных расходов на I/O и эффективное распределение трансформаций.
Типовой поток обработки:
1. Сканирование файловой системы
2. Формирование списка исходников
3. Разбиение на батчи
4. Параллельная трансформация через SWC
5. Запись результата
Ключевым ограничением становится не CPU, а дисковый ввод-вывод и управление количеством одновременных задач.
## Использование @swc/core для массовой трансформации
Основной API — `transformFile` и `transform`. При обработке множества файлов используется асинхронный подход.
```javascript
import { transformFile } fr om "@swc/core";
import { readdir } fr om "fs/promises";
import path fr om "path";
async function getFiles(dir) {
const entries = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(entries.map(async (entry) => {
const res = path.resolve(dir, entry.name);
if (entry.isDirectory()) {
return getFiles(res);
}
if (entry.isFile() && res.endsWith(".js")) {
return res;
}
return null;
}));
return files.flat().filter(Boolean);
}
async function compileFiles() {
const files = await getFiles("./src");
const results = await Promise.all(
files.map(file =>
transformFile(file, {
jsc: {
parser: {
syntax: "ecmascript",
},
target: "es2019",
},
module: {
type: "commonjs",
},
})
)
);
return results;
}
```
Модель `Promise.all` создаёт конкурентный запуск всех задач, однако при большом количестве файлов возникает перегрузка event loop и ограничение по памяти.
## Ограничение параллелизма через пул задач
Полная параллелизация редко эффективна. Оптимальная стратегия — ограниченный concurrency pool.
```javascript
import { transformFile } from "@swc/core";
async function runPool(items, lim it, worker) {
const queue = [...items];
const results = [];
const active = new Set();
async function next() {
if (!queue.length) return;
const item = queue.shift();
const p = worker(item)
.then((res) => results.push(res))
.finally(() => active.delete(p));
active.add(p);
if (active.size < lim it) {
await next();
}
}
const starters = Array.from({ length: lim it }, next);
await Promise.all(starters);
await Promise.all(active);
return results;
}
```
Применение к SWC:
```javascript
const results = await runPool(
files,
8,
(file) => transformFile(file, config)
);
```
Ограничение параллелизма снижает конкуренцию за CPU и уменьшает давление на GC.
## Внутренняя параллельность SWC и влияние на внешнюю модель
SWC использует многопоточность внутри Rust-реализации. Это означает, что каждый вызов `transformFile` уже может быть частично распараллелен на уровне:
* парсинга AST
* трансформаций деревьев
* генерации кода
В результате внешняя параллельность накладывается на внутреннюю, формируя двухуровневую модель:
* внешний уровень: Node.js concurrency
* внутренний уровень: Rust thread pool
Избыточная параллелизация на внешнем уровне может привести к деградации производительности из-за oversubscription потоков.
## Пакетная обработка через группировку файлов
Эффективная стратегия — батчинг:
```javascript
function chunk(array, size) {
const res = [];
for (let i = 0; i < array.length; i += size) {
res.push(array.slice(i, i + size));
}
return res;
}
```
Использование:
```javascript
const batches = chunk(files, 50);
for (const batch of batches) {
await Promise.all(
batch.map(file => transformFile(file, config))
);
}
```
Преимущества:
* снижение давления на файловую систему
* уменьшение количества активных промисов
* улучшение локальности кэша CPU
## Параллельная обработка с использованием worker_threads
В сценариях, где требуется строгая изоляция CPU-нагрузки, используется `worker_threads`.
```javascript
import { Worker } from "worker_threads";
function runWorker(file) {
return new Promise((resolve, reject) => {
const worker = new Worker("./worker.js", {
workerData: file,
});
worker.on("message", resolve);
worker.on("error", reject);
});
}
```
worker.js:
```javascript
import { parentPort, workerData } from "worker_threads";
import { transformFileSync } from "@swc/core";
const result = transformFileSync(workerData, {
jsc: {
parser: { syntax: "ecmascript" },
target: "es2020",
},
});
parentPort.postMessage(result);
```
Такой подход полезен при:
* тяжёлых трансформациях AST
* изоляции памяти
* предотвращении блокировки event loop
## Сравнение стратегий параллелизма
| Подход | Плюсы | Минусы |
| -------------------- | ----------------------------- | --------------------- |
| Promise.all | простота | перегрузка памяти |
| concurrency pool | контроль нагрузки | сложнее реализация |
| batching | стабильная производительность | менее гибко |
| worker_threads | изоляция | накладные расходы IPC |
| SWC internal threads | высокая скорость | ограниченный контроль |
## Кэширование результатов трансформации
При массовой обработке файлов критично исключение повторных трансформаций.
Стратегия ключа кэша:
* путь файла
* hash содержимого
* конфигурация SWC
```javascript
import crypto from "crypto";
function hashContent(content) {
return crypto.createHash("sha1").update(content).digest("hex");
}
```
При интеграции с файловой системой:
* хранение результатов в `.cache`
* проверка модификации по mtime
* инвалидация при изменении конфигурации
## Потоковая модель обработки больших проектов
При тысячах файлов применяется pipeline:
1. чтение директории потоково
2. фильтрация по расширению
3. немедленная отправка в очередь обработки
4. запись результата без накопления всего массива
```javascript
import { createReadStream } from "fs";
async function processStream(files, handler) {
for await (const file of files) {
await handler(file);
}
}
```
Комбинация с ограниченным пулом задач позволяет удерживать стабильное потребление памяти.
## Балансировка нагрузки между CPU и I/O
SWC-трансформация обычно CPU-bound, но в реальных сценариях:
* чтение файлов — I/O bound
* трансформация — CPU bound
* запись — I/O bound
Эффективная модель требует перекрытия фаз:
* пока один файл компилируется, другой читается
* пока результат пишется, следующий уже парсится
Эта модель реализуется через pipeline с очередями.
## Интеграция с build-системами
В системах сборки (Webpack, Rollup, Vite-подобные пайплайны) SWC используется как трансформер:
* замена Babel
* ускорение TS-компиляции
* предобработка JSX
Параллельная обработка файлов становится критическим фактором времени сборки, особенно при инкрементальной компиляции.
Особенность интеграции:
* распределение файлов по чанкам модулей
* сохранение зависимостей графа
* избежание повторной трансформации зависимых модулей
## Практическая модель оптимального пайплайна
Стабильная архитектура массовой обработки файлов с SWC включает:
* ограниченный concurrency pool (4–12 потоков на процесс)
* внутренний Rust parallelism без искусственного увеличения внешнего параллелизма
* батчинг по 20–100 файлов
* кэширование по хэшу содержимого
* стриминговое чтение директорий
* минимизацию промежуточных массивов
Такая комбинация обеспечивает устойчивую производительность при масштабировании проектов до десятков тысяч файлов без деградации времени сборки.