JS with Types 💪 com

Types without Transpilation

https://youtube.com/watch?v=93_knuo_VQs

npx jswt init


AJ ONeal
@_beyondcode
youtube.com/coolaj86


Dangerous Wrong Thinker

Equal Opportunity Offender

Technophobic Technologist Extraordinairé



Savvi.Legal

Business Lifecycle Platform
for Founders & Investors

System of Record, Deal Rooms

(we automate away the stuff founders hate)


Dash Incubator

Digital Cash
(cryptocurrency to be used as money)


JS with Types 💪

Types without Transpilation

npx jswt init

(like, sub, follow if you wannu)


Useful Links

Slides: https://beyondcodebootcamp.github.io/
Video: https://youtu.be/93_knuo_VQs
Slides Builder: MD Slides

Reference Material


💊

Not a Gateway Drug to TypeScript


Coding like it's 1999


💊

Why Go 🐹 and Zig ⚡️ developers
shouldn't give up on JavaScript


#0

Philosophy


Build steps are not good.


Build steps are not good.

Slow build steps are evil.


Code gen is better
than meta-programming.


Duck typing is good.


Duck typing is good.

Inheritance is evil.


Better tooling makes
for better developers.


Copy and paste
(and find and replace)
are not bad.


Copy and paste
(and find and replace)
are not bad.

Transpilation is evil.


Creeds of Craftsmanship (.com)


The Unix Philosophy
Zen of Python
Go Proverbs
Zen of Zig
The Prettier Rationale
(DRY, WET &) AHA Programming
The actual Agile Manifesto


#1

Types are in the Tooling


JavaScript has always had types


🦆


(Static Types are a Lie!)


(safe languages have always been dynamically-typed)


Types of Types

Single-Value Multi-Value Collection Meta
  • Primitives
    • Booleans
    • Number
    • String
    • BigInt*
    • Int/Uint*
  • Function
  • Nullish
    • null
    • undefined
    • not defined
  • Struct
  • Tuple
  • WeakMap*
  • WeakSet*
  • Map
    (key-value pairs)
  • List
    (indexed values)
  • newtypes
  • type aliases
    (marker types)
  • value enums
    (C-style)
  • type enums
    (functional-style)
  • mixins
    (unions)
  • templates
  • utilities

🦺 🚧

Bad Tools => Bad Types


🔨 🧰


JavaScript

let msg = "Hello, World!";

let answer = 42;

JSDoc


JSDoc

/** @type {String} */
let msg = "Hello, World!";

/** @type {Number} */
let answer = 42;

JSDoc

Communicate Intent


JSDoc

# 📝
npm install --location=global jsdoc

TSC


TSC

/** @type {String} */
let msg = 42;
// [E] Type 'number' is not assignable to type 'string'

/** @type {Number} */
let answer = "Hello, World!";
// [E] Type 'string' is not assignable to type 'number'

TSC

Catch, Correct, Classify


TSC

# 😱
npm install --location=global typescript

TSC

TypeScript Checker


JSDoc + TSC Config


npm init

npx -p typescript -- tsc --init \
    --allowJs --alwaysStrict --checkJs \
    --moduleResolution node \
    --noEmit --noImplicitAny \
    --target es2022 \
    --typeRoots './typings,./node_modules/@types'
mv tsconfig.json jsconfig.json
--include '*.js,src/**/*.js,lib/**/*.js' \
--exclude node_modules \

😵‍💫


too hard

🤯🔫


⚡️


jswt


jswt

# 🚀
npm init

# 💪
npx jswt init
npx jswt reexport

jswt

JS With Types


jswt

JS WT!?


jswt

foo/
├── 📝 .prettierignore
├── 📝 docs/
├── 💪 index.js
├── 💪 jsconfig.json
├── 💪 jsdoc.conf.json
├──    lib/
│      └── 💪 foo.js
├── 💪 package.json
├──    package-lock.json
├── 📝 README.md
├── 💪 types.js
└── 💪 typings/

npm run fmt
npm run lint
npm run reexport


#2

JavaDoc and C# had a baby...


Type Syntax

/**
 * ...
 */

Type Syntax

/**
 * ...
 */
/** ... */

Inline Types

/** @type {String} */
var msg;

Inline Parameters

/**
 * @param {String} [greeting]
 * @param {Object} other
 * @param {String} other.name
 */
function greet(greeting = "Hello", { name }) {
  return `${greeting}, ${name}!`;
}

Struct Definitions

/**
 * A great person
 * @typedef Person
 * @prop {Number} age - years since birth
 * @prop {Array<Person>} friends - mutual follows
 * @prop {FooGreeter} greet
 */

/** @type {Person} */
var friend;

