Category Archives: TypeScript

Trying out bun

You can never get too complacent with the JavaScript eco-system, no sooner do you start to feel comfortable than something else becomes the new hotness, this time it’s bun. Bun is a JavaScript runtime, pretty much analogous to Node.js, so basically a potential replacement for Node.js.

Setting things up

The Installation page gives details on setting up for various OS’s, Windows is not well supported at the moment (i.e. experimental release), but as I love devcontainers in VSCode, we’ll just set up a devcontainer to use the docker image.

  • Create a folder for your (yes we’re going to do it) hello world app, mine’s named hello-world
  • Create folder named .devcontainer (for more info. see my post Visual Code with vscontainers)
  • Create a file in .devcontainer named devcontainer.json and put the following code in it
    {
      "image": "oven/bun",
      "forwardPorts": [3000]
    }
    

Now open Visual Code on the folder hello-world (or whatever you named it) and VS Code will hopefully ask if you want to open the folder as a devcontainer, obviously say yes and it’ll set up the docker image for you and then you’ll be working in a devcontainer with bun.

Is it working?

If you carried the steps above (or installed by one of the other means) we now want to check bun is working. From your terminal either on the devcontainer or any terminal if you installed it on your machine or container, run the command

bun

You should see a list of commands etc. If not, check through your installation and in the case of the devcontainer make sure you’re using the terminal in VS Code and it’s showing your root folder of the docker image.

Getting Started

Hopefully everything is running, so we need to create something, so run

bun init

Then fill in the options, mine are using all the defaults, but I’ll then them below anyway

  • package name hello-world
  • entry point index.ts

and that’s all there’s is so it. Bun will create and index.ts file, .gitignore, tsconfig.json, package.json and READEME.md. The index.ts looks like this

console.log("Hello via Bun!");

Let’s run this using

bun run index.ts

As you’d expect we’re seeing Hello via Bun! in our terminal window.

I want a server!

Most, if not all my work with Node.js was writing server based code, so let’s take the example from the Bun Quick Start page and fire up a little server app.

const server = Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello World from the server");
  },
});

console.log(`Listening on http://localhost:${server.port} ...`);

We’re using port 3000 which we also have listed in our devcontainer.json within forwardPorts. So running index.ts from bun will start the server on port 3000 and VS Code (in my case) will ask if I wish to open the browser for that port. If you’re using a different method then simply open your browser with URL http://localhost:3000/.

We can ofcourse put scripts into our package.json, to save us some typing, for example adding

"scripts": {
    "start": "bun run index.ts"
  },

Now we can use the command

bun run start

Everything else should pretty much work as Node.js, but it’s reported that bun (which essentially also includes package management tooling) is quite a bit faster than npm and yarn, also it includes hot reload out of the box, i.e. we don’t need to use Nodemon. It automatically transpiles TypeScript and JSX. So it’s very much an “all in one” solution to Node.js style development.

So that’s a really quick run through and a reminder to myself how to get bun up and running as quickly as possible.

useState and useEffect in React

Since React’s move to using hooks instead of classes, two of the primary hooks we need to get used to using are useState and useEffect.

Note: I’m using Typescript for my React apps. so code listed will have types etc. but if you’re from a pure Javascript background it should still be pretty obvious what’s going on.

useState

So, in the time before hooks, we might use classes. We’d pass props into the constructor of the class and set state within the class. Hence, we might end up with something like this

interface IViewerProps {
   startAt: number;
}

interface IViewerState {
    counter: number;
}

export class Viewer extends React.Component<IViewerProps, IViewerState> {
   constructor(props: IViewerProps) {
      super(props);
         this.state = {
            counter:  props.startAt
         }

      this.onClick = this.onClick.bind(this);
   }

   onClick(): void {
      this.setState({
         counter: this.state.counter + 1
      });
   }

   render() {
      return (
         <div>
            <div>{this.state.counter}</div>
            <button onClick={this.onClick}>Click Me</button>
         </div>
      );
   };
}

