javascript - Importarea modulelor TypeScript & WebPack


Am unele probleme în a face ca WebPack să injecteze dependențele importate pentru un proiect scris în TypeScript. Prima mea problemă este să fac TypeScript să recunoască modulul importat.

Am un fișier header.ts care declară un modul care este cuibărit sub vi.input și exportă o clasă VIInputDirective. În fișierul main.ts, încerc să import clasa VIInputDirective exportată din fișierul header.ts, dar se pare că TypeScript nu o recunoaște.

header.ts

module vi.input.header {
  import IDirective = angular.IDirective;

  export class VIInputDirective implements IDirective {
    ...
  }
}

main.ts

import {VIInputDirective} from "./header.ts"; // Nothing imported; Cannot Resolve issue
VIInputDirective.whatever(); // Does not work

material.dt.s

declare module vi.input {
  ...
}

Dacă înlocuiesc import {VIInputDirective} from "./header.ts"; în fișierul main.ts cu import VIMHeaderDirective=vi.input.header.VIInputDirective; funcționează bine, dar apoi webpack la transpilare/injectare îmi dă următoarea eroare:

VM1469:1Uncaught ReferenceError: vi is not defined

Am încercat să export modulul vi.input.header direct (adică export module vi.input.header), dar asta nu a funcționat. De asemenea, am încercat să folosesc sintaxa de referință pentru a include fișierul, dar nici asta nu a funcționat: ///<reference path="file_path"/>.

Este o problemă legată de cuibărirea modulelor pentru că dacă elimin modulul și export direct clasa VIInputDirective, funcționează bine. Totuși, vreau să o păstrez într-un modul cuibărit.

Răspuns #1

Nu folosești module. Modulele au una sau mai multe instrucțiuni import sau export la nivel superior. În schimb, folosești spațiul de nume global și creezi subspații de nume ale spațiului de nume global pentru a-ți organiza programul. Aceasta se numește tiparul modulului revelator. Nu implică utilizarea modulelor.

