JavaScript & TypeScript

We've learned nothing

There’s a sort of evil out there.

Some History and Empathy

  • JavaScript was born in about 10 days in the mid-nineties and the top requirement was to appease the Netscape marketing team…so not the ideal scenario for a complex language.
  • ECMA, an industry association from 1961 concerned with making communication standards, stepped in to help with JavaScript shortly after release.
    • ECMA could not use the name JavaScript for trademark reasons, so it calls it ECMAScript.
  • ECMAScript 4 is when shit got real, tried to make a push for “full on language”, also a legal shitstorm which ended in 3.1 instead after 8 years.
    • In order to “minimize” confusion, ECMAScript 3.1 was re-named ECMAScript 5.
    • 3.1/5 had a small number of improvements.
  • Next came ECMAScript 6, aka ECMAScript 2015 (cause why not).
    • Much more hefty update, including modules.
  • Great blog up to this point.
  • ECMAScript now has smaller releases with dates (e.g. ECMAScript 2020 bring Nullish Coalescing Operator ??).

Productivity

Patterns for writing better code.

strict mode

  • strict mode (vs. sloppy mode) was introduced in ECMAScript 5
  1. Eliminates some JavaScript silent errors by changing them to throw errors.
  2. Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that’s not strict mode.
  3. Prohibits some syntax likely to be defined in future versions of ECMAScript.
  • applies to entire scripts or to individual functions
'use strict';

modules, exports, and imports

  • The scope of JavaScipt started off very small, initial modularity was just loading separate scripts into the global scope
    • scripts can overwrite each other
    • order of loading scripts matters
<!DOCTYPE html>
<html>
	<head>
		<script src="person.js"></script>
		<script src="author.js"></script>
	</head>
	<body>
		<script>
		    person.author.doJob('ES6 module history');
		</script>
	</body>
	<script>
		// shared scope means other code can inadvertently destroy ours
		var person ='all gone!';
	</script>
</html>

Loading scripts

  • Node brought along CommonJS on the server side

    • concise syntax with an external requirement for a “loader”
      • exports object and require function
    • avoids global scope by the loader (require function impl)
      • Before a module’s code is executed, Node.js will wrap it with a function wrapper
  • AMD spec was on the frontend side

    • async nature means no static analysis
  • UMD combines CJS and AMD and sucks

  • ES6 finally proposes modules ESM, with the lessons (re-learned…)

    • can determine imports/exports at compile time (statically)
    • importing is now handled by the runtime instead of a library
  • deep dive

  • modules

    • modern browsers have started to support module functionality natively, used to just be “get the whole script, it shouldn’t be that big”
    • Use of native JavaScript modules is dependent on the import and export statements
  • export

    • The export statement is used when creating JavaScript modules to export live bindings to functions, objects, or primitive values from the module so they can be used by other programs with the import statement.
  1. Named Exports (Zero or more exports per module)
  2. Default Exports (One per module)
  • Named exports are useful to export several values. During the import, it is mandatory to use the same name of the corresponding object.
// file test.js
let k; export default k = 12;

// some other file
import m from './test'; // note that we have the freedom to use import m instead of import k, because k was default export
console.log(m);        // will log 12

default export can be called anything in import

<script type="module" src="main.js"></script>

declare script as type module

  • You can only use import and export statements inside modules, not regular scripts
  • modules are deferred automatically
  • Modules are only executed once, even if they have been referenced in multiple <script> tags
import * as Module from './modules/module.js';

Module.function1()
Module.function2()

grabs all the exports available inside module.js, and makes them available as members of an object Module, effectively giving it its own namespace

typescript compiler tsc

  • composite – enforces certain constraints which make it possible for build tools to determine if project has been built
  • incremental – save information about the project graph from the last compilation to files stored on disk
  • target – JS language features (e.g. ES2022)
  • declaration – emit type files (e.g. *.d.ts)
  • declarationMap – helps tools (IDEs) map back to source with project references
  • lib – default set of type definitions for built-in JS APIs, DOM for window, document
  • esModuleInterop – less surprises with CJS due to some early bad assumptions
    • also enables allowSyntheticDefaultImports
    • used with importHelpers to minimize extra code created
  • strict – enable all extra type checking features