Function Definitions

/**
 * A great way to say "Hello"
 * @callback Greeter
 * @param {String} name
 * @param {Person} other
 * @returns {Promise<String>}
 * @throws
 */

/** @type {Greeter} */
async function greet(greeting, other) {
  if (isStranger(other)) {
    throw Error("Stranger Danger!");
  }
  return `${greeting}, ${other.name}!`;
}

Weirdness...

/**
 * @function ❌
 */

🤷‍♂️


🪲

github.com/microsoft/TypeScript/issues/50274


⚠️ @prop vs @param

/**
 * @callback Greeter
 * @param {Person} other
 * @prop {String} foo
 */
function greet(other) {
  // ...
}
greet.foo = "bar";

Nullish Definitions

/** @type {Boolean?} */
let alive = null;

/** @type {Boolean?|undefined} */
let cat;

Map Types

/** @type {Object.<String, String>} */

Map Types

/** @type {Object.<String, String>} */
/** @type {Object.<Name, Number>} */

Map Types

/** @type {Object.<String, String>} */
/** @type {Object.<Name, Number>} */
/** @type {Object.<Number ❌, Fruit>} */

Map Types

/** @type {Object.<String, String>} */
/** @type {Object.<Name, Number>} */
/** @type {Object.<Number ❌, Fruit>} */
/** @type {Object.<String, any ⚠️>} */

Tuple Types

/** @type {[Result, Error]} */
let [result, err] = await doStuff();

Tuple Types

/** @typedef {[EventName,EventData]} SocketIoResult */

/** @type {SocketIoResult} */
let result = ["follow", { "source": ... }];

Type Extension

/** @typedef {PersonBase & PersonExtra} Person */

Type Extension

/** @typedef {PersonBase & PersonExtra} Person */
/** @typedef {File | Folder | Symlink | Pipe} DirEntry */

Narrowing Functions

/** @typedef {File | Folder | Symlink | Pipe} DirEntry */

/**
 * @param {DirEntry} dirEntry
 * @returns {dirEntry is File}
 */
function isFile(dirEntry) {
  return "file" === dirEntry.type;
}

Utility Types

/** @typedef {Partial<Person>} PersonLike */

See TypeScript: Utility Types


Utility Types

/** @typedef {Partial<Person>} PersonLike */
/** @typedef {Required<Person>} PersonFull */

See TypeScript: Utility Types


Weirdness...

/**
 * @typedef {PersonBase} Person
 * @prop {String} ssn ❌
 */

🤷‍♂️


Lots of quirks


Utility Functions

/**
 * @param {Array<any>} arr
 * @returns any - 😬
 */
function last(arr) {
  return arr[arr.length - 1];
}

Utility Functions

/**
 * @template {any} T
 * @param {Array<T>} arr
 * @returns T
 */
function last(arr) {
  return arr[arr.length - 1];
}

Utility-ish Function

/** @typedef {Partial<Person>} PersonLike */

/**
 * @template {PersonLike} T
 * @param {T} p
 * @returns T
 */
Person.sanitize = function (p) {
  if (p.ssn) {
    p.ssn = "***-**-" + p.ssn.slice(-4);
  }
  return p;
};

JSDoc Reference: @template


Import Types

npm install --save @types/node
npm install --save @types/express

Import Types

npm install --save @types/node
npm install --save @types/express
/** @type {import('express').Handler} */
function greet(req, res) {
  res.json("Hello, World!");
}

/** @type {import('express').ErrorRequestHandler} */
function apologize(err, req, res, next) {
  res.json("Hello, World!");
}

Reexport Types

/** @typedef {import('express').Handler} Handler */
/** @typedef {import('./users.js').User} User */

💯


👏



#3

Deep Dive


🤿



⚠️ Primitives++

/** @type {BigInt} */
let debt = 1_000_000_000_000n;

/** @type {Uint8Array} */
let answers = Uint8Array.from([11, 37, 42]);

/** @type {Buffer|ArrayBuffer} */
let bytes = Buffer.from([255, 255, 255, 255]);

⚠️ Built-Ins

/** @type {Date} */
let matcher = Date.parse("2022-09-22T15:05:00.000-0600");

/** @type {RegExp} */
let matcher = /^abc$/;

⚠️ Syntax vs Semantics


⚠️ Syntax vs Semantics

(no intention / meaning)


⚠️ Types are Semantic


Structs vs Maps


Structs

Mr. Potato Head

A "kit" of stuff that goes together


Structs

POJO

(Plain-Old JSON Object)

/**
 * A great human being
 * @typedef {Object} Person
 * @prop {String} name
 * @prop {Number} age
 */

/** @type {Person} */
let bob = { name: "Bob", age: 42 };