In this instance we’d pass a startAt value via the props, assign to the internal state then update this internal state.

Functional Components

Now, the move to functional based components ofcourse would lose the ability to maintain state across function calls, unless we had some way to associate it with that function call. Whereas a class would handle this initialization within its constructor. In the case of a functional component, we need a way where subsequent calls to that function cannot reinitialize the state or – in the case of out button counter example, the state would simply be reset to the one supplied by the props each render.

Let’s look at recreating the class above but as a functional component.

export function Viewer(props: IViewerProps) {
   const [state, setState] = useState(props.startAt);

   function onClick(): void {
      setState(state + 1);
   }

   return (
      <div>
         <div>{state}</div>
            <button onClick={onClick}>Click Me</button>
         </div>
   );
}

Now in this case useState is initialized the first time it’s used with the props.startAt value. This initialization does not take place again, during the lifecycle of this function, so that when you click the button it updates the state and re-renders the component without reinitializing the state. We can see this by putting console.log(`Props: ${props.startAt} State: ${state}`); after the useState line. In this case you’ll see the props value remains constant but the state changes on each click of the button.

This is great. But, what happens if the parent control actually needs to change the props. So, for example maybe we click a reset button to reset the value to the default.

useEffect

Whilst useState allows us to store state between function calls on a React component, we need a way to handle side effects, or more specifically in this example, we need ways of changing the state when the props change.

Let’s assume our parent component can set and reset the initial state for our Viewer component via the props. In fact, here’s that App component to demonstrate this


function App() {
   const [state, setState] = useState(1);

   function onReset() {
      setState(state === 0 ? 1 : 0);
   }

   return (
      <div className="App">
         <header className="App-header">
            <Viewer startAt={state} onReset={onReset}/>
         </header>
      </div>
   );
}

Note: this is a silly contrived example as we need the props to actually change – but for real work usage, imagine at some point a change in your app. is stored to localStorage and maybe. onReset loads the latest from localStorage. If that props value has now changed it will not (at this time) be reflected in the Viewer render.

You can see we’re using useState to supply the state as props to our Viewer component from our App. If you load the app as it stands, you’ll see nothing changes on the page, from our original implementation. The Viewer will keep incrementing even when reset is clicked. This is because we have no way to reset the state (remember it’s created like it would be in a constructor, i.e. when the function was first called).

This is where we use useEffect. The useEffect hook allows us to respond to changes in the props (and/or other dependencies), by adding the following code below the useState line in the Viewer component

useEffect(() => {
   setState(props.startAt);
}, [props])

Now when the props change (the [props] code denotes useEffecthas a dependency on the props value) useEffect will call setState, updating it with the latest props. We could ofcourse make this more granular by just having a dependency on [props.startAt]. We can supply an array of dependencies, any of which changes will cause useEffect code to execute.

Note: Ofcourse with a React class-based component we will also have the issue of how to reinitialize state from the props, because the props are set via the constructor. Hence this is not an issue just for functional components but in these cases useEffect is an elegant solution.

Publishing a TypeScript package to NPM

Over the holidays I’ve been updating F# unit conversion code so that I can regenerate the unit converters for different languages/technologies, hence I now have F# unit conversion, one for C# (removes the dependency on FSharp lib and written more in a C# idiomatic way), then I created SwiftUnits for Swift and finally (for the moment) TypeScript named unit-conversions (I might look to rename in it keeping with the other libs).

So next up I wanted to get this code packaged and available via the preferred package manager, hence F# and C# are now on NuGet, Swift isn’t on a package site but can be used as a package via it’s github location, so finally I wanted to get the TypeScript code packaged and deployed to NPM and that’s what this post discusses…