typescript project references

  • project references

  • guidance

  • maybe not needed?

  • The path property of each reference can point to a directory containing a tsconfig.json file, or to the config file itself (which may have any name).

  • Referenced projects must have the new composite setting enabled. This setting is needed to ensure TypeScript can quickly determine where to find the outputs of the referenced project. Enabling the composite flag changes a few things

  • tsc --build which is typescript’s multistage build

building

  • nodejs is the runtime (btdubs, browsers are the OG runtime)
  • npm is the package manager
  • yarn is the dependency manager
pacman -S nodejs npm yarn

prep arch env for javascript development

  • yarn run [script] [<args>]
  • scripts are defined in package.json
  • the run is optional :|

npm

yarn

  • v2+ is at berry
  • yarn install is used to install all dependencies for a project. This is most commonly used when you have just checked out code for a project, or when another developer on the project has added a new dependency that you need to pick up.
    • running yarn with no command will run yarn install
yarn workspaces foreach

run a command in each workspace

  • appears to not fail if command doesn’t exist in workspace, which is a bummer cause can’t use convention pattern in packages

ESM, TS, Node, the Browser…FIGHT

What do we know?

  • Browsers and Node both like explicit extensions in import statements

package

  • the type can be set to module or commonjs to force how all files in the package are handled

file extensions

  • if type isn’t set in the package config, file extensions can be used on a per-file bases (so not all or nothing)

tsconfig

  • typescript exposes compiler settings to change behavior and emitted code
  • module can be CommonJS or a whole bunch or ESM options
    • ES2020 adds dynamic support
    • node16 and nodenext emit based on the node module rules

      The emitted JavaScript uses either CommonJS or ES2020 output depending on the file extension and the value of the type setting in the nearest package.json. Module resolution also works differently.

    • moduleResolution – compiler will try to locate a file that represents the imported module. To do so the compiler follows one of two different strategies: Classic or Node
      • node module resolution is the most-commonly used in the TypeScript community and is recommended for most projects
      • A relative import is one that starts with /, ./ or ../
    • TypeScript compiler has a set of additional flags to inform the compiler of transformations that are expected to happen to the sources to generate the final output
      • baseUrl and paths

References

JavaScipt Fundamentals

JavaScipt things I always forget.

objects and classes

  • object is the simplest structure in JS, a map of string keys to any type data or function

    • objects are callable
  • class is functionality wrapped around an object

    • the class constructor is called with the new keyword and it:
      1. create a new object
      2. bind this to the new object, so you can refer to this in your constructor code
      3. run the code in the constructor
      4. return the new object
    • classes promote inheritance which can be difficult to scale
      • prefer more verbose, but explicit, composition over inheritance
        • for example a function might be required to wire together 2 objects (animal + barker) to produce a dog object
  • objects

  • classes

  • handbook

prototypes and this

  • the prototype chain to find a method is kinda gnarly
    • objects have a link to a prototype object
    • When trying to access a property of an object, the property will not only be sought on the object but on the prototype of the object, the prototype of the prototype, and so on until either a property with a matching name is found or the end of the prototype chain is reached
const o = { a: 1 };
// The newly created object o has Object.prototype as its [[Prototype]]
// Object.prototype has null as its prototype.
// o ---> Object.prototype ---> null

const b = ['yo', 'whadup', '?'];
// Arrays inherit from Array.prototype
// (which has methods indexOf, forEach, etc.)
// The prototype chain looks like:
// b ---> Array.prototype ---> Object.prototype ---> null

function f() {
  return 2;
}
// Functions inherit from Function.prototype
// (which has methods call, bind, etc.)
// f ---> Function.prototype ---> Object.prototype ---> null

