node.js - Cum să folosești corect modulele și interfețele în TypeScript cu destinația NodeJS?
Echipa mea și cu mine suntem noi în TypeScript și NodeJS, și avem câteva probleme serioase să punem acest proiect în practică. Este destinat să fie o conductă de module pentru activele unui joc, scrisă în TypeScript pentru utilizare cu NodeJS (nu am fost implicat în luarea deciziilor aici, și discuția este încă într-un fel de desfășurare, dar avem totuși nevoie de un prototip).
Avem deja o idee clară despre arhitectură, dar e cam greu să facem prototipul să funcționeze, pentru că pur și simplu nu reușesc să înțeleg cum funcționează modulele în combinație cu interfețele și dependențele externe. Unele dintre componentele noastre trebuie să folosească și NodeJS sau alte module de bibliotecă (de exemplu NodeJS.fs pentru citirea/scrierea fișierelor), și am prefera să putem folosi ///reference pentru a adăuga fișierele d.ts corespunzătoare pentru a menține siguranța tipurilor în timpul dezvoltării.
Un exemplu al presupusei structuri a proiectului este:
/pipeline
/log
- ILog.d.ts
- StdOutLog.ts
- FileLog.ts
- ...
/reader
- IReader.d.ts
- TextReader.ts
- ...
/parser
- IParser.d.ts
- XmlParser.ts
- JsonParser.ts
- ...
/task
- ITask.d.ts
- JsTask.ts
- CliTask.ts
- ...
/...
Modul în care dorim să îl folosim este să oferim implementări implicite pentru interfețele noastre (pentru a acoperi elementele de bază, de exemplu, rularea unei comenzi în consolă, logarea într-un fișier sau într-un flux, citirea configurațiilor JSON și XML, ...), precum și interfețele în sine, astfel încât alte echipe să își poată crea propriile extensii (de exemplu, class GitTask extends CliTask pentru a încadra operațiunile cu repository-uri, sau class JpgReader implements IReader).
Pe partea apelantă, într-un alt proiect/aplicație runner, ar trebui să funcționeze astfel:
import pipeline = require('pipeline');
//...
var log = new pipeline.log.FileLog();
log.info("filelog started");
var parser = new pipeline.parser.XmlParser();
parser.parse(somexmldata);
log.info("parsing XML");
// ...
Este foarte probabil să fac greșit (tm), dar simt că nu este ușor posibil să facem cu TypeScript ceea ce vrem să facem, mai ales având în vedere că, de exemplu, definiția pentru componenta log poate avea cu ușurință mai multe interfețe și, de asemenea, enumerări (factory, logger, logitem, logtarget, loglevel). Din câte înțeleg, modulele NodeJS trebuie să fie un singur fișier JS, dar folosind chiar și un singur import transformă modulul tău într-un modul extern și acestea nu se vor mai compila într-un singur fișier, așa că mi se pare un obstacol destul de greu de trecut.
Există o modalitate de a realiza acea structură și utilizare intenționată cu TypeScript pentru NodeJS? Dacă da, cum ar trebui să arate fișierele (în special în ceea ce privește ierarhia modulelor și FQN-urile componentelor, de exemplu pipeline.log.X, pipeline.task.Y etc.; cum aș folosi corect module și export pentru asta)? Dacă nu, care ar fi alternativele potrivite pentru a ne atinge obiectivul?
[Actualizare] Am refactorizat prototipul meu conform sugestiei lui basarat și arată deja mult mai bine decât înainte. Totuși, am întâmpinat o eroare de compilare atunci când folosesc o clasă din alta:
error TS2095: Could not find symbol 'LogItem'.
LogItem este o clasă definită în /log și este folosită de StdOutLog.ts din același folder. IntelliJ îmi afișează mai multe definiții pentru aceasta (evident, una pentru clasa LogItem în sine și cealaltă în fișierul index din /log unde este exportată). Am încercat să folosesc notația modulului log.LogItem, dar nu a funcționat. Adăugarea tuturor fișierelor cu implementări implicite în fișierul de declarații globale nu a funcționat nici ea. [/Update]
[Update2] Iată mai mult cod în jurul locului unde apare eroarea. Nu primesc niciun fel de erori/marcaje în IntelliJ, doar când rulez task-ul grunt-ts.
// src/log/LogItem.ts:
///<reference path='../pipeline.d.ts'/>
class LogItem implements ILogItem {
// ...
constructor(level:LogLevel, message:string) {
// ...
}
public static create(level:LogLevel, message:string):ILogItem {
return new LogItem(level, message);
}
// ...
}
export = LogItem;
// src/log/Log.ts:
///<reference path='../pipeline.d.ts'/>
class Log implements ILog {
// ...
private getLogItem(level:LogLevel, message:string):ILogItem {
return LogItem.create(level, message); // <-- that's where I get the "symbol not found" error
}
// ...
}
export = Log;
// src/pipeline.d.ts:
///<reference path="../typings/node/node.d.ts" />
//grunt-start
/// <reference path="pipeline.ts" />
/// <reference path="log/Log.ts" />
/// <reference path="log/LogFactory.ts" />
/// <reference path="log/LogItem.ts" />
/// <reference path="log/StdLogEmitter.ts" />
/// <reference path="log/log.d.ts" />
/// <reference path="log/log.ts" />
/// <reference path="parser/JsonParser.ts" />
/// <reference path="parser/parser.d.ts" />
/// <reference path="parser/parser.ts" />
//grunt-end
[/Update2]
Răspuns #1
Referențiați toate fișierele voastre .d.ts într-un fișier global globals.d.ts care are tag-uri ///<reference către toate referințele individuale + fișierele vendor.d.ts (de ex. node.d.ts).
/pipeline
/globals.d.ts
/log
- ILog.d.ts
- StdOutLog.ts
- FileLog.ts
- ...
/reader
- IReader.d.ts
- TextReader.ts
- ...
/parser
- IParser.d.ts
- XmlParser.ts
- JsonParser.ts
- ...
/task
- ITask.d.ts
- JsTask.ts
- CliTask.ts
- ...
/...
Acest lucru te împiedică să faci constant referire la fișierele .d.ts.
Acum fiecare fișier TypeScript va exporta o anumită clasă, de exemplu XmlParser.ts:
/// <reference path='../globals.d.ts'/>
class XMLParser implements IParser{
}
export = XMLParser;
Fiecare folder are, de asemenea, un index.ts care importă și exportă toate clasele din acel folder, stil modul extern:
export import XmlParser = require('./xmlParser');
// so on
La fel pentru nivelul de deasupra (pipeline/index.ts):
export import parser = require('./parser/index');
Acum, dacă importați pipeline/index.ts, codul dumneavoastră va funcționa așa cum vă așteptați :
import pipeline = require('./pipeline/index');
var parser = new pipeline.parser.XmlParser();
parser.parse(somexmldata);
log.info("parsing XML");
Notă: Grunt-ts poate crea aceste declarații import/export pentru tine, astfel încât să nu trebuiască să te îngrijorezi de căile fișierelor: https://github.com/grunt-ts/grunt-ts/issues/85
