TypeScript Natif dans Node.js : La Révolution du --experimental-strip-types
Salut HaWkers, 2025 a marqué le début d'une nouvelle ère pour les développeurs TypeScript : exécuter du code TypeScript directement dans Node.js, sans transpilation préalable.
Vous êtes-vous déjà demandé pourquoi nous devons compiler TypeScript en JavaScript avant de l'exécuter ? Et si Node.js pouvait simplement... ignorer les types et exécuter directement ?
Le Grand Changement dans Node.js 22+
Avec Node.js 22, une fonctionnalité expérimentale révolutionnaire a été introduite : --experimental-strip-types. Ce flag permet à Node.js d'exécuter des fichiers TypeScript nativement, en supprimant les annotations de type au moment de l'exécution.
Le Problème Traditionnel
Historiquement, travailler avec TypeScript dans Node.js impliquait toujours plusieurs étapes :
# Flux traditionnel (encore courant en 2024)
# 1. Écrire TypeScript
# 2. Compiler avec tsc
npx tsc
# 3. Exécuter le JavaScript généré
node dist/index.js
# Ou utiliser des outils comme ts-node
npx ts-node src/index.tsDes outils comme ts-node, tsx, et ts-node-dev sont apparus pour simplifier, mais ils dépendaient encore de la transpilation en mémoire.
Comment Fonctionne le --experimental-strip-types
Node.js 22+ a introduit une approche radicalement différente : au lieu de transpiler TypeScript en JavaScript, il supprime simplement les annotations de type et exécute le code.
Exemple Pratique
// user.ts - Code TypeScript pur
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
class UserService {
private users: Map<number, User> = new Map();
createUser(name: string, email: string): User {
const id = this.users.size + 1;
const user: User = {
id,
name,
email,
createdAt: new Date()
};
this.users.set(id, user);
return user;
}
getUser(id: number): User | undefined {
return this.users.get(id);
}
getAllUsers(): User[] {
return Array.from(this.users.values());
}
}
// Utilisation
const service = new UserService();
const user = service.createUser('Jeff', 'jeff@example.com');
console.log(user);Avant (Node.js 20 et antérieur) :
# Il fallait compiler d'abord
npx tsc user.ts
node user.js
# Ou utiliser ts-node
npx ts-node user.tsMaintenant (Node.js 22+) :
# Exécution directe !
node --experimental-strip-types user.tsCe qui se Passe Sous le Capot
Node.js ne compile pas TypeScript. Il fait quelque chose de plus simple et rapide :
// TypeScript original
function greet(name: string): string {
return `Hello, ${name}!`;
}
const user: User = { name: 'Jeff', age: 30 };
// Ce que Node.js exécute réellement (types supprimés)
function greet(name) {
return `Hello, ${name}!`;
}
const user = { name: 'Jeff', age: 30 };
Configuration Moderne pour TypeScript + Node.js en 2025
Créons un projet moderne en profitant de ces nouvelles capacités :
1. Initialisation du Projet
# Créer le répertoire
mkdir modern-node-ts
cd modern-node-ts
# Initialiser package.json
npm init -y
# Installer TypeScript (uniquement pour le type checking)
npm install -D typescript @types/node
# Créer tsconfig.json
npx tsc --init2. Configuration du tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
// Pour le type checking uniquement, ne génère pas de sortie
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}3. Package.json avec Scripts Modernes
{
"name": "modern-node-ts",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "node --experimental-strip-types --watch src/index.ts",
"start": "node --experimental-strip-types src/index.ts",
"type-check": "tsc --noEmit",
"check": "tsc --noEmit && echo '✓ Type check passed'"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.6.0"
}
}4. Exemple d'Application Complète
// src/config.ts
export interface AppConfig {
port: number;
host: string;
env: 'development' | 'production' | 'test';
}
export const config: AppConfig = {
port: parseInt(process.env.PORT || '3000'),
host: process.env.HOST || 'localhost',
env: (process.env.NODE_ENV as AppConfig['env']) || 'development'
};
// src/database.ts
export interface DatabaseRecord {
id: string;
createdAt: Date;
updatedAt: Date;
}
export class InMemoryDatabase<T extends DatabaseRecord> {
private records: Map<string, T> = new Map();
async create(data: Omit<T, 'id' | 'createdAt' | 'updatedAt'>): Promise<T> {
const record = {
...data,
id: crypto.randomUUID(),
createdAt: new Date(),
updatedAt: new Date()
} as T;
this.records.set(record.id, record);
return record;
}
async findById(id: string): Promise<T | null> {
return this.records.get(id) || null;
}
async findAll(): Promise<T[]> {
return Array.from(this.records.values());
}
async update(id: string, data: Partial<T>): Promise<T | null> {
const record = this.records.get(id);
if (!record) return null;
const updated = {
...record,
...data,
updatedAt: new Date()
};
this.records.set(id, updated);
return updated;
}
async delete(id: string): Promise<boolean> {
return this.records.delete(id);
}
}
// src/user.ts
import type { DatabaseRecord } from './database.js';
import { InMemoryDatabase } from './database.js';
export interface User extends DatabaseRecord {
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
}
export class UserRepository extends InMemoryDatabase<User> {
async findByEmail(email: string): Promise<User | null> {
const users = await this.findAll();
return users.find(u => u.email === email) || null;
}
async isAdmin(userId: string): Promise<boolean> {
const user = await this.findById(userId);
return user?.role === 'admin';
}
}
// src/index.ts
import { createServer } from 'http';
import { config } from './config.js';
import { UserRepository } from './user.js';
const userRepo = new UserRepository();
// Créer quelques utilisateurs d'exemple
const admin = await userRepo.create({
name: 'Admin User',
email: 'admin@example.com',
role: 'admin'
});
const regularUser = await userRepo.create({
name: 'Jeff Bruchado',
email: 'jeff@example.com',
role: 'user'
});
console.log('📦 Users created:', { admin, regularUser });
// Serveur HTTP simple
const server = createServer(async (req, res) => {
const url = new URL(req.url || '/', `http://${req.headers.host}`);
if (url.pathname === '/users' && req.method === 'GET') {
const users = await userRepo.findAll();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(users, null, 2));
return;
}
if (url.pathname.startsWith('/users/') && req.method === 'GET') {
const id = url.pathname.split('/')[2];
const user = await userRepo.findById(id);
if (!user) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'User not found' }));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(user, null, 2));
return;
}
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Route not found' }));
});
server.listen(config.port, config.host, () => {
console.log(`🚀 Server running at http://${config.host}:${config.port}`);
console.log(`📝 Environment: ${config.env}`);
console.log('\nAvailable routes:');
console.log(' GET /users');
console.log(' GET /users/:id');
});Maintenant exécutez :
# Développement avec hot reload
npm run dev
# Type checking
npm run check
# Production
npm start
Avantages de l'Approche Native
1. Démarrage Plus Rapide
Sans transpilation signifie une initialisation plus rapide, particulièrement important pour :
- Scripts CLI
- Fonctions serverless
- Tests unitaires
- Développement local
# Comparaison des temps de démarrage
# ts-node (transpilation JIT)
time ts-node src/index.ts
# ~450ms
# Node.js 22+ avec strip-types
time node --experimental-strip-types src/index.ts
# ~120ms 🚀2. Setup Minimaliste
{
"devDependencies": {
"typescript": "^5.6.0", // Uniquement pour le type checking
"@types/node": "^22.0.0"
}
}Pas besoin de : ts-node, tsx, @swc/core, esbuild, etc.
3. Watch Mode Natif
# Watch mode intégré à Node.js
node --experimental-strip-types --watch src/index.tsDétecte les changements et redémarre automatiquement, sans outils externes.
4. Debugging Direct
# Le debugging dans VS Code fonctionne nativement
node --experimental-strip-types --inspect src/index.ts
Limitations et Considérations
Ce qui NE Fonctionne PAS (Encore)
1. Transformations TypeScript
// ❌ Les enums ne fonctionnent pas (nécessitent une transformation)
enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE'
}
// ✅ Utilisez les union types
type Status = 'ACTIVE' | 'INACTIVE';
// ❌ Les decorators ne fonctionnent pas
class UserController {
@Get('/users')
getUsers() {}
}
// ✅ Utilisez des patterns alternatifs
const routes = {
getUsers: {
method: 'GET',
path: '/users',
handler: getUsers
}
};2. Syntaxe Namespace
// ❌ Les namespaces ne sont pas supportés
namespace Utils {
export function formatDate() {}
}
// ✅ Utilisez les modules ES6
export function formatDate() {}3. Paramètres Optionnels dans les Interfaces Implémentées
Ça fonctionne, mais soyez prudent avec la compatibilité.
Quand Utiliser Encore les Build Tools
Vous avez encore besoin d'outils comme esbuild, swc, ou tsc pour :
- Production avec bundling
- Compatibilité avec les anciennes versions de Node.js
- Utilisation de fonctionnalités TypeScript avancées (enums, decorators)
- Optimisations de taille de bundle
// package.json pour setup hybride
{
"scripts": {
"dev": "node --experimental-strip-types --watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"type-check": "tsc --noEmit"
}
}
Comparaison : Strip Types vs Alternatives
Node.js Strip Types
# Avantages :
# - Zéro dépendances supplémentaires
# - Démarrage rapide
# - Watch mode natif
# - Debugging direct
# Inconvénients :
# - Expérimental (peut changer)
# - Sans transformations (enums, decorators)
# - Nécessite Node.js 22+tsx (Successeur de ts-node)
npm install -D tsx
# Avantages :
# - Très rapide (utilise esbuild)
# - Supporte toutes les fonctionnalités TS
# - Fonctionne avec les anciennes versions de Node.js
# - Stable et mature
# Inconvénients :
# - Dépendance supplémentaire
# - Transpilation JIT (petit overhead)Bun
# Avantages :
# - TypeScript natif sans flags
# - Extrêmement rapide
# - Runtime complet
# Inconvénients :
# - Runtime différent de Node.js
# - La compatibilité peut varierDeno
# Avantages :
# - TypeScript première classe
# - Sécurisé par défaut
# - Outils intégrés
# Inconvénients :
# - Écosystème différent
# - Nécessite des changements dans le codeL'Avenir de TypeScript dans Node.js
Roadmap Attendue
2025-2026 :
- Strip types sort de l'expérimental
- Support de plus de syntaxe TypeScript
- Performance optimisée
2027+ :
- TypeScript comme citoyen de première classe
- Possible support du type checking en runtime (opt-in)
- Intégration complète avec le tooling Node.js
// Vision du futur (spéculatif)
const futureNodeTS = {
native_typescript: true,
strip_types: 'stable',
optional_features: {
runtime_type_checking: true, // opt-in
enum_support: true,
decorator_support: true
},
performance: {
startup: 'near-javascript speed',
execution: 'zero overhead',
memory: 'minimal'
},
developer_experience: {
setup_complexity: 'minimal',
tooling_integration: 'seamless',
debugging: 'native'
}
};Adoption de l'Industrie
TypeScript devient le standard :
- 38.5% des développeurs utilisent déjà TypeScript
- Top 5 des langages les plus populaires
- La majorité des nouveaux projets Node.js commencent avec TS
// Pattern émergent en 2025
// Les projets commencent directement en TypeScript
// package.json
{
"name": "my-new-project",
"type": "module",
"scripts": {
"dev": "node --experimental-strip-types --watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}Si vous êtes enthousiaste par les nouveautés de TypeScript et Node.js, je vous recommande de jeter un œil à un autre article : Create React App Déprécié : Migration vers Vite et l'Avenir de React où vous découvrirez un autre grand changement dans l'écosystème JavaScript en 2025.

