Moduler vs. navneområder: Hvad er den korrekte måde at organisere et stort TypeScript-projekt på?


Jeg er ret ny til TypeScript, og jeg skriver en lille prototype-ramme for WebGL. Jeg er i øjeblikket i gang med at refaktorere mit projekt og er stødt på nogle problemer med, hvordan jeg skal organisere mit projekt, da begge tilgange (moduler og navneområder) ser ud til at have alvorlige ulemper.

Dette indlæg handler ikke om, HVORDAN man bruger disse mønstre, men om hvordan man overvinder de problemer, som hvert af disse medfører.

Status quo: Brug af navneområder

Kommer man fra C# syntes dette at være den mest naturlige måde at gøre det på. Hver klasse/modul får sit passende namespace, og jeg leverer parameteren "outFile" i tsconfig.json, så alting bliver sammenflettet til én stor fil. Efter kompilering har jeg mit rodenamespace som et globalt objekt. Afhængigheder er ikke bygget ind i projektet, så man skal manuelt levere de nødvendige *.js-filer i sin html (ikke godt)

Eksempelfil

namespace Cross.Eye {
    export class SpriteFont {   
        //code omitted
    }    
}

Eksempel på brug (Du skal sørge for, at Cross-namespace er indlæst i det globale namespace på forhånd ved at tilføje js-filen i html'en)

namespace Examples {
    export class _01_BasicQuad {
        context: Cross.Eye.Context;
        shader: Cross.Eye.ShaderProgram;

        //code omitted
    }
}

Fordele

  • Ligetil at bruge, hvis du kommer fra C#/Java
  • Uafhængig af filnavne - omdøbning af filer vil ikke ødelægge din kode.
  • Nem at omstrukturere: IDE'er kan nemt omdøbe namespaces/klasser, og ændringerne vil blive anvendt konsekvent i hele din kode.
  • Bekvemmelighed: At tilføje en klasse til projektet er lige så enkelt som at tilføje en fil og erklære den i det ønskede namespace.

Ulemper

For de fleste projekter anbefaler vi at bruge eksterne moduler og bruge namespace til hurtige demoer og portering af gammel JavaScript-kode.

fra https://basarat.gitbooks.io/typescript/content/docs/project/namespaces.html

  • Et rodnavnerum er altid(?) et globalt objekt (dårligt)
  • Kan ikke(?) bruges med værktøjer som browserify eller webpack, hvilket er essentielt for at pakke biblioteket med dets afhængigheder eller pakke din egen kode med biblioteket, når det faktisk bruges.
  • Dårlig praksis, hvis du planlægger at udgive en npm-modul

State of the art (?): Moduler

Typescript understøtter ES6-moduler, de er nye og skinnende, og alle ser ud til at være enige om, at de er vejen frem. Ideen ser ud til at være, at hver fil er et modul, og ved at levere filerne i import-udsagn kan du definere dine afhængigheder meget eksplicit, hvilket gør det nemt for bundlingsværktøjer at pakke din kode effektivt. Jeg har for det meste én klasse per fil, hvilket ikke ser ud til at fungere så godt med modulmønstret.

Dette er min filstruktur efter refaktoreringen:

Jeg har også en index.ts-fil i hver mappe, så jeg kan importere alle dens klasser med import * as FolderModule from "./folder"

export * from "./AggregateLoader";
export * from "./ImageLoader";
export * from "./TiledLoader";
export * from "./XhrLoaders";
export * from "./XmlSpriteFontLoader";

Eksempelfil - Jeg tror problemet bliver tydeligt synligt her..

import {SpriteFont} from "./SpriteFont";
import {ISpriteTextGlyph, ISpriteChar} from "./Interfaces";
import {Event,EventArgs} from "../../Core";
import {Attribute, AttributeConfiguration} from "../Attributes";
import {DataType} from "../GlEnums";
import {VertexStore} from "../VertexStore";
import {IRectangle} from "../Geometry";
import {vec3} from "gl-matrix";

export class SpriteText {
    // code omitted
}

Eksempel på brug. Som du kan se, behøver jeg ikke længere gå gennem navnerummene, fordi jeg kan importere klasserne direkte.

import {
    Context,
    Shader,
    ShaderProgram,
    Attribute,
    AttributeConfiguration,
    VertexStore,
    ShaderType,
    VertexBuffer,
    PrimitiveType
} from "../cross/src/Eye";

import {
    Assets,
    TextLoader
} from "../cross/src/Load";

export class _01_BasicQuad {
    context: Context;
    shader: ShaderProgram;

    // code omitted.
}

Fordele

  • Gør din kode mere modulær, da den ikke længere er bundet til navnerum.
  • Du kan bruge bundlingsværktøjer som browserfy eller webpack, som også kan samle alle dine afhængigheder
  • Du er mere fleksibel, når du importerer klasser, og behøver ikke længere at gå gennem navneområdekæder.

Ulemper

  • Meget kedeligt, hvis hver klasse er en separat fil, skal du skrive de samme import-sætninger igen og igen.
  • Omdøbning af filer vil bryde din kode (dårligt).
  • Omdøbning af klassenavne vil ikke sprede sig til dine imports (meget dårligt - det kan dog afhænge af dit IDE, jeg bruger vs-code)

IMHO virker begge tilgange fejlbehæftede. Navnerum synes at være frygteligt forældede, upraktiske for store projekter og inkompatible med almindelige værktøjer, mens brugen af moduler er ret upraktisk og bryder nogle af de funktioner, jeg oprindeligt brugte TypeScript til.

I en perfekt verden ville jeg skrive mit framework ved hjælp af namespace-mønsteret og eksportere det som et modul, som derefter kan importeres og pakkes med dets afhængigheder. Dette ser dog ikke ud til at være muligt uden nogle grimme hacks.

Så her er mit spørgsmål: Hvordan har du håndteret disse problemer? Hvordan kan jeg minimere de ulemper, hver tilgang indebærer?

Opdatering

Efter at have fået lidt mere erfaring med TypeScript og JavaScript-udvikling generelt, må jeg påpege, at moduler sandsynligvis er vejen frem for 90 % af alle anvendelsestilfælde.

De sidste 10% er forhåbentlig legacy-projekter, som bruger globale namespaces, som du gerne vil krydre med lidt TypeScript (som faktisk fungerer rigtig godt).

Meget af min kritik af moduler kan (og er blevet) løst ved bedre IDE-understøttelse. Visual Studio Code har siden tilføjet automatisk modulopløsning, som fungerer rigtig godt.

Svar #1

tl;dr: Vælg ikke fortiden. Vælg fremtiden: Moduler.

I de tidlige udkast af ES6-modulspecifikationen var der et inline-moduler begreb, som derefter blev fjernet i september 2013. Dette begreb var dog allerede implementeret af TypeScript-teamet i 2012 med de første beta-versioner af sproget: det var interne moduler. Derefter blev den endelige specifikation for ES6-moduler udgivet i juli 2014 uden inline-moduler. Et år senere, i juli 2015, med version 1.5 af TypeScript, blev interne moduler omdøbt til navnerum for at undgå forvirring med standarden.

Navnerum er en gammel funktion. Det vil ikke være en del af sproget ECMAScript. Og TypeScript-teamet vil fortsat følge standarden. Der har ikke været nogen forbedring vedrørende TS-navnerum siden udgivelsen af ECMAScript-modulstandarden i juli 2014.

Ulemper [ved ES6-moduler]

  • Meget kedeligt, hvis hver klasse er en separat fil, skal du skrive de samme import-sætninger igen og igen.
  • Omdøbning af filer vil bryde din kode (dårligt).
  • Omdøbning af klassenavne vil ikke sprede sig til dine imports (meget dårligt - det kan dog afhænge af dit IDE, jeg bruger vs-code)

Vi kan håbe på nogle forbedringer af disse problemer med fremtidige IDE'er. Det første er allerede løst af WebStorm.

Relaterede artikler