Getting Started

  • First off you’ll need to have a NPM account, so if you’ve not got one, jump over to https://www.npmjs.com/ and create one.
  • Next, create a folder for your package and then run yarn init (or the npm equivalent) and tsc –init
  • Create an index.ts file in the same folder as your package.json – this will basically export all parts from our library to the user
  • Create src folder off of the root folder – this will contain the TypeScript source for our package/lib

We’ve got the barebones in place, now we can start to code and configure our package. Add the source for your package into the src folder and then in the index.ts file just export the parts you want, so for example src for my unit-conversions package contains the TS files (my library code) and the index.ts has exports from my src files, like this

export { Angle } from './src/Angle';
export { Area } from './src/Area';
// etc.

We’re not going to waste time looking at the source for this package as it’s available on github from the previously mentioned link.

package.json requirements

Let’s get to the package.json configuration. We’ll need a name for the package, now I wanted to use a namespace, hence my package name is @putridparrot/conversion-units. Next up we’ll need a version, I’m classing my code as a beta release, hence using 0.1.x where x is the latest build. You’ll usually give your package a description, keywords etc. Importantly we’ll need a main and typings entry to allow the developer using the package an entry point as well as TypeScripts typings/definitions.

Here’s my current package.json as an example

{
    "name": "@putridparrot/conversion-units",
    "version": "0.1.3",
    "description": "Units of measure converters package",
    "main": "dist/index.js",
    "typings": "dist/index.d.ts",
    "keywords": [
      "units",
      "conversion",
      "convert",
      "unit of measure"
    ],
    "repository": {
      "type": "git",
      "url": "https://github.com/putridparrot/unit-conversions"
    },
    "scripts": {
      "build": "tsc"
    },
    "devDependencies": {
      "typescript": "^4.1.3"
    },
    "author": "PutridParrot",
    "license": "MIT"
  }

In my case we’ll build the code to the output directory dist (so if you change this, often lib is used for example, you’ll need to change the folder in the main and typings entries of the package.json).

tsconfig.json

I’m just going to list the current tsconfig.json from my project below

{
  "compilerOptions": {
    "target": "es5",                          
    "module": "commonjs",                     
    "declaration": true,                      
    "outDir": "dist",                         
    "strict": true,                           
    "esModuleInterop": true,                  
    "skipLibCheck": true,                     
    "forceConsistentCasingInFileNames": true,  
    "rootDir": ".",
  },
  "exclude": ["node_modules", "dist"]
}

The above is a clean up version of the tsconfig.json that is generated via tsc –init but the basics are pretty obvious, the main additions I made were to explicitly state the outDir. Without this the generated JavaScript files will go into the src folder and we don’t want that. Also I added the rootDir and the exclude section. All should be pretty self-explanatory but if not, have a read of Intro to the TSConfig Reference.

Packaging

Assuming your code builds/transpiles using tsc then once that’s run we should have a dist folder with the .d.ts and .js files. Now it’s time to push this package to NPM.

From bash or your preferred terminal the following just to ensure you’re logged into NPM

npm whoami

If you’re not logged in or are logged into the wrong account (if you’re running multiple accounts) then run

npm login

Note: Remember if you’ve setup 2FA in NPM then you’ll be prompted to use your authorizer app. to supply the “one-time” pass code.

Now if you have a paid account you might publish a private package in which case just run

npm publish

But if you do not have a private package option or simply want to make the package public then simply run

npm publish --access public

You will get asked for the passcode again (if 2FA is enabled), but once supplied the package will become available on the NPM site.

If you also have the same code committed to github then you’ll probably have a README.md and this will also be uploaded and available on the package page of NPM.

Publish only what you need to

One thing you may find is that the publish command will actually publish pretty much everything to NPM (it automatically excludes node_modules and some other files, see The .npmignore File for information on what’s ignored by default).

This means that when you install a package to your application you’re getting all the files that make up that published package – so for example my initial published package include my .github folder.

As you can probably deduce from the link above, to filter out files/folders that you do not want/need as part of your published package, you can add the file .npmignore which basically takes the same format entries as .gitignore. So from this we might have something like this

