Aller au contenu

Créer une application polyglotte

Combinez un paquet npm et un paquet PyPI dans un même programme Elide : passez des valeurs, des fonctions et des erreurs entre JavaScript et Python avec --interop.

Sur cette page

Cette page construit un programme qui utilise un paquet npm depuis JavaScript et un paquet PyPI depuis Python, dans un seul processus.

PrérequisComment
Pythonelide setup python ; elide info affiche python sous Languages
Appels entre langages--interop à chaque exécution ; le manifeste n’a aucun réglage équivalent
Paquets npmdependencies.npm dans elide.pkl, puis elide install --ecosystems=npm
Paquets PyPIUn environnement virtuel Python 3.13, sélectionné avec VIRTUAL_ENV

Configurer le projet

// elide.pkl
amends "elide:project.pkl"

name = "poly"

dependencies {
  npm { packages { "pretty-ms@9.3.1" } }
  pypi { packages { "humanize==4.12.3" } }
}
elide install --ecosystems=npm

Un simple elide install n’installe que Maven. Elide n’installe pas les paquets PyPI : elide install --ecosystems=pypi se termine avec le code 2 et le message pypi installs are not available in this build. Gardez le bloc pypi comme référence et installez les paquets avec le pip de Python 3.13 dans un environnement virtuel :

python3.13 -m venv .venv
.venv/bin/pip install humanize==4.12.3
export VIRTUAL_ENV=$PWD/.venv

Elide charge les paquets depuis $VIRTUAL_ENV/lib/python3.13/site-packages. Les paquets en pur Python fonctionnent ; certains modules d’extension C ne se chargent pas (voir Python).

Écrire le programme

# report.py
import humanize

def size(n):
    return humanize.naturalsize(n)

def summarize(files):
    total = sum(f.bytes for f in files)
    return {"count": len(files), "total": humanize.naturalsize(total)}
// app.mjs
import prettyMs from "pretty-ms";
import report from "./report.py";

const files = [
  { name: "build.log", bytes: 1_500_000, ageMs: 172_800_000 },
  { name: "test.log", bytes: 32_000, ageMs: 5_400_000 },
];

for (const f of files) {
  console.log(`${f.name}: ${report.size(f.bytes)}, ${prettyMs(f.ageMs)} old`);
}

const summary = report.summarize(files);
console.log(`${summary.count} files, ${summary.total}`);
elide --interop run app.mjs
build.log: 1.5 MB, 2d old
test.log: 32.0 kB, 1h 30m old
2 files, 1.5 MB

L’export par défaut d’un fichier Python est son module ; importez-le depuis un point d’entrée .mjs. Deux échecs courants :

ManquantErreur
--interopError: Cannot import 'report.py': JavaScript entries run without Python. Re-run with --interop to use it.
VIRTUAL_ENVModuleNotFoundError: No module named 'humanize'

Passer des valeurs

# shapes.py
def label(order):
    return f"{order.item} x{order.qty}"

def doubled(prices):
    return [p * 2 for p in prices]

def config():
    return {"region": "eu", "retries": 3}

def apply(fn, value):
    return fn(value)

def fail():
    raise ValueError("bad input")
// values.mjs
import shapes from "./shapes.py";

console.log(shapes.label({ item: "bolt", qty: 4 }));
console.log(shapes.doubled([1, 2.5, 3]).map((n) => n + 1));
const config = shapes.config();
console.log(config.region, config.get("retries"));
console.log(JSON.stringify(Object.fromEntries(config.items())));
console.log(shapes.apply((s) => s.toUpperCase(), "shout"));
console.log(shapes.apply((v) => v, null), shapes.apply((v) => v, undefined));
elide --interop run values.mjs
bolt x4
(3)[3, 6, 7]
eu 3
{"region":"eu","retries":3}
SHOUT
null undefined
ValeurDe JavaScript vers PythonDe Python vers JavaScript
Nombres, chaînes, booléensValeurs nativesValeurs natives
null, undefined / NoneNone, et inchangées au retournull
Objet / dictLire les champs comme attributs (order.item) ; order["item"] lève TypeErrorLire les clés avec d.key, d["key"] ou d.get("key") ; Object.keys, in et JSON.stringify voient les méthodes du dict, pas ses clés. Convertissez avec Object.fromEntries(d.items()).
Tableau / listItérable, indexable, len() fonctionneArray.isArray vaut true ; map, la décomposition et Array.from fonctionnent
FonctionAppelableAppelable

Appeler JavaScript depuis Python

Le module elide évalue du JavaScript et renvoie sa valeur :

# calls.py
from elide import js

slug = js("(s) => s.trim().toLowerCase().replaceAll(' ', '-')")
print(slug("  Hello Polyglot World "))

point = js("({ x: 1, y: 2, tags: ['a', 'b'] })")
print(point.x + point.y, point.tags[0], len(point.tags))
print(sorted(js("[3, 1, 2]")))

try:
    js("JSON.parse('{oops')")
except RuntimeError as e:
    print("caught:", e)

js("Promise.resolve(42)").then(lambda v: print("resolved:", v))
elide --interop python calls.py
hello-polyglot-world
3 a 2
[1, 2, 3]
caught: JSException: SyntaxError: Unexpected token o in JSON at position 1
resolved: 42

Gérer les erreurs

Une exception Python arrive dans JavaScript sous la forme d’une Error dont le name est la classe de l’exception ; une erreur JavaScript arrive dans Python sous la forme d’une RuntimeError. Une erreur non interceptée affiche une seule pile couvrant les deux langages :

// errors.mjs
import shapes from "./shapes.py";

try {
  shapes.fail();
} catch (e) {
  console.log(e.name, "|", e.message);
}

shapes.apply((x) => {
  throw new RangeError(`bad value ${x}`);
}, 7);
elide --interop run errors.mjs
ValueError | ValueError: bad input
RangeError: bad value 7
    at :=> (errors.mjs:10:9)
    at apply (shapes.py:11:12)
    at :module:eval (errors.mjs:9:1)

Le programme se termine avec 1.

Appeler du code asynchrone

DepuisFonctionneNe fonctionne pas
JavaScript qui appelle PythonAppeler un wrapper synchrone qui exécute asyncio.run(...)await sur une coroutine Python renvoie la coroutine, non exécutée
Python qui détient une promesse JavaScriptpromise.then(callback)await promise lève TypeError: object polyglot.ForeignObject can't be used in 'await' expression
# rates.py
import asyncio

async def fetch_rate(code):
    await asyncio.sleep(0.01)
    return {"EUR": 0.92, "GBP": 0.79}[code]

def rate(code):
    return asyncio.run(fetch_rate(code))

def when_ready(promise, done):
    promise.then(lambda value: done(f"python saw {value}"))
// rates.mjs
import rates from "./rates.py";

console.log("rate:", rates.rate("EUR"));

const total = new Promise((resolve) => setTimeout(() => resolve(7), 10));
rates.when_ready(total, (msg) => console.log(msg));
await total;
elide --interop run rates.mjs
rate: 0.92
python saw 7

asyncio.run bloque la boucle d’événements JavaScript jusqu’à la fin de la coroutine. Les threads Python ne sont pas disponibles sous --interop.

Java et Kotlin

elide run Hello.kt exécute Kotlin seul ; JavaScript et Python ne peuvent pas l’appeler. L’importer échoue :

import hello from "./Hello.kt";
TypeError: Unsupported file extension: 'file:/path/to/Hello.kt'

Voir aussi Langages et interopérabilité, Python et Installateur npm.

Exemples

Cette page vous a-t-elle aidé ?