TypeScript Nativo no Node.js: A Revolução do --experimental-strip-types
Olá HaWkers, 2025 marcou o início de uma nova era para desenvolvedores TypeScript: executar código TypeScript diretamente no Node.js, sem transpilação prévia.
Você já se perguntou por que precisamos compilar TypeScript para JavaScript antes de executar? E se o Node.js pudesse simplesmente... ignorar os tipos e executar direto?
A Grande Mudança no Node.js 22+
Com o Node.js 22, uma funcionalidade experimental revolucionária foi introduzida: --experimental-strip-types. Esta flag permite ao Node.js executar arquivos TypeScript nativamente, removendo anotações de tipo em tempo de execução.
O Problema Tradicional
Historicamente, trabalhar com TypeScript no Node.js sempre envolveu múltiplas etapas:
# Fluxo tradicional (ainda comum em 2024)
# 1. Escrever TypeScript
# 2. Compilar com tsc
npx tsc
# 3. Executar o JavaScript gerado
node dist/index.js
# Ou usar ferramentas como ts-node
npx ts-node src/index.tsFerramentas como ts-node, tsx, e ts-node-dev surgiram para simplificar, mas ainda dependiam de transpilação em memória.
Como Funciona o --experimental-strip-types
O Node.js 22+ introduziu uma abordagem radicalmente diferente: em vez de transpilar TypeScript para JavaScript, ele simplesmente remove as anotações de tipo e executa o código.
Exemplo Prático
// user.ts - Código TypeScript puro
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());
}
}
// Uso
const service = new UserService();
const user = service.createUser('Jeff', 'jeff@example.com');
console.log(user);Antes (Node.js 20 e anterior):
# Precisava compilar primeiro
npx tsc user.ts
node user.js
# Ou usar ts-node
npx ts-node user.tsAgora (Node.js 22+):
# Execução direta!
node --experimental-strip-types user.tsO Que Está Acontecendo Por Baixo
O Node.js não está compilando TypeScript. Ele está fazendo algo mais simples e rápido:
// TypeScript original
function greet(name: string): string {
return `Hello, ${name}!`;
}
const user: User = { name: 'Jeff', age: 30 };
// O que Node.js realmente executa (tipos removidos)
function greet(name) {
return `Hello, ${name}!`;
}
const user = { name: 'Jeff', age: 30 };
Configuração Moderna para TypeScript + Node.js em 2025
Vamos criar um projeto moderno aproveitando estas novas capacidades:
1. Inicialização do Projeto
# Criar diretório
mkdir modern-node-ts
cd modern-node-ts
# Inicializar package.json
npm init -y
# Instalar TypeScript (apenas para type checking)
npm install -D typescript @types/node
# Criar tsconfig.json
npx tsc --init2. Configuração do tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
// Para type checking apenas, não gera output
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}3. Package.json com Scripts Modernos
{
"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. Exemplo de Aplicação Completa
// 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();
// Criar alguns usuários de exemplo
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 });
// Servidor HTTP simples
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');
});Agora execute:
# Desenvolvimento com hot reload
npm run dev
# Type checking
npm run check
# Produção
npm start
Vantagens do Approach Nativo
1. Startup Mais Rápido
Sem transpilação significa inicialização mais rápida, especialmente importante para:
- Scripts CLI
- Funções serverless
- Testes unitários
- Desenvolvimento local
# Comparação de tempo de startup
# ts-node (transpilação JIT)
time ts-node src/index.ts
# ~450ms
# Node.js 22+ com strip-types
time node --experimental-strip-types src/index.ts
# ~120ms 🚀2. Setup Minimalista
{
"devDependencies": {
"typescript": "^5.6.0", // Apenas para type checking
"@types/node": "^22.0.0"
}
}Não precisa de: ts-node, tsx, @swc/core, esbuild, etc.
3. Watch Mode Nativo
# Watch mode built-in do Node.js
node --experimental-strip-types --watch src/index.tsDetecta mudanças e reinicia automaticamente, sem ferramentas externas.
4. Debugging Direto
# Debugging no VS Code funciona nativamente
node --experimental-strip-types --inspect src/index.ts
Limitações e Considerações
O Que NÃO Funciona (Ainda)
1. Transformações de TypeScript
// ❌ Enums não funcionam (precisam transformação)
enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE'
}
// ✅ Use union types
type Status = 'ACTIVE' | 'INACTIVE';
// ❌ Decorators não funcionam
class UserController {
@Get('/users')
getUsers() {}
}
// ✅ Use patterns alternativos
const routes = {
getUsers: {
method: 'GET',
path: '/users',
handler: getUsers
}
};2. Namespace Syntax
// ❌ Namespaces não são suportados
namespace Utils {
export function formatDate() {}
}
// ✅ Use modules ES6
export function formatDate() {}3. Parâmetros Opcionais em Interfaces Implementadas
Funciona, mas tenha cuidado com compatibilidade.
Quando Ainda Usar Build Tools
Você ainda precisa de ferramentas como esbuild, swc, ou tsc para:
- Produção com bundling
- Compatibilidade com Node.js antigo
- Uso de features TypeScript avançadas (enums, decorators)
- Otimizações de bundle size
// package.json para setup híbrido
{
"scripts": {
"dev": "node --experimental-strip-types --watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"type-check": "tsc --noEmit"
}
}
Comparação: Strip Types vs Alternativas
Node.js Strip Types
# Pros:
# - Zero dependencies extras
# - Startup rápido
# - Watch mode nativo
# - Debugging direto
# Cons:
# - Experimental (pode mudar)
# - Sem transformações (enums, decorators)
# - Requer Node.js 22+tsx (Sucessor do ts-node)
npm install -D tsx
# Pros:
# - Muito rápido (usa esbuild)
# - Suporta todas features TS
# - Funciona em Node.js antigo
# - Estável e maduro
# Cons:
# - Dependência extra
# - Transpilação JIT (overhead pequeno)Bun
# Pros:
# - TypeScript nativo sem flags
# - Extremamente rápido
# - Runtime completo
# Cons:
# - Runtime diferente do Node.js
# - Compatibilidade pode variarDeno
# Pros:
# - TypeScript first-class
# - Secure by default
# - Ferramentas built-in
# Cons:
# - Ecossistema diferente
# - Requer mudanças no códigoO Futuro do TypeScript no Node.js
Roadmap Esperado
2025-2026:
- Strip types sai do experimental
- Suporte a mais syntax TypeScript
- Performance otimizada
2027+:
- TypeScript como citizen de primeira classe
- Possível suporte a type checking em runtime (opt-in)
- Integração completa com tooling do Node.js
// Visão do futuro (especulativo)
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'
}
};Adoção da Indústria
TypeScript está se tornando o padrão:
- 38.5% dos desenvolvedores já usam TypeScript
- Top 5 linguagens mais populares
- Maioria dos novos projetos Node.js começam com TS
// Padrão emergente em 2025
// Projetos começam direto em 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"
}
}
Migração de Projeto Existente
Passo a Passo
# 1. Atualizar para Node.js 22+
nvm install 22
nvm use 22
# 2. Atualizar package.json
# Adicionar script de dev
# 3. Testar execução direta
node --experimental-strip-types src/index.ts
# 4. Verificar features incompatíveis
# - Procurar por enums
# - Procurar por namespaces
# - Procurar por decorators
# 5. Refatorar se necessário
# Converter enums para union types, etc.
# 6. Atualizar CI/CD
# Adicionar Node.js 22 nos workflowsScript de Migração
// scripts/check-compatibility.ts
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
interface CompatibilityIssue {
file: string;
line: number;
issue: string;
suggestion: string;
}
async function checkFile(filePath: string): Promise<CompatibilityIssue[]> {
const content = await readFile(filePath, 'utf-8');
const lines = content.split('\n');
const issues: CompatibilityIssue[] = [];
lines.forEach((line, index) => {
// Check for enums
if (/^\s*enum\s+/.test(line)) {
issues.push({
file: filePath,
line: index + 1,
issue: 'Enum found',
suggestion: 'Convert to union type or const object'
});
}
// Check for namespaces
if (/^\s*namespace\s+/.test(line)) {
issues.push({
file: filePath,
line: index + 1,
issue: 'Namespace found',
suggestion: 'Convert to ES6 modules'
});
}
// Check for decorators
if (/^\s*@\w+/.test(line)) {
issues.push({
file: filePath,
line: index + 1,
issue: 'Decorator found',
suggestion: 'Use alternative pattern or keep build step'
});
}
});
return issues;
}
async function scanDirectory(dir: string): Promise<CompatibilityIssue[]> {
const entries = await readdir(dir, { withFileTypes: true });
const issues: CompatibilityIssue[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory() && entry.name !== 'node_modules') {
issues.push(...await scanDirectory(fullPath));
} else if (entry.isFile() && /\.ts$/.test(entry.name)) {
issues.push(...await checkFile(fullPath));
}
}
return issues;
}
// Execute
const issues = await scanDirectory('./src');
if (issues.length === 0) {
console.log('✅ No compatibility issues found!');
console.log('Your project is ready for --experimental-strip-types');
} else {
console.log(`⚠️ Found ${issues.length} compatibility issues:\n`);
issues.forEach(issue => {
console.log(`${issue.file}:${issue.line}`);
console.log(` Issue: ${issue.issue}`);
console.log(` Suggestion: ${issue.suggestion}\n`);
});
}Se você está empolgado com as novidades do TypeScript e Node.js, recomendo dar uma olhada em outro artigo: Create React App Depreciado: Migrando para Vite e o Futuro do React onde você vai descobrir outra grande mudança no ecossistema JavaScript em 2025.