Map

Phone Home Screet

Collection of Like-ish things


Typically by id, but...

/** @type {Object.<String, Person>} */
let people = {
  Bob: { name: "Bob", age: 42 },
  Jane: { name: "Jane", age: 37 },
};

Map

/** @type {Object.<String, Person>} */
let people = {
  Bob: { name: "Bob", age: 42 },
  Jane: { name: "Jane", age: 37 },
};

(kinda tuple-ish)


Map

(kinda tuple-ish)

/** @typedef {"Bob"|"Jane"|"Avery"|"Joy"} GreatName */
/** @type {Object.<GreatName, Person>} */
let people = {
  Bob: { name: "Bob", age: 42 },
  Jane: { name: "Jane", age: 37 },
};

people["Jack"] = {};
// [E] "Jack" not found in "Bob"|"Jane"|"Avery"|"Joy"
// [E] "{}" cannot be assigned to "Person"

Tuples vs Lists


Tuples

JS WT!?


Tuples

Unopened Lego Set

"Kit by the Numbers"


Tuples

Struct...

but indexed, rather than named


Tuples

/**
 * @typedef {[Person, Error]} PersonResult
 */
let [person, err] = await Person.getById(37);

Tuples (vs Structs)

let [p, err] = await Person.getById(37);
let { person, error } = await Person.getById(37);

Lists

Pokemon Cards

Ordered collection of Like-ish things


Lists

/** @type {Array<String>} */
let fruits = ["apple", "banana", "grape"];

(⚠️ moot - it's already inferred)


Lists

/** @type {Array<Fruit>} */
let fruits = [
  { name: "apple", calories: 90 },
  { name: "banana", calories: 105 },
];

Enums


Value Weak Sauce Enums

const (
  NONE = 0 // falsy, defaulty
  VIEWER = 1
  EDITOR = 2
  OWNER = 3
)

Weirdness...

/**
 * @enum ⚠️
 */

(C-style)

@enum 🤷‍♂️


Value Enums

/** @typedef {"apple"|"banana"|"grape"} FruitNames */
/**
 * @param {FruitNames} fruitName
 */
function smashFruit(fruitName) {
  // ...
}

smashFruit("monkey");

Type Enums

For Like-ish things

(things that could go in a Map or List)


Type Enums

/** @typedef {Array|Uint8Array|Buffer} Bytes */
/** @typedef {Person|Human} PersonLike */

Type Enums

💩

/** @typedef {String|Number|BigInt} BigNumber */

(code smell)


Type Enums

🤷‍♂️ (legacy code)

/**
 * @param {Object|Function} [opts]
 * @param {Function} [cb]
 */
function getImages(opts, cb) {
  // ...
}

Implied Enums

If it fits it ships!


Type Alias

a.k.a. Marker Type

/** @typedef {Number} Dollars */

/** @type {Dollars}
let salary = 1_000_000;

(improve documentation)


❌ NewType

/** @type {Dollars} */
let savings = 1_000_000;

/** @type {Cents} */
let price = 500_00;

let quantity = savings / price;
// 💣 Cannot divide Dollars by Cents

Polymorphism


Polymorphism

😬


👍 Mixins

/**
 * @typedef {Object} WithFriends
 * @prop {Array<Person>} friends
 */

/** @typedef {BasePerson & WithFriends} PersonWithFriends */

Type Utils

/**
 * @typedef {Partial<FullPerson>} PersonLike
 * @typedef {Required<FullPerson>} PerfectPerson
 * @typedef {NonNullable<FullPerson>} SteelPerson
 * @typedef {Pick<BasePerson, "name" | "age">} MiniPerson
 * @typedef {Omit<BasePerson, "ssn">} PublicPerson
 */

Type Utils

/**
 * @param {ReadOnly<PersonLike>} p
 * @returns {String}
 */
Person.hash = function (p) {
  // ...
};

Type Utils

⚠️


Type Utils

⚠️

Just because you can
doesn't mean you should


Type Utils

⚠️

Flat is Better than Nested - Zen of Python

Type Utils

⚠️

Avoid Hasty Abstractions - Kent C. Dodds


Type Utils

⚠️

People aren't great at recursion - Every Juan, Ever


🏊‍♂️ 🫁


🏊‍♂️ 🫁


🦆

Quack!


🦆

Quack!


Q&A


Like, Sub, & Follow

(if you wannu)


Thanks.

/**
 * Keep on trying 'til we run out of cake
 * @template {PersonLike} T
 * @param {T} subject
 * @param {Boolean} [alive]
 * @returns {Promise<T?>}
 * @throws
 */
function doScience(subject, alive) {
  // ...
}