Neu veröffentlicht: E-Commerce mit Power Pages, Stripe & Analytics

· Webentwicklung  · 4 minuten Lesezeit

Warum "module": "Node16" deine Vite-Extension kaputt macht – und wie du es in 2 Minuten fixst

Neun TypeScript-Fehler auf einmal, alle über fehlende .js-Extensions in Importen. Der Grund: Ein tsconfig.json-Setting, das für Node.js korrekt ist, aber mit Vite als Bundler fundamental inkompatibel ist. Ich erkläre warum – und zeige die richtige Konfiguration.

Inhalt

Der Fehler, der keine Erklärung zu haben scheint

Du hast TypeScript-Code geschrieben, der syntaktisch korrekt ist. Die Imports sehen so aus:

import { API_ENDPOINT } from "@/config";
import { buildPayload } from "@/background/payload-builder";

Dann führst du npm run build aus und bekommst das:

error TS2835: Relative import paths need explicit file extensions in EcmaScript imports
when '--moduleResolution' is 'node16' or 'nodenext'.
Did you mean './payload-builder.js'?

Nicht einen Fehler. Neun davon. In verschiedenen Dateien. Alle dasselbe.

Und das Absurde: Der Code hat vorher funktioniert. Du hast nichts geändert.

Was wirklich passiert

Das Problem liegt in der tsconfig.json und dem Zusammenspiel von zwei Settings, die zusammen eine Falle bilden:

{
  "compilerOptions": {
    "module": "Node16",
    "moduleResolution": "node16"
  }
}

Was "module": "Node16" bedeutet: TypeScript behandelt deine Dateien als natives ECMAScript-Module für Node.js 16+. In diesem Modus gelten die Node.js-ESM-Regeln – und die besagen: Relative Imports brauchen explizite Dateiendungen.

// So muss es in Node.js ESM aussehen:
import { foo } from "./foo.js"; // .js, nicht .ts!

Das ist korrekt für Node.js-Projekte. Es ist fundamental falsch für Vite.

Warum Vite anders ist: Vite ist ein Bundler. Er verarbeitet TypeScript-Dateien, bevor sie in den Browser oder als Extension-Bundle kommen. Vite versteht .ts-Imports direkt – er braucht keine .js-Extension, weil er selbst auflöst, was welche Datei ist.

Wenn TypeScript-Compiler (tsc) und Vite gleichzeitig im Spiel sind, entsteht ein Konflikt: tsc prüft nach Node.js-ESM-Regeln (.js erforderlich), Vite erwartet Bundler-Semantik (.ts oder gar keine Extension).

Die Lösung

Für Vite-basierte Projekte (Chrome Extensions, Frontend-Apps, alles mit @crxjs) ist die korrekte tsconfig.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2020", "DOM"],
    "strict": true,
    "noEmit": true,
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "jsx": "react-jsx"
  },
  "include": ["src"]
}

Die vier entscheidenden Änderungen:

SettingVorherNachherWarum
moduleNode16ESNextKein Node.js-ESM-Enforcement
moduleResolutionnode16bundlerVite-kompatible Auflösung
noEmit(fehlend)truetsc nur für Type-Checking, Vite baut
allowImportingTsExtensions(fehlend)true.ts in Imports erlaubt

noEmit: true ist der wichtigste Schalter.

Mit noEmit: true kompiliert tsc keinen JavaScript-Output. Es prüft nur die Typen. Das ist für Vite-Projekte der richtige Ansatz: Vite übernimmt das Bauen, TypeScript übernimmt das Type-Checking. Beide tun, was sie am besten können.

Warum das Backend eine andere tsconfig hat

Wenn du dir das Backend-Projekt ansiehst, findest du dort eine andere Konfiguration:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "moduleResolution": "node",
    "outDir": "./dist",
    "strict": true
  }
}

Hier ist "module": "CommonJS"und das ist richtig.

Das Backend ist ein Node.js-Server. Er verwendet require() und CommonJS-Module. Kein Bundler, kein Vite, kein Browser-Kontext. TypeScript soll hier tatsächlich JavaScript ausgeben (outDir: ./dist), das Node.js direkt ausführen kann.

Die Regel lautet also:

  • Vite-Projekt (Extension, Frontend): "module": "ESNext", "moduleResolution": "bundler", noEmit: true
  • Node.js-Projekt (Backend, CLI): "module": "CommonJS", "moduleResolution": "node", outDir gesetzt

Dieselbe tsconfig für beide zu verwenden ist ein klassischer Anfängerfehler – und ein Fehler, den man nicht auf Anhieb versteht, weil die Fehlermeldung nicht sagt “wrong module system”, sondern “missing .js extensions”.

Der Vollständigkeits-Check: Alle drei Projekte

Nach dem Fix habe ich alle drei Projekte im Ökosystem überprüft:

# Extension (Vite + @crxjs)
cd extension && npm run build
# ✅ Keine Fehler – moduleResolution: bundler ist korrekt

# Backend (Node.js + Express)
cd backend && npm run build
# ✅ Keine Fehler – module: CommonJS ist korrekt

# Frontend (Vite + React)
cd frontend && npm run build
# ✅ Keine Fehler – moduleResolution: bundler war bereits korrekt

Drei verschiedene Projekte, drei verschiedene Build-Kontexte, drei verschiedene tsconfig-Konfigurationen. Alle richtig. Alle für ihre jeweilige Laufzeitumgebung optimiert.

Das Muster hinter dem Fehler

Dieser Fehler tritt immer auf, wenn jemand:

  1. Eine tsconfig.json von einem Node.js-Projekt kopiert und in ein Vite-Projekt einfügt
  2. Einer Anleitung folgt, die für Node.js-ESM-Projekte geschrieben ist, aber für Bundler-Projekte verwendet wird
  3. TypeScript-Templates nutzt, die nicht Vite-spezifisch sind

Das macht diesen Fehler besonders tückisch: Er hat eine oberflächlich plausible Lösung (.js zu allen Importen hinzufügen), die das eigentliche Problem nicht löst, sondern verschlimmert.

Die richtige Lösung ist immer eine Frage: Was ist das Laufzeitmodell dieses Projekts?

  • Browser (Vite als Bundler): bundler
  • Node.js (kein Bundler): node oder node16
  • Beides (Monorepo): Separate tsconfig-Dateien für jedes Projekt

Warum ich das in einem eigenen Post dokumentiere

Weil dieser Fehler Zeit kostet. Nicht fünf Minuten – sondern manchmal Stunden, wenn man die falsche Lösung ausprobiert oder den Fehler nicht mit Vite in Verbindung bringt.

Und weil ich in Kundenprojekten regelmäßig auf veraltete oder falsch konfigurierte TypeScript-Setups stoße. Power Pages Portale mit React-Integrationen haben oft komplexe Projekt-Setups mit mehreren tsconfig-Dateien (main, test, strict). Wer den Unterschied zwischen node, node16 und bundler als moduleResolution versteht, kann diese Probleme in Minuten lösen statt in Stunden.

Das ist kein exotisches TypeScript-Wissen. Es ist Grundlage.

Zurück zur Übersicht: Von der Screenshot-Extension zur KI-Memory-Engine

Zurück zum Blog

Ähnliche Beiträge

Alle Beiträge ansehen