tests
coverage
.github
src/

In this case I don’t want to publish the tests, coverage for github workflow. Also I remove the src as ultimately the package will simply use the dist/src anyway.

HTML Canvas

HTML Canvas allows us to draw graphics (using JavaScript). We can create a canvas element and use a WebGL API to draw lines, fill rectangles etc.

Let’s demonstrate some of the canvas and drawing functionality by creating a simple Tic-Tac-Toe UI. Start of by creating a HTML file and within that we add a canvas element, for example

<canvas id="tic-tac-toe-board"></canvas>

Now in our JavaScript (or in my case I’m going to use TypeScript) we can get the element using the id and then start interacting with the canvas using WebGL, for for example here’s a “main” method which we’d run in the HTML body’s onload event. The first thing we need to do is get the canvas element using

const board = <HTMLCanvasElement> document.getElementById('tic-tac-toe-board');

Next up we need to get the element’s context using

const ctx = board.getContext('2d');  
if(ctx != null) {
}

Once we have the context we can use methods, such as fillRect, moveTo, lineTo etc. as well as set properties on the context, for example

const ctx = board.getContext('2d');  
if(ctx != null) {
  ctx.fillStyle = 'white';
  ctx.fillRect(0, 0, board.clientWidth, board.clientHeight);
}

Below is an example of an implementation of code to draw a Tic-Tac-Toe board and handle click events.

function main() {
  const board = <HTMLCanvasElement> document.getElementById('tic-tac-toe-board');

  const size = 500;
  const lineColour = "#ddd";
  const lineStart = 4;
  const lineLength = size - 5;
  const sectionSize = size / 3;

  board.width = size;
  board.height = size;

  const elemLeft = board.offsetLeft + board.clientLeft;
  const elemTop = board.offsetTop + board.clientTop;
  const elemWidth = board.clientWidth;
  const elemHeight = board.clientHeight;

  // example of handling click event
  board.addEventListener('click', ev => {
     const x = ev.pageX - elemLeft;
     const y = ev.pageY - elemTop;

     console.log(`X: ${x}, Y: ${y}`);
  });

  const ctx = board.getContext('2d');  
  if(ctx != null) {
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, elemWidth, elemHeight);

    ctx.lineWidth = 10;
    ctx.lineCap = 'round';
    ctx.strokeStyle = lineColour;
    ctx.beginPath();

    for (let y = 1; y <= 2; y++) {  
      ctx.moveTo(lineStart, y * sectionSize);
      ctx.lineTo(lineLength, y * sectionSize);
    }
      
    for (let x = 1; x <= 2; x++) {
      ctx.moveTo(x * sectionSize, lineStart);
      ctx.lineTo(x * sectionSize, lineLength);
    }
      
    ctx.stroke();
  }
}

Source code for this post is available on github

Building and testing TypeScript code using Github actions

In previous posts we’ve used Github actions to build .NET core code, now let’s create a workflow to build/transpile a TypeScript application.

  • Create a file (mine’s named build.yml) in your Github repo. in the folder .github/workflows
  • As per other examples of such a .yml action file we need to give the build a name and set up the triggers, so let’s do that, here’s the code
    name: Build
    
    on:
      push:
        branches: [ master ]
      pull_request:
        branches: [ master ]
    
    jobs:
      build:
    
        runs-on: ubuntu-latest
    

    In the above we’ve simply named the action Build which is triggered by pushes to master (and pull requests). We then set the action up to run on an ubuntu VM

  • Next we’ll add the following code to create a strategy (basically think, job configurations and application wide variables)
    strategy:
      matrix:
        node-version: [12.x]
    

    In this case we’re creating a variable named node-version with the version of node we want to use.

  • Now we’ll create the steps for the build
    steps:
      - uses: actions/checkout@v2
      - name: Node.js
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm install -g yarn    
      - name: yarn install, build and test
        run: | 
          yarn 
          yarn build
          yarn test
    

    Firstly we declare the checkout action to be used, then we used the setup-node action to (as the name suggests) setup node with the version we defined. In this example we then run npm to install yarn. Obviously we could have simply used npm instead (and you can replace the yarn commands with npm if you wish).

    Finally we have a step to install, build and test our code running the relevant yarn commands to carry out these tasks.