const p = { b: 2, __proto__: o };
// It is possible to point the newly created object's [[Prototype]] to
// another object via the __proto__ literal property. (Not to be confused
// with Object.prototype.__proto__ accessors)
// p ---> o ---> Object.prototype ---> null

standard prototypes

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }

  get area() {
    return this.height * this.width;
  }

  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

const square = new Square(2);
// square ---> Square.prototype ---> Polygon.prototype ---> Object.prototype ---> null

class prototypes

class Box {
  constructor(value) {
    this.value = value;
  }

  // Methods are created on Box.prototype
  getValue() {
    return this.value;
  }
}

modifying class prototype

  • this points to an object instance

    • which object? well now, that is the part that sucks
      • When used in an object method, this refers to the object
    • Methods like call(), apply(), and bind() can refer this to any object
      • bind > apply and call > method > global
  • Bind vs Call?

    • call is invoked immediately
const person = {
  firstName  : "John",
  lastName   : "Doe",
  id         : 5566,
  myFunction : function() {
    return this;
  }
};

this is the person object

const person1 = {
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
}

const person2 = {
  firstName:"John",
  lastName: "Doe",
}

// Return "John Doe":
person1.fullName.call(person2);

with call an object can use a method belonging to another object, this refers to person2

const person = {
  firstName:"John",
  lastName: "Doe",
  fullName: function () {
    return this.firstName + " " + this.lastName;
  }
}

const member = {
  firstName:"Hege",
  lastName: "Nilsen",
}

let fullName = person.fullName.bind(member);

with bind an object can borrow a method from another object

this.x = 9; // 'this' refers to the global object (e.g. 'window') in non-strict mode
const module = {
  x: 81,
  getX() {
    return this.x;
  },
};

console.log(module.getX()); // 81

const retrieveX = module.getX;
console.log(retrieveX()); // 9; the function gets invoked at the global scope

// Create a new function with 'this' bound to module
// New programmers might confuse the
// global variable 'x' with module's property 'x'
const boundGetX = retrieveX.bind(module);
console.log(boundGetX()); // 81

showing off a common issue that bind can fix

  • arrow functions might be a simpler way to fix method extract issues than bind

var and (then) let and const

  • before the advent of ES6, var declarations ruled let and const are the new hotness
  • let is block scoped, can be updated (but not re-declared)
  • const is block scoped, cannot be updated

comparisons with referential equality

const hero1 = {
  name: 'Batman'
};
const hero2 = {
  name: 'Batman'
};
hero1 === hero1; // => true
hero1 === hero2; // => false
hero1 == hero1; // => true
hero1 == hero2; // => false
Object.is(hero1, hero1); // => true
Object.is(hero1, hero2); // => false

referential equality in practice

TypeScript Fundamentals

TypeScript things I always forget.

optional parameters

function printName(last?: string) {
  if (last) {
    ...
  }
}
  • The ?. operator is like the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined.

interfaces and types

Interfaces and types feature sets are converging, making the distinction confusing.

interface User {
  name: string;
  id: number;
}

const user: User = {
  name: "Hayes",
  id: 0,
};

interface

  • handbook
  • duck typing like Go
  • variables use const whereas properties use readonly
function printName(obj: { first: string; last?: string }) {
  // ...
}

optional properties with ?

type ID = number | string;

type alias

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

type assertions

union and intersection types

interface A { a: number }  
interface B { b: number }

var ab: A & B = { a: 1, b: 1 };  
var a: A = ab;  // A & B assignable to A  
var b: B = ab;  // A & B assignable to B

intersection literal

const args = [8, 5];
// const args: number[]
const angle = Math.atan2(...args); // error! Expected 2 arguments, but got 0 or more.
console.log(angle);

const args = [8, 5] as const;
// const args: readonly [8, 5]
const angle = Math.atan2(...args); // okay
console.log(angle);

giving the typescript compiler some more info to work with, narrow the scope