Почему TypeScript не живёт в вакууме?
Когда мы пишем на TypeScript, мы привыкаем к его строгости, автодополнению и дженерикам. Но рано или поздно перед каждым разработчиком встаёт вопрос: "А что, если мне нужно вызвать код на Python, C++ или Go?". Не переписывать же весь проект?
Хорошая новость: TypeScript — не изолированный остров. Он умеет дружить с другими языками через нативные модули, WebAssembly, gRPC и даже обычные HTTP-мосты. В этой статье мы разберём три реальных сценария интеграции TypeScript с другими языками. Вы узнаете, как склеить фронтенд, бэкенд и высокопроизводительные вычисления в одном стеке.
Сценарий 1: TypeScript + Python — дата-сайенс на стероидах
Python — король машинного обучения и анализа данных. Но его слабость — веб-интерфейсы. TypeScript, наоборот, блестяще работает в браузере и на сервере. Логично объединить их силы.
Способ А: HTTP-микросервис (самый простой)
Самый очевидный путь — запустить Python-сервер (например, FastAPI) и дёргать его из TypeScript через fetch.
Python (backend.py):
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
app = FastAPI()
model = joblib.load('model.pkl')
class InputData(BaseModel):
features: list[float]
@app.post("/predict")
def predict(data: InputData):
result = model.predict([data.features])
return {"prediction": result.tolist()}
TypeScript (frontend.ts):
interface PredictionResponse {
prediction: number[];
}
async function getPrediction(features: number[]): Promise<number[]> {
const response = await fetch('http://localhost:8000/predict', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ features })
});
if (!response.ok) throw new Error('Ошибка предсказания');
const data: PredictionResponse = await response.json();
return data.prediction;
}
// Использование
getPrediction([1.2, 3.4, 5.6])
.then(pred => console.log('Результат:', pred))
.catch(err => console.error(err));
Способ Б: Python-скрипты через child_process (для бэкенда)
Если вам не нужен отдельный сервер, можно запускать Python прямо из Node.js. Это удобно для разовых задач — обработка изображений, генерация отчётов.
import { exec } from 'child_process';
import { promisify } from 'util';
const execPromise = promisify(exec);
async function runPythonScript(scriptPath: string, args: string[]): Promise<string> {
const command = `python3 ${scriptPath} ${args.join(' ')}`;
const { stdout, stderr } = await execPromise(command);
if (stderr) throw new Error(`Python error: ${stderr}`);
return stdout.trim();
}
// Пример: передаём JSON в Python
const result = await runPythonScript('./analyze.py', ['--data', '{"value": 42}']);
console.log('Python ответил:', result);
Важно: такой подход медленнее микросервиса, зато не требует настройки отдельного сервера. Подходит для прототипов и CI/CD.
Сценарий 2: TypeScript + C++ — скорость на грани фантастики
Когда нужно обрабатывать видео, аудио или криптографию, Python может не хватить. Тут в игру вступает C++ и его мощный союзник — WebAssembly (WASM).
Компиляция C++ в WASM и вызов из TypeScript
Допустим, у нас есть простая функция на C++, которая ускоряет умножение матриц:
// matrix.cpp
extern "C" {
void multiply_matrices(float* a, float* b, float* result, int size) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
float sum = 0.0f;
for (int k = 0; k < size; k++) {
sum += a[i * size + k] * b[k * size + j];
}
result[i * size + j] = sum;
}
}
}
}
Компилируем в WASM с помощью Emscripten:
emcc matrix.cpp -s WASM=1 -s EXPORTED_FUNCTIONS='["_multiply_matrices"]' -o matrix.wasm
Теперь вызываем из TypeScript (в браузере или Node.js):
// Загружаем WASM-модуль
async function loadWasm(): Promise<WebAssembly.Instance> {
const response = await fetch('matrix.wasm');
const bytes = await response.arrayBuffer();
const results = await WebAssembly.instantiate(bytes, {});
return results.instance;
}
// Обёртка для удобства
function multiplyWASM(a: Float32Array, b: Float32Array, size: number): Float32Array {
const instance = wasmInstance; // глобальная переменная
const result = new Float32Array(size * size);
// Копируем данные в память WASM (упрощённо)
const aPtr = instance.exports.malloc(a.length * 4);
const bPtr = instance.exports.malloc(b.length * 4);
const resultPtr = instance.exports.malloc(result.length * 4);
// Заполняем буферы...
instance.exports.multiply_matrices(aPtr, bPtr, resultPtr, size);
// Читаем результат
// ... (опущено для краткости)
return result;
}
// Использование
const wasmInstance = await loadWasm();
const result = multiplyWASM(
new Float32Array([1, 2, 3, 4]),
new Float32Array([5, 6, 7, 8]),
2
);
console.log('Результат умножения (WASM):', result);
Плюсы: скорость близка к нативной. Минусы: сложность передачи сложных структур данных (строки, объекты). Для простых числовых массивов — идеально.
Нативные аддоны (Node.js + C++)
Если вы работаете на сервере и не боитесь node-gyp, можно написать .node аддон. Пример с использованием N-API:
// addon.cpp
#include <napi.h>
#include <vector>
Napi::Number FastSum(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
auto arr = info[0].As<Napi::Array>();
double sum = 0.0;
for (uint32_t i = 0; i < arr.Length(); i++) {
sum += arr.Get(i).As<Napi::Number>().DoubleValue();
}
return Napi::Number::New(env, sum);
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("fastSum", Napi::Function::New(env, FastSum));
return exports;
}
NODE_API_MODULE(addon, Init)
TypeScript-обёртка:
const addon = require('bindings')('addon.node');
interface NativeAddon {
fastSum(arr: number[]): number;
}
const typedAddon = addon as NativeAddon;
console.log('Сумма через C++:', typedAddon.fastSum([1, 2, 3, 4,