The above expects the package.json of your project to have scripts named test and build. For example

"scripts": {
  "test": "jest",
  "build": "tsc"
}

Modules, modules and yet more modules

Another post that’s sat in draft for a while – native JavaScript module implementations are now supported in all modern browsers, but I think it’s still interesting to look at the variations in module systems.

JavaScript doesn’t (as such) come with a built in module system. Originally it was primarily used for script elements within an HTML document (inline scripting or even scripts per file) not a full blown framework/application as we see nowadays with the likes of React, Angular, Vue etc.

The concept of modules was introduced as a means to enable developers to separate their code into separate files and this ofcourse aids in reuse of code, maintainability etc. This is not all that modules offer. In the early days of JavaScript you could store your scripts in separate files but these would then end up in the global namespace which is not ideal, especially if you start sharing your scripts with others (or using other’s scripts), then there’s the potential of name collisions, i.e. more than one function with the same name in the global namespace.

If you come from a language such as C#, Java or the likes then you may find yourself being slightly surprised that this is a big deal in JavaScript, after all, these languages (and languages older than JavaScript) seemed to have solved this problems already. This is simply the way it was with JavaScript because of it’s original scripting background.

What becomes more confusing is that there isn’t a single solution to modules and how they should work, several groups have created there own versions of modules over the life of JavaScript.

Supported by TypeScript

This post is not a history of JavaScript modules so we’re not going to cover every system every created but instead primarily concentrate on those supported by the TypeScript transpiler, simply because this where my interest in the different modules came from.

TypeScript supports the following module types, “none”, “commonjs”, “amd”, “system”, “umd”, “es2015” and “ESNext”. These are the one’s that you can set in the tsconfig.json’s compilerOptions, module.

To save me looking at writing my own examples of the JavaScript code, we’ll simply let the TypeScript transpiler generate code for us and look at how that looks/works.

Here’s a simple TypeScript (Demo.ts) file with some code (for simplicity we’re not going to worry about lint rules such as one class per file or the likes).

export class MyClass {    
}

export default class MyDefaultClass {    
}

Now my tsconfig.json looks like this

{
  "compilerOptions": {
    "target": "es6",  
    "module": "commonjs",  
    "strict": true
  },
  "include": ["Demo.ts"]
}

And I’ll simply change the “module” to each of the support module kinds and we’ll look at the resultant JavaScript source. Alternatively we can use this index.js

var ts = require('typescript');

let code = 'export class MyClass {' + 
'}' +
'export default class MyDefaultClass {' +
'}';

let result = ts.transpile(code, { module: ts.ModuleKind.CommonJS});
console.log(result);

and change the ModuleKind the review the console output. Either way we should get a chance to look at what style of output we get.

Module CommonJS

Setting tsconfig module to CommonJS results in the following JavaScript being generated. The Object.defineProperty simple assigns the property __esModule to the exports. Other than this the class definitions are very similar to our original code. The main difference is in how the exports are created.

The __esModule is set to true to let “importing” modules know that this code is is a transpiled ES module. It appears this matters specifically with regards to default exports. See module.ts

The exports are made directly to the exports map and available via the name/key. For example exports[‘default’] would supply the MyDefaultClass, like wise exports[‘MyClass’] would return the MyClass type.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class MyClass {
}
exports.MyClass = MyClass;
class MyDefaultClass {
}
exports.default = MyDefaultClass;

We can import the exports using require, for example

var demo = require('./DemoCommon');

var o = new demo.MyClass();

Module AMD