Din păcate, TypeScript obișnuia să se refere la acest tipar ca folosind Module Interne. Această terminologie a fost de atunci desființată, iar utilizarea sintaxei module x {} (observați că lipsesc "-urile în jurul x) este puternic descurajată. Limbajul a introdus un cuvânt cheie sinonim namespace, pentru a reduce confuzia.

Încărcătoarele și împachetătoarele JavaScript precum Webpack, RequireJS și SystemJS lucrează cu module, adică ceea ce TypeScript numea module externe.

Pentru a clarifica, următoarele construcții pe care le menționați nu sunt legate de modul

  1. declarații sintactice de nivel superior, neexportate module/namespace

    module vi.input.header { ... }
    

    acest lucru ar fi acum scris ca

    namespace vi.input.header { ... }
    

    pentru a minimiza confuzia, dar, indiferent, emisia a rezultat întotdeauna în.

    var vi;
    (function (vi) {
        var input;
        (function (input) {
            var header;
            (function (header) {
            })(header = input.header || (input.header = {}));
        })(input = vi.input || (vi.input = {}));
    })(vi || (vi = {}));
    

    Rețineți că aceasta modifică domeniul global într-un tipar utilizat frecvent de diverse biblioteci. namespace-urile (cunoscut anterior ca module interne), precum cel de mai sus, au proprietatea interesantă că mai multe fișiere pot contribui la conținutul lor, iar acesta este, de fapt, scopul lor principal. Aceasta explică gradul de imbricare și atribuțiile condiționale către variabile din emit-ul de mai sus. Acest lucru nu are nicio legătură cu utilizarea Modulelor.

  2. import atribuiri care fac referire la membri ai namespace precum

    import IDirective = angular.IDirective;
    

    nu se califică drept declarații import de nivel superior și, prin urmare, nu fac ca fișierul care le conține să fie considerat un modul. Acest lucru este valabil chiar dacă sunt plasate la nivelul superior al unui fișier. Motivul este că sistemele de module, fie ele AMD, CommonJS, System sau ES2015, toate folosesc șiruri de caractere ca specificatori de module, importând din astfel de șiruri; care, incidental, pot reprezenta căi de fișiere, URL-uri, nume simple rezolvate sau ID-uri de module sintetice.

Din nou, declarațiile import name=qualified.global.name din codul tău sunt o caracteristică specifică TypeScript care nu are legătură cu Modulele. Ele pot fi destul de utile pentru a crea aliasuri pentru tipuri și valori imbricate, dar module nu creează.

Acum iată unde devine interesant și unde se intersectează cu întrebarea ta specifică: namespace-urile pot fi folosite și, uneori, destul de elegant, din module externe, dar semantica lor este foarte diferită

Considera

services.ts

export namespace app {
  export class SomeService { }
}

care se compilează în următorul JavaScript

export var app;
(function (app) {
    class SomeService {
    }
    app.SomeService = SomeService;
})(app || (app = {}));

main.ts

export namespace app {
  export function bootstrap() { }
}

care se compilează în următorul JavaScript

export var app;
(function (app) {
    function bootstrap() { }
    app.bootstrap = bootstrap;
})(app || (app = {}));

Ambele dintre cele de mai sus sunt module externe, adică module adevărate, care folosesc namespace-uri ca mecanisme interne de organizare a codului, dar concluzia esențială este că ele nu contribuie la un namespace app comun, fiecare având propria variabilă app cu scop de fișier. Niciunul nu are acces implicit la membrii celuilalt, declarațiile namespace app nu se combină între fișiere și faptul că au un sistem de denumire intern similar este întâmplător în raport cu modularitatea lor.

Deci, cum se leagă toate acestea de întrebarea ta și de sugestiile pe care ai încercat să le aplici?

Să vedem

headers.ts

module vi.input.header {
  import IDirective = angular.IDirective;

  export class VIInputDirective implements IDirective {
    static whatever() { }
  }
}

Acest fișier nu este un modul, așa cum s-a explicat mai sus, și folosește spațiul de nume global pentru a-și expune declarațiile. Dacă l-am scrie astăzi, am folosi cuvântul cheie namespace în loc de cuvântul cheie module, conform convenției.

main.ts

import {VIInputDirective} from "./header.ts"; // Nothing imported; Cannot Resolve issue
VIInputDirective.whatever(); // Does not work

De fapt, niciuna dintre linii nu funcționează deoarece importați headers.ts ca și cum ar fi un modul, dar, așa cum am văzut, nu este un modul. Mai mult, prima linie, care este o declarație import la nivel superior care importă dintr-un șir specificator de modul, ironic, face ca main.ts, el însuși, să fie un modul.

Pe scurt, cele două stiluri nu se amestecă bine, iar partajarea codului între ele nu este simplă și probabil nu ar trebui să încerci să o faci (am omis formatul UMD din acest răspuns pentru a încerca să îl păstrez relativ simplu).

Acum revenim la punctul de plecare

text puternic

declare module vi.input {
    ....
}

Dacă înlocuiesc import {VIInputDirective} from "./header.ts"; în fișierul main.ts cu import VIMHeaderDirective=vi.input.header.VIInputDirective; funcționează bine, dar atunci webpack la transpile/inject îmi dă următoarea eroare:

Într-adevăr, așa cum s-a explicat mai sus. Acest import, care nu țintește un șir specificator de modul, și lipsa altor importuri sau exporturi de nivel superior, schimbă main.ts astfel încât să nu mai fie un modul. Acest lucru face ca TypeScript să-l verifice corect, variabilele globale referindu-se unele pe altele folosind import=namespace.value fiind perfect legitim, dar acestea nu sunt module și instrumentele JavaScript, cum ar fi Webpack, operează pe module.

Așadar, cum ai scrie această aplicație în această nouă lume curajoasă? Deoarece folosești un instrument de împachetare a modulelor, Webpack, ai scrie-o folosind module corespunzătoare pe tot parcursul.

main.ts

import {VIInputDirective} from "./header.ts";
VIInputDirective.whatever();

headers.ts

import {IDirective} from 'angular';

export class VIInputDirective implements IDirective {
  static whatever() { }
}

material.d.ts nu mai arată așa cum arăta când ai scris asta, deoarece a fost actualizat pentru a funcționa cu module corespunzătoare. Dacă trebuie să faci referire la ceva din el, folosește sintaxa modulelor

optiuni-dialog-meu.ts

import {material} from 'angular';

const dialogOptions: material.IDialogOptions = { ... };

export default dialogOptions;

Am încercat să nu simplific excesiv, dar a fost necesar să fac anumite generalizări pentru a evita să scriu o nuvelă despre subiect, însă cred că sper că am acoperit și transmis punctele cheie.

Răspunsul #2

Cauți spații de nume, nu modul, așa că.

header.ts

export namespace vi.input.header {
  export class VIInputDirective {

  }
}

main.ts

import { vi } from "./header.ts"; 
var foo = new vi.input.header.VIInputDirective();