JS with Types 💪 com
Types without Transpilation
https://youtube.com/watch?v=93_knuo_VQs
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
- JSDoc: Tags: @type
- TypeScript: Everyday Types
- Gil Tayar: JSDoc Typing
- Gil Tayar: All Benefits, No Drawback
- JavaScript Jabber: Typing without Transpilation
💊
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 |
---|---|---|---|
|
|
|
|
🦺 🚧
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 */
Utility Types
/** @typedef {Partial<Person>} PersonLike */
/** @typedef {Required<Person>} PersonFull */
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;
};
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
- "Object" notation: {}
- "Array" notation: []
⚠️ Syntax vs Semantics
- "Object" notation: {}
- "Array" notation: []
(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 Enums ❌
- Type Enums 💪
- Implied 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) {
// ...
}