The Asynchronous Module Definition was designed to allow the module to be asynchronously loaded. CommonJS is a synchronous module style and hence when it’s being loaded the application will be blocked/halted. With the AMD module style expects the define function which passes arguments into a callback function.

As you can see the code within the define function is basically our CommonJS file wrapped in the callback.

define(["require", "exports"], function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    class MyClass {
    }
    exports.MyClass = MyClass;
    class MyDefaultClass {
    }
    exports.default = MyDefaultClass;
});

To import/require AMD modules we need to use requirejs, for example assuming we created a file from the above generated code named DemoAmd.js, then we can access the module using

var requirejs = require('requirejs');
requirejs.config({
    baseUrl: __dirname,
    nodeRequire: require
});

var demo = requirejs('./DemoAmd');

var o = new demo.MyClass();

Module System

System.register([], function (exports_1, context_1) {
    "use strict";
    var MyClass, MyDefaultClass;
    var __moduleName = context_1 && context_1.id;
    return {
        setters: [],
        execute: function () {
            MyClass = class MyClass {
            };
            exports_1("MyClass", MyClass);
            MyDefaultClass = class MyDefaultClass {
            };
            exports_1("default", MyDefaultClass);
        }
    };
});

Module UMD

UMD or Universal Module Definition is a module style which can be used in place of CommonJS or AMD in that it uses if/else to generate modules in either CommonJS or AMD style. This means you can write your modules in a way that can be imported into a system expecting either CommonJS or AMD.

(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    class MyClass {
    }
    exports.MyClass = MyClass;
    class MyDefaultClass {
    }
    exports.default = MyDefaultClass;
});

We can import UMD modules using either require (as shown in our CommonJS code) or requirejs (as shown in our AMD code) as UMD modules represent modules in both styles.

Module es2015 and ESNext

As you might expect as the TypeScript we’ve written is pretty standard for ES2015 the resultant code looks identical.

export class MyClass {
}
export default class MyDefaultClass {
}

Module None

I’ve left this one until last as it needs further investigation as module None suggests that code is created which is not part of the module system, however the code below works quite happily with require form of import. This is possibly a lack of understanding of it’s use by me. It’s included for completeness.

"use strict";
exports.__esModule = true;
var MyClass = /** @class */ (function () {
    function MyClass() {
    }
    return MyClass;
}());
exports.MyClass = MyClass;
var MyDefaultClass = /** @class */ (function () {
    function MyDefaultClass() {
    }
    return MyDefaultClass;
}());
exports["default"] = MyDefaultClass;

Inspecting modules

function printModule(m, indent = 0) {
    let indents = ' '.repeat(indent);

    console.log(`${indents}Filename: ${m.filename}`);    
    console.log(`${indents}Id: ${m.id}`);    
    let hasParent = m.parent !== undefined && m.parent !== null;
    console.log(`${indents}HasParent: ${hasParent}`);
    console.log(`${indents}Loaded: ${m.loaded}`);
    if(m.export !== []) {
        console.log(`${indents}Exports`);
        for(const e of Object.keys(m.exports)) {
            console.log(`${indents}  ${e}`);
        }
    }
    if(m.paths !== []) {
        console.log(`${indents}Paths`);
        for(const p of m.paths) {
            console.log(`${indents}  ${p}`);
        }
    }

    if(m.children !== []) {
        console.log(`${indents}Children`);
        for (const child of m.children) {
            printModule(child, indent + 3);
        }
    }
}


console.log(Module._nodeModulePaths(Path.dirname('')));
printModule(module);

Destructing in JavaScript

In JavaScript/TypeScript, if you’re using the Prefer destructuring from arrays and objects (prefer-destructuring) eslint rule, you’ll want to use destructing syntax to get values from objects and arrays.

If we imagine we have an object like this

class Person
{
   firstName: string;
   lastName: string;
}

The to get the firstName from a Person instance, we tend to use

const firstName = person.firstName;

Instead this rule prefers that we use the following syntax

