29.04.2023, Vladimír Klaus, navštíveno 535x

JavaScript

Již nějakou dobu používám ve Visual Studiu TypeScript, který "automaticky" nechám kompilovat do JavaScriptu. Stačí k tomu NodeJS prostředí a v konfiguraci uvést, aby se překládalo při uložení. Vše fungovalo nadmíru krásně, až do chvíle, kdy jsem se chystal přejít na moduly.

Řešení problému "Uncaught ReferenceError: exports is not defined" při kompilaci z TypeScriptu do JavaScriptu, obr. 1

Moduly jsou, zjednodušeně řečeno, další normální JavaScript/TypeScript soubory, které ale exportují (nabízí) nějaké funkce, které zase v jiných modulech či běžných souborech naimportujete (využijete). Hlavní předností je přehlednost a především to, že pak se na dané stránce odkazujete jen na jeden soubor a on si vše ostatní dotáhne těmi importy.

JavaScript

Prakticky to není nic světoborného, zvláště, když to zkoušíte v JavaScriptu a na něčem velmi jednoduchém. Mám tedy nějakou HTML stránku, kde volám JavaScript soubor/modul, který jen něco vypíše do konzole. Nejdůležitější je přidat type="module", jinak skončíte s chybou "Uncaught SyntaxError: Cannot use import statement outside a module".

<html>
<body>
    <script type="module" src="hlavni.js"></script>
</body>
</html>

Hlavní soubor načítá knihovnu, resp. jen její funkci Ahoj() a to pak volá.

import { Ahoj } from './knihovna.js';

Ahoj()

A vlastní knihovna pak definuje funkci Ahoj() včetně toho, že ji vystavuje/exportuje.

export function Ahoj() {
    console.log('Ahoj světe!')
}

Vše je v jedné složce, která vypadá třeba takto:

Řešení problému "Uncaught ReferenceError: exports is not defined" při kompilaci z TypeScriptu do JavaScriptu, obr. 2

TypeScript

Když se toto pokusíte přepsat do TypeScriptu, je to velmi jednoduché - je to vlastně úplně stejné. Jenomže prohlížeče TypeScriptu nerozumí, tak se to nejprve musí přeložit do JavaScriptu. Nejprve tedy *.js soubory přejmenujte na *.ts.

A jak se to přeloží, řeší konfigurační soubor tsconfig.json. Jedna z možných a hlavně funkčních verzí je tato:

{
  "compilerOptions": {
    "module": "es2015",
    "target": "es5",
    "sourceMap": true
  },
  "compileOnSave": true,
  "include": ["./*.ts"]
}

Složka s tímto jednoduchým projektem bude vypadat takto:

Řešení problému "Uncaught ReferenceError: exports is not defined" při kompilaci z TypeScriptu do JavaScriptu, obr. 3

Abychom dostali JavaScript verze, musíme každý *.ts soubor otevřít, udělat nějakou změnu a uložit. Po aktualizaci bude vypadat složka s projektem takto:

Řešení problému "Uncaught ReferenceError: exports is not defined" při kompilaci z TypeScriptu do JavaScriptu, obr. 4

Pokud nyní spustíte index.html a podíváte se do konzole, měla by obsahovat zadaný pozdrav.

Řešení problému "Uncaught ReferenceError: exports is not defined" při kompilaci z TypeScriptu do JavaScriptu, obr. 5

V čem je tedy problém?

Zcela zásadní problém je v konfiguračním souboru. Pokud si ho necháte vygenerovat přes NodeJS pomocí příkazu:

tsc --init

tak vám vygeneruje poměrně velký soubor s desítkami parametrů, které mohou nabývat řady hodnot. A i když je to v souboru přímo vysvětleno, výchozí podoba není pro práci s moduly vhodná.

Řešení problému "Uncaught ReferenceError: exports is not defined" při kompilaci z TypeScriptu do JavaScriptu, obr. 6

Pak začnete experimentovat s parametry, odkazy na soubory, měnit závorky a inspirovat se buď na internetu nebo se radit s Chat GPT. Všechno bude marné, a budete se neustále točit v kruhu, vždy skončíte s nějakou chybou. Chyby, se kterými jsem dlouho zápasil já:

  • Uncaught SyntaxError: Cannot use import statement outside a module
  • Uncaught ReferenceError: exports is not defined at main.js: 2:23
  • Uncaught SyntaxError: Unexpected token 'export'
  • Failed to load resource: the server responded with a status of 404 (Not Found)
  • Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.

⚠️ Co může být příčinami těchto chyb:

  1. Nevhodné hodnoty parametrů "module" (aktuálně 12 možností) a "target" (aktuálně 13 možností), především pokud máte "module": "CommonJS"
  2. Chybějící type="module" při odkazování na JavaScript soubory/moduly 
  3. Chybějící nebo špatná přípona v odkazu import { Ahoj } from './knihovna.js'; přestože se uvádí, že ji nemusíte uvádět a ve VS to opravdu funguje i bez přípony
  4. Přesunete modul do podsložky, která se nepřekládá
  5. Přesunete modul do jiné složky, ale v modulu zůstaly původní relativní odkazy na jiné moduly, takže se modul nepřeloží
  6. Změníte jen jeden *.ts soubor, takže se přeloží jen on a zbylé nikoliv
  7. Změníte tsconfig.json a myslíte si, že jeho uložením se všechny *.ts přeloží - nepřeloží se ani jeden, musí se každý nejprve změnit/uložit
  8. Neuvědomíte si, že prohlížeč může mít v mezipaměti předchozí verzi *.js souborů, takže se změny zdánlivě neprojevují
  9. Opisujete "funkční" příklady ze StackOverflow a jiných míst, které již mohou být zastaralé, vyžadují další komponenty či systémy nebo nikdy nefungovaly, jak dokazuje i řada komentářů
  10. Měníte parametry tsconfig.json, které nemají na výslednou podobu vliv

Závěr

V tuto chvíli nevím, zda uvedené parametry jsou nejideálnější, zda by nebylo vhodné použít třeba novější ES2022 apod. Pro mě je nejdůležitější, že jsem se dopracoval ke kombinaci parametrů, které generují JavaScript, který (v mých aplikacích) funguje.

Zdroje: