TypeScript как клей: интеграция с Python, C++ и Go

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему 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,                

Блоги

Рекомендаций по книгам