const { firstName } = person;

If for some reason (for example in React if you’re destructing state which may have been changed) you have need to get the value using destructing syntax but assigned to a new variable/value name, then we use

const { firstName: fname } = person;

Adding TypeScript to Electron

So we’ve seen that Electron is basically a browser window with integrations which allows us to use JavaScript to interact with it, but as somebody who prefers the type safety features that come with TypeScript, obviously I’d want to integrate TypeScript as well.

If you’ve not got TypeScript installed globally then run

npm install --save-dev typescript

If we take our application from the Getting Started post, lets simply started by adding a tsconfig, just run the following from your project’s root folder

tsc --init

Now change our main.js to main.ts. We’ll obviously need a build step to transpile the TypeScript to JavaScript which can then be uses via Electron, so add the following to the scripts section in package.json

"build": "tsc"

you might also like to either rename the start script or add another script to both build/transpile and run the application, i.e.

"go": "tsc && electron ."

Obviously this will litter your code base with generated .js files, so it’s best to transpile our code to a folder of it’s own, in this case we’ll call it src. Just add the following to the tsconfig.json

"outDir": "./src",

Then change package.json “main” to the following

"main": "./src/main.js",

That’s all there is to it, now we can add some type checking to our code and write TypeScript.

TypeScript 3.7.2

TypeScript 3.7.2 has just been released.

First off you’re going to want to install it globally on your machine, hence run (obviously this will install the latest version so not specific to version 3.7.2)

npm install -g typescript

or if you want it locally to your project, ofcourse you can run

npm install -D typescript 

If you’re using VS Code and you want to set it up for this version (if it’s not already set for the latest) then press F1 and select Preferences: Open User Settings adding (or editing) the following (off of the root level of the JSON)

"typescript.tsdk": "node_modules\\typescript\\lib",

I’m not going go through the new features exception to say we’ve now got optional chaining the ?. operator from C# and Nullish coalescing using the ?? operator.

Properties and fields in TypeScript

There’s several ways to handle properties within TypeScript, let’s look at a simple class

class Point {
    x: number;
    y: number;
}

Whilst Visual Code etc. will say these are properties, they’re more like public fields (if you wish to compare to a language such as C#), hence missing the functional side that comes with properties.

By default no accessors means the properties x and y are public. TypeScript allows us to mark them as private in which case we could then write functional getters and setters as per Java, for example

class Point {
    private x: number;
    private y: number;

    getX() {
        return this.x;
    }
    setX(x: number) {
        this.x = x;
    }
    getY() {
        return this.y;
    }
    setY(y: number) {
        this.y = y;
    }
}

TypeScript also supports C# style property getter and setters like this

class Point {
    private x: number;
    private y: number;

    get X() : number {
        return this.x;
    }
    set X(x : number) {
        this.x = x;
    }
    get Y() : number {
        return this.y;
    }
    set Y(y : number) {
        this.y = y;
    }
}

and like C# these get/set methods result in property style syntax, i.e.

var p = new Point();
p.Y = 4;
console.log(p.Y);

Constructors within TypeScript can reduce the ceremony for creating fields and properties by using the private or public keywords for constructor arguments, for example

class Point {
  constructor(public x: number, public y: number) {
  }
}

// becomes

class Point {
    get x() {
        return this.x;
    }
    set x(x) {
        this.x = x;
    }
    get y() {
        return this.y;
    }
    set y(y) {
        this.y = y;
    }
}

Using private constructor arguments is equivalent private fields, so for example

class Point {
  constructor(private x: number, private y: number) {
  }
}

// becomes

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

If we need to overwrite the getter/setters functionality, we can write

class Point {
    constructor(public x: number, public y: number) {
    }
   
    public get x() : number {
        return this.x;
    }
    public set x(x : number) {
        this.x = x;
    }   
    public get y() : number {
        return this.x;
    }
    public set y(y : number) {
        this.y = y;
    }
}