npm i typescript -g
<script src="./app.js" type="text/javascript"></script>
// app.ts
// WE USE TYPE ASSIGNMENT WHEN WE DECLARE PARAMETERS
function somma(a: number, b: number) {
return a + b
};
console.log(somma(3, 5));
Basic data types:
- String
- Number
- Boolean
Custom data types:
- Array
let tsArray: [];
let tsArray2: array;
let tsArray3: string[];
let tsArray5: number[];
let tsArray6: any[];
- Object
let tsObject: {};
let tsObject2: object;
//๐ Type 'string' is not assignable to type 'object'๐
tsObject2 = 'mario'; // โ
// โ๏ธ OK
tsObject2 = {
name: "Mario",
age: 30,
isMarried: false,
address: {city: 'Roma', number: 30, street: 'Via Milano'}
};
// WE CAN DECLARE THE EXPECTED OBJECT STRUCTURE
let tsObject3: {
name: string,
age: number,
isMarried: boolean
};
// WE CAN NOT CHANGE VALUE TYPES
tsObject3 = {
name: "Mario",
//๐ Type 'string' is not assignable to type 'number' ๐
age: 'trenta', // โ
isMarried: false
};
// WE CAN NOT ADD UNDECLARED PROPERTIES
tsObject3 = {
name: "Mario",
age: 30,
isMarried: false,
//๐ Object literal may only specify known properties, and 'address' does not exist in type '{ name: string; age: number; isMarried: boolean; }'. ๐
address: {city: 'Roma', number: 30, street: 'Via Milano'} // โ
};
// WE CAN DECLARE AN EXPECTED OBJECT STRUCTURE ON A FUNCTION PARAMETER
function getData(data: {id: number; username: string; passwod: string}) {
// WE CAN ACCESS THE PROPERTIES OF THE PARAMETERS
console.log(`USER ID: ${data.id}`);
};
- Tuple (fixed length array)
// IT WILL EXPECT AN ARRAY CONTAINING ONLY 2 VALUES TYPE NUMBER
let tuple : [number, number];
//๐ Type 'string' is not assignable to type 'number'. ๐
tuple = ['carl', 2]; // โ
//๐ Type '[number, number, number]' is not assignable to type '[number, number]'. Source has 3 element(s) but target allows only 2. ๐
tuple = [1, 2, 3]; // โ
Advanced data types:
- Any
- Void
- Null
- Undefined
- Never
- Union (used to assign multyple data types)
//๐ Type 'boolean' is not assignable to type 'string | number'. ๐
let unionTs: string | number = 'a string'; // โ
let unionTs2: string | number | string[]= 'a string';
- Type Aliases (custom data types)
type Person = {
name: string,
age: number,
isMarried: boolean
address: {city: string, number: number, street: string}
};
let user: Person;
user = {
name: 'Mario',
age: 30,
isMarried: false,
address: {city: 'Roma', number: 30, street: 'Via Milano'}
};
type Test = string | number;
//๐ Type 'boolean' is not assignable to type 'Test'. ๐
let customTest: Test = true; // โ
// โ๏ธ
let customTest: Test = 'true';
// WE CAN DECLARE THE EXPECTED TYPE ON A FUNCTION PARAMETER
function getPerson(user: Person) {
// WE CAN ACCESS THE PROPERTIES OF THE PARAMETERS
console.log(`USER ID: ${user.name}`);
};
- Enum
// WE CAN DECLARE THE EXPECTED VALUES LIST THANKS TO ENUM
enum Role {
ADMIN,
USER,
GUEST
};
let enumUser = {
name: 'Mario',
age: 30,
role: Role.ADMIN
}
// EXAMPLE OF USE
type User = {
name: string,
age: number,
isMarried: boolean,
address: {city: string, number: number, street: string},
role: Role
};
let testUser1: User = {
name: 'Mario',
age: 30,
isMarried: false,
address: { city: 'Roma', number: 30, street: 'Via Milano'
},
role: Role.ADMIN
}
// ENUM ASSIGNS AN INDEX ON EACH ENTRY. WE CAN EDIT THIS INDEX AND ASSIGN IT TO A NEW NUMERIC VALUE OR STRING:
enum Role {
ADMIN = 10,
USER, // THIS WILL BE (enum member) Role.USER = 11
GUEST = 'guest' // (enum member) Role.GUEST = "guest"
};
// IF WE ASSIGN A DEFAULT VALUE TO A PARAMETER, TS INFERENCE WILL ASSIGN THE TYPE OF THAT VALUE TO THE PARAMETER. (parameter) b: number
function somma2(a: number, b = 3) {
return a + b
};
IF WE CHECK somma2 WE WILL SEE THAT TS EXPECTS A NUMBER, A SECOND NUMBER (OPTIONAL AS IT AS A DEFAULT) AND WILL RETURN A NUMBER AS RESULT
function somma2(a: number, b?: number): number
WE CAN ALSO DECLARE A TYPE OF THE RETURN VALUE IF NEEDED
function tenCheck(a: number, b = 3): any {
if ((a+b) > 10 ) {
return 'sum is > 10'
} else {
return 'sum is <= 10'
}
};
THE BEST WAY TO WRITE THIS FUNCTION SHOULD BE
function somma3(a: number, b: number = 3): string {
if ((a + b) > 10) {
return 'La somma รจ > 10';
} else {
return 'La somma รจ <= 10';
}
}
IF WE WILL NOT RETURN A VALUE, WE CAN DECLARE A RETURN TYPE OF VOID
function somma3(a: number, b = 3): void {
console.log(a+b);
};
THIS WAY IF WE DECLARE A RETURN NOT VOID TS WILL WARN US
function somma3(a: number, b = 3): void {
//๐ Type 'number' is not assignable to type 'void'. ๐
return a + b // โ
}
IF WE WANT TO ASSIGN A FUNCTION TO A VARIABLE, WE CAN ASSIGN THE TYPE FUNCTION AS WE DECLARE THE VARIABLE:
function somma3(a: number, b = 3): void {
console.log(a+b);
};
let sommaVar: Function = somma3
WHEN ASSIGNING FUNCTIONS TO VARIABLES WE CAN SPECIFY WHAT KIND OF PARAMETERS AND RETURN WE ACCEPT
let sommaVar: (a: number, b: number) => number;
THIS WAY WE WILL GET NOTIFIED OF AN ERROR IF WE ASSIGN A FUNCTION WITH DIFFERENT PARAMETERS OR RETURN VALUES. IF WE ASSIGN TO sommaVar A FUNCTION WITH A RETURN OF VOID FOR EXAMPLE, WE WILL GET:
Type '(a: number, b?: number) => void' is not assignable to type '(a: number, b: any, number: any) => number'.
Type 'void' is not assignable to type 'number'.
tsc app.ts
This will create the app.js file:
// app.js
// code
function somma(a, b) {
return a + b;
}
;
console.log(somma(3, 5));
// ecc
WE CAN USE
tsc app.ts --watch
OR
tsc app.ts -w
TO AUTOMATICALLY WATCH OUR .ts FILE FOR CHANGES AND UPDATE OUR .js FILE
[15:15:17] Starting compilation in watch mode...
[15:15:17] Found 0 errors. Watching for file changes.
IF WE USE
tsc --init
TS WILL CREATE A .json FILE AND THE COMPILER WILL AUTOMATICALLY MANAGE AND COMPILE .ts FILES
Created a new tsconfig.json with:
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
You can learn more at https://aka.ms/tsconfig
NOW AFTER USING --init WE CAN WATCH ALL FILES
tsc -w
WE CAN ADD AT THE END OF tsconfig.json AN ARRAY OF FILES/DIRECTORIES TO EXLUDE FROM OUR WATCH:
// [.json file]
},
"exclude": [
"node_modules",
"./file-to-exclude.ts"
]
}
WE SHOULD DECLARE TO EXCLUDE node_modules IF WE USE "exclude" IN tsconfig.json.
IF WE WANT TO CHECK OUR SOURCE TYPESCRRIPT FILES WITH THE BROWSER DEVTOOLS WE HAVE TO ENABLE "sourceMap" ON tsconfig.json
/* Create source map files for emitted JavaScript files. */
"sourceMap": true,
WE CAN SPECIFY A FOLDER CONTAINING OUR TS SOURCE FILES IN tsconfig.json:
/* Specify the root folder within your source files. */
"rootDir": "./src",
AND A FOLDER FOR OUR COMPILED JS FILES
/* Specify an output folder for all emitted files. */
"outDir": "./dist",
REMEMBER TO UPDATE THE SCRIPT PATH:
<script src="./dist/app.js" type="text/javascript"></script>
CLASSES WORK ALMOST THE SAME
class Persona {
private nome: string;
private cognome: string;
constructor(nome: string, cognome: string) {
this.nome = nome;
this.cognome = cognome
};
getFullName(): string {
return `${this.nome} ${this.cognome}`
};
greet(): void {
console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
};
welcome(persona: Persona): string {
return `Benvenuto ${persona.nome} ${persona.cognome}!`
};
changeSurname(surname: string) {
this.cognome = surname;
};
};
let mario: Persona;
mario = new Persona("Mario", "Rossi");
const luca = new Persona("Luca", "Gialli");
// full name: Luca Gialli
console.log('full name:', luca.getFullName());
// Ciao, sono Mario Rossi
mario.greet();
// Benvenuto Mario Rossi!
console.log(
luca.welcome(mario)
);
WE CAN DECLARE A PROPERTY AS PRIVATE SO WE CAN'T REASSIGN IT WITHOUT A METHOD
class Persona {
private nome: string;
private cognome: string;
// ...
changeSurname(surname: string) {
this.cognome = surname;
};
};
TS CAN AUTOMATICALLY DETECT CONSTRUCTOR PROPERTIES WHEN DECLARED
class Persona {
constructor(private nome: string, private cognome: string) {}
getFullName(): string {
return `${this.nome} ${this.cognome}`
};
greet(): void {
console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
};
welcome(persona: Persona): string {
return `Benvenuto ${persona.nome} ${persona.cognome}!`
};
};
(WE CAN ADD SOME ERROR MANAGEMENT INSIDE THE MUSTACHE BRACKETS CONTRUCTOR)
class Persona {
constructor(private nome: string, private cognome: string) {
if (!nome || !cognome) {
throw new Error("Nome e cognome devono essere forniti.");
}
};
getFullName(): string {
return `${this.nome} ${this.cognome}`
};
greet(): void {
console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
};
welcome(persona: Persona): string {
return `Benvenuto ${persona.nome} ${persona.cognome}!`
};
};
WE CAN DECLARE A PROPERTY AS READ ONLY SO IT WE CAN ONLY READ BUT NOT CHANGE IT:
class Persona {
constructor(private readonly nome: string, private cognome: string) {
};
// ...
};
class Persona {
static nazione: string = "Italia";
constructor(private nome: string, private cognome: string) {
};
// GETTER
getFullName(): string {
return `${this.nome} ${this.cognome}`
};
greet(): void {
console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
};
welcome(persona: Persona): string {
return `Benvenuto ${persona.nome} ${persona.cognome}!`
};
static getNazione(): string {
return Persona.nazione;
};
};
STATIC PROPERTIES CAN BE ACCESSED FROM THE CLASS ITSELF OR USING A GETTER METHOD:
console.log("Nazione di persona:", Persona.nazione);
// Nazione di persona: Italia
let mario: Persona
mario = new Persona("Mario", "Rossi");
console.log("Nazione di Mario:", mario.getNation());
// Nazione di Mario: Italia
PRIVATE PROPERTIES CAN BE ACCESSED ONLY ON THE CLASS IN WICH THEY ARE DECLARED, SO IF WE WANT TO ACCESS THE PARENT PROPERTIES nome AND cognome WE SHOULD SET THEM TO PROTECTED. A PROTECTED PROPERTY CAN BE ACCESSED BY THE CLASS AND HER CHILD CLASSES BUT NOT EDITED WITHOUT USING A SETTER METHOD.
class Persona {
constructor(protected readonly nome: string, protected cognome: string) { };
// ...
};
WE DECLARE nome AND cognome AS SUPER PROPERTIES TO ACCESS THE PARENT CONSTRUCTOR
class Studente extends Persona {
constructor(
nome: string,
cognome: string,
private matricola: number
) {
super(nome, cognome);
};
getStudentData(): string {
return `Nome: ${this.nome}, Cognome: ${this.cognome} - Matr: ${this.matricola}`;
};
// SETTER
changeEnrCode(code: number) {
this.matricola = code;
};
};
let marco = new Studente('Marco', 'Verdi', 0o1234);
// Nome: Marco, Cognome: Verdi - Matr: 668
WE CAN DECLARE AN ABSTRACT CLASS TO USE AS TEMPLATE. WE COULD USE Persona AS ABSTRACT CLASS AS IN OUR APP EVERY PERSON WILL HAVE A ROLE AND WE WILL NOT HAVE THE NEED TO GENERATE A Persona INSTANCE
abstract class Persona {
constructor(protected readonly nome: string, protected cognome: string) { };
// ...
};
THIS WAY WE CAN NOT CREATE A NEW Persona
// ๐ Cannot create an instance of an abstract class. ๐
const luca = new Persona("Luca", "Gialli"); // โ
BUT WE CAN CREATE A NEW Studente (WICH EXTENDS Persona):
// โ๏ธ
const marco = new Studente('Marco', 'Verdi', 0o1234);
WE CAN ALSO DECLARE ABSTRACT METHODS IN ABSTRACT CLASSES. WHEN DECLARED AN ABSTRACT METHOD DOES NOT NEED LOGIC, BUT ONLY DECLARATION:
abstract class Persona {
constructor(protected readonly nome: string, protected cognome: string) { };
// ...
abstract greet(): void;
};
BUT EACH CHILD CLASSES NEED THE LOGIC OF THE DECLARED METHOD, WICH CAN CHANGE FROM CLASS TO CLASS:
class Studente extends Persona {
constructor(
nome: string,
cognome: string,
private matricola: number
) {
super(nome, cognome);
};
// ...
// ๐
greet(): void {
console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
};
};
IF AN ABSTRACT CLASS HAS AN ABSTRACT METHOD EACH CHILD MUST HAVE IT'S LOGIC.
SINGLETON ARE USED TO GET AN UNIQUE HARDCODED INSTANCE OF A CLASS.
class Preside {
private static instance: Preside;
constructor(private nome: string, private cognome: string) { };
static getInstance() {
// IF THE INSTANCE IS ALREADY DEFINED, RETURN IT
if (Preside.instance) {
return this.instance;
}
// IF NOT, CREATE A NEW INSTANCE WITH HARDCODED DATA
this.instance = new Preside('Giovanni', 'Blu');
return this.instance;
};
greet() {
console.log(`Sono il Preside ${this.nome}, ${this.cognome}`);
};
};
Preside.getInstance().greet();
INTERFACES ARE USED TO DEFINE THE ABSTRACTION OF BEHAVIOURS OF METHODS OR PROPERTIES A CLASS CAN IMPLEMENT, WITHOUT HAVING TO EXTEND ANOTHER CLASS. EXAMPLE: A SMARTPHONE IS A DEVICE WICH HAS INTERNET CONNECTION, A TELPHONE IS A DEVICE WITHOUT INTERNET CONNECTION.
abstract class Device {
constructor(protected nome: string, protected year: number) {};
abstract turnOn(): void;
abstract turnOff(): void;
getYear() {
console.log(`Questo ${this.nome} รจ stato prodotto nel ${this.year}`);
}
};
interface internetConnection {
ip: string;
connect(): void;
disconnect(): void;
};
class Smarphone extends Device implements internetConnection {
ip: string;
constructor(nome: string, year: number, ip: string) {
super(nome, year);
this.ip = ip;
}
connect(): void {
console.log(`${this.nome} si รจ connesso all'indirizzo IP ${this.ip}`);
}
disconnect(): void {
console.log(`${this.nome} si รจ disconnesso dall'indirizzo IP ${this.ip}`);
}
turnOn(): void {
console.log(`${this.nome} si accende`);
}
turnOff(): void {
console.log(`${this.nome} si spegne`);
}
}
let iphone = new Smarphone('iPhone', 2019, '192.158.1.38');
// Questo iPhone รจ stato prodotto nel 2019
iphone.getYear();
// iPhone si accende
iphone.turnOn();
// iPhone si รจ connesso all'indirizzo IP 192.158.1.38
iphone.connect();
// iPhone si รจ disconnesso dall'indirizzo IP 192.158.1.38
iphone.disconnect();
// iPhone si spegne
iphone.turnOff();
AN INTERFACE CAN EXTEND ONE OR MORE INTERFACES:
interface connectionType extends internetConnection {
connectType: string;
};
class Computer extends Device implements connectionType {
connectType: string;
ip: string;
constructor(nome: string, year: number, ip: string, connectionType: string) {
super(nome, year);
this.connectType = connectionType;
this.ip = ip;
}
connect(): void {
console.log(`${this.nome} si รจ connesso all'indirizzo IP ${this.ip}`);
}
disconnect(): void {
console.log(`${this.nome} si รจ disconnesso dall'indirizzo IP ${this.ip}`);
}
turnOn(): void {
console.log(`${this.nome} si accende`);
}
turnOff(): void {
console.log(`${this.nome} si spegne`);
}
};
let macBook = new Computer('Macbook', 2017, '192.158.1.38', 'WiFi');
// WiFi
console.log(macBook.connectType);
GENERICS ARE TYPES THAT CAN BE USED TO
- DEFINE TYPES FOR FUNCTION PARAMETERS
- DEFINE TYPES FOR FUNCTION RETURN
WE CAN DECLARE THE DESIRED TYPE OF VALUES
const arr: Array<string> = ['string1', 'string2'];
T IS A BUILT-IN TYPE THAT DEFINES A GENERIC TYPE THIS WAY IT WILL GENERATE A GENERIC ARRAY OF THE SAME TYPE
function newArray<T>(items: T[]) {
return new Array().concat(items);
};
THIS WAY IT WILL TAKE VIA INFERENCE NUMBER AS TYPE
// function newArray<number>(items: number[]): any[]
const arr1 = newArray([1,2,3]);
THIS WAY IT WILL TAKE VIA INFERENCE STRING AS TYPE
// function newArray<string>(items: string[]): any[]
const arr2 = newArray(['a','b','c']);
THIS WAY IT WILL TAKE VIA INFERENCE STRING OR NUMBER UNION AS TYPE
// function newArray<string | number>(items: (string | number)[]): any[]
const arr3 = newArray(['a','b',3]);
THIS WAY THIS WAY IT WILL ACCEPT ONLY NUMBERS AS PARAMETERS
const arr4 = newArray<number>([1,2,3]);
WE CAN ALSO DECLARE WE WILL PASS MULTIPLE TYPES AS UNION
const arr5 = newArray<number | string | boolean>([1,'a',true]);
WE CAN CONSTRAIN T TO BE A SPECIFIC TYPE
function newArray<T extends number>(items: T[]): T[] {
return new Array().concat(items);
};
function newArray<T extends number | string>(items: T[]): T[] {
return new Array().concat(items);
};
WE CAN DECLARE A CLASS THAT ACCEPT GENERIC VALUES THAT WILL BE TYPIZED BY INFERENCE
class List<T> {
// IT HAS A GENERIC ARRAY HAS PRIVATE PROPERTY
private list: T[] = [];
addItem(item: T): void {
this.list.push(item);
};
removeItem(item: T): void {
this.list.splice(this.list.indexOf(item, 1));
};
};
// AS WE CREATE A NEW ISTANCE, ALL TYPES WILL BE CHANGED BY INFERENCE
const list = new List<number>();
//๐ IT WILL ACCEPT ONLY NUMBERS AS ARGUMENTS NOW ๐
// (method) List<number>.addItem(item: number): void
list.addItem(1);
//๐ Argument of type 'string' is not assignable to parameter of type 'number'. ๐
list.addItem('a'); // โ
TO SUMMARIZE, HERE ASSIGNS TYPE number TO T:
const list = new List<number>();
DECORATORS MUST BE ENABLED IN tsconfig.json
/* Enable experimental support for legacy experimental decorators. */
"experimentalDecorators": true,
A DECORATOR FACTORY IS A FUNCTION THAT RETURN A DECORATOR. WE DEFINE THE DESIDERED PARAMETERS IN OUR FUNCTION AND WE RETURN A DECORATOR THAT USES THE PARAMETERS AND THE CLASS PROPERTIES.
//๐ FUNCTION THAT RETURNS A DECORATOR FACTORY ๐
function createHtmlElement(template: string, id: string, text: string) {
//๐ FUNCTION THAT RETURNS A DECORATOR ๐
return function (constructor: any) {
//๐ CREATES THE HTML ELEMENT ๐
const container = document.getElementById(id);
const element = document.createElement(template);
//๐ CREATES THE CONTENT USING THE CLASS CONSTRUCTOR ๐
const content = new constructor(text);
if (container) {
container.appendChild(element);
element.textContent = content.text;
}
};
};
//๐ USES THE DECORATOR FACTORY ๐
@createHtmlElement('h1', 'container', 'DECORATORS TEST')
class DecoTextH1 {
constructor(public text: string) { }
};
@createHtmlElement('p', 'container', 'DECORATORS TEST')
class DecoTextP {
constructor(public text: string) { }
};
WE CAN USE DECORATOR WHEN WE ACCESS A CLASS PROPERTY:
function propertyDecorator(target: any, property: string) {
console.log("Property decorator executed. Property is:", property, " Target Class is:", target);
}
function parameterDecorator(target: any, methodName: string, parameterIndex: number) {
console.log(`Parameter decorator executed. Target: ${target}, Method: ${methodName}, Parameter Index: ${parameterIndex}`);
}
class MyClass {
@propertyDecorator
myProperty: string;
constructor(public myProperty: string) {
this.myProperty = myProperty;
}
}
// OUTPUT:
// Property decorator executed. Property is: myProperty Target Class is:
// {
// constructor: class MyClass
// length: 1
// name: "MyClass"
// ECC
// }
WE CAN APPLY DECORATOR TO METHODS PARAMETERS OF A CLASS:
function parameterDecorator(target: any, methodName: string, parameterIndex: number) {
console.log(`Parameter decorator executed. Target: ${target}, Method: ${methodName}, Parameter Index: ${parameterIndex}`);
}
class MyClass {
@propertyDecorator
myProperty: string;
constructor(myProperty: string) {
this.myProperty = myProperty;
}
//
getProperty(@parameterDecorator property: string) {
property = this.myProperty;
console.log(`Property: ${property}`);
}
}
WE ADD TYPE MODULE ON THE SCRIPT TAG IN INDEX.HTML
<script src="./dist/app.js" type="module"></script>
WE MAKE SURE THAT IN tsconfig.json, UNDER compilerOptions THAT "module" AND target USE THE RIGHT VERSION OF ES
"target": "es2016",
"module": "ES6",
WE CREATE A SEPARATE FILE AND WE USE THE COMMAND export TO DECLARE THE DATA WE NEED
// utils.ts
export function logSuccess():void {
console.log('successful import!');
};
export function logSum(a: number, b: number):void {
console.log('Sum is ',a + b);
};
export function multiplication(a: number, b: number): number {
return a * b;
};
WE IMPORT THE FUNCTIONS/DATA FROM THE FILE
// USE.js EXTENSION OR THE COMPILED FILE WILL NOT FIND THE FILE IMPORT
import { logSuccess, logSum, multiplication } from './utils.js';
logSuccess();
logSum(5, 2);
console.log(`Your operation result is ${multiplication(5, 2)}`);
WE CAN ALSO EXPORT AS AN OBJECT
function logSuccess():void {
console.log('successful import!');
};
function logSum(a: number, b: number):void {
console.log('Sum is ',a + b);
};
function multiplication(a: number, b: number): number {
return a * b;
};
export { logSuccess, logSum, multiplication };
https://www.typescripttutorial.net/typescript-tutorial/nodejs-typescript/
Create two sub-directories called build and src Youโll store the TypeScript code in the src directory. Once the TypeScript compiler compiles the source TypeScript files, it will store the output files in the build directory.
Create the tsconfig.json file:
tsc --init
When you compile the TypeScript files, the TypeScript compiler will use the options in the tsconfig.json to compile the project.
Now, you can open the tsconfig.json file. There are many options. In this tutorial, youโll focus on these two options:
rootdir โ specifies the root directory of the TypeScript input files. outdir -stores the JavaScript output files.
These options are commented by default. And youโll need to uncomment ( remove the // at the beginning of the line) and change them as follows:
For the outDir option:
"outDir": "./build"
And for the rootDir option:
"rootDir": "./src"
The nodemon module allows you to automatically restart the application when you change the JavaScript source code.
The concurrently module runs multiple commands concurrently.
First, execute the npm init command from the root directory of the project:
npm init --yes
Next, install the nodemon and concurrently module:
npm install --g nodemon concurrently
Note that the -g flag will instruct npm to install these two modules globally. This allows you to use them in other projects.
Then, open the package.json file, and youโll something like this in the scripts option:
// ...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
// ...
After that, change the scripts option to the following:
// ...
"scripts": {
"start:build": "tsc -w",
"start:run": "nodemon build/app.js",
"start": "concurrently npm:start:*"
},
// ...
This "start:build": "tsc -w" will watch for changes in the ./src directory and compile them automatically.
This "start:run": "nodemon build/app.js" will automatically run the app.js in the ./build directory whenever the new file is generated.
This "start": "concurrently npm:start:"* runs all the commands that start with npm:start:*, which executes both start:build and start:run commands above.
Since the app.js will be the entry point for the Node.js program, you also need to change the following option in the package.json file to app.js:
From:
"main": "index.js"
To:
"main": "app.js"
Finally, execute the following command:
npm start
To verify the configuration, you change some code in the app.ts. And youโll see the output in the console.