Slide 1

Slide 1 text

Krystan Hu ff Menne TypedEmber extends Confidence How to defeat imposter syndrome with TypeScript

Slide 2

Slide 2 text

Maya Angelou “Uh-oh, they’re going to find out now. I’ve run a game on everybody, and they're going to find me out.”

Slide 3

Slide 3 text

Elizabeth Cox, Educator “To call it a syndrome is to downplay how universal it is.”

Slide 4

Slide 4 text

an observable fact or event phenomenon

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

It's not an issue of competence. It's an issue of confidence.

Slide 7

Slide 7 text

It me!

Slide 8

Slide 8 text

Image: Marco Zecchin for SERA Architects (with permission) LinkedIn

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Maybe I don’t belong here.

Slide 14

Slide 14 text

Nevertheless, she persisted.

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

2020

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Eureka!

Slide 21

Slide 21 text

TODO Surround yourself with supportive people. Own your accomplishments. Learn to take your mistakes in stride. See yourself as a work in progress. Train yourself not to need external validation. Realize there’s no shame in asking for help. Use positive a ffi rmations. Say “yes” to opportunities. Visualize success. Go to therapy. Do some yoga. Embrace feeling like an imposter. Decide to be con fi dent.

Slide 22

Slide 22 text

Convert your app to TypeScript to foster confidence in your engineering team. 👍👍

Slide 23

Slide 23 text

Don’t panic.

Slide 24

Slide 24 text

What even is a “type”? • What kind of data • What you can and cannot do

Slide 25

Slide 25 text

primitive-types.js 1; 'Hello, EmberConf!'; true; 9007199254740991n; Symbol(); undefined; null; number string boolean bigint symbol undefined null Primitives

Slide 26

Slide 26 text

primitive-types.js typeof 1; //=> 'number' typeof 'Hello, EmberConf!'; //=> 'string' typeof true; //=> 'boolean' typeof 9007199254740991n; //=> 'bigint' typeof Symbol(); //=> 'symbol' typeof undefined; //=> 'undefined' typeof null; //=> 'object' null === null; //=> true 👋 handwave 👋 handwave Making typeof null === 'null' would break the internet Primitives

Slide 27

Slide 27 text

Structural types structural/object.js typeof { hello: 'EmberConf!' }; //=> 'object' typeof ['Hello', 'EmberConf!']; //=> 'object' typeof new Set(['Hello', 'EmberConf!']); //=> 'object' typeof new Map([['Hello', 'EmberConf!']]); //=> 'object' class Tomster {} let tomster = new Tomster(); typeof tomster; //=> 'object' object

Slide 28

Slide 28 text

👋 handwave 👋 handwave For framework code or when using iFrames, you might not want to use instanceof either. Structural types structural/object.js class Tomster {} let tomster = new Tomster(); tomster instanceof Tomster; //=> true Array.isArray(['Hello', 'EmberConf!']); //=> true

Slide 29

Slide 29 text

Structural types structural/function.js function hello(conf = 'EmberConf') { return `Hello, ${conf}!`; } typeof hello; //=> 'function' hello(); //=> 'Hello, EmberConf!' hello.call(this, 'RailsConf'); //=> 'Hello, RailsConf!' hello.hola = 'Hola!'; hello.hola; 'Hola!' object

Slide 30

Slide 30 text

Wut? “JavaScript is a loosely typed and dynamic language.”

Slide 31

Slide 31 text

loosely-typed/reassignment.js let year = 2021; typeof year; //=> 'number' year = 'two thousand and twenty one'; typeof year; //=> 'string'; Loosey goosey

Slide 32

Slide 32 text

loosely-typed/coercion.js 2 + 2; //=> 4 2 + '2'; //=> '22' 2 + [2]; //=> '22' 2 + true; //=> 3 2 + null; //=> 2 2 + undefined; //=> NaN 2 + new Set([2]); //=> '2[object Set]’ Loosey goosey 🤪

Slide 33

Slide 33 text

My Unassuming JavaScript Website Thank you for coming to my website! I wrote some loosely typed and dynamic JavaScript. 2 + 2 = 22 LOLOL Here’s a cat pic. /\_/\ ( o.o ) > ^ < www.mywebsite.com A real dynamo Uncaught TypeError: Cannot read property 'oops' of undefined Uncaught TypeError: undefined is not a function Uncaught TypeError: Cannot set property 'foo' of undefined

Slide 34

Slide 34 text

Meme: KC Greene

Slide 35

Slide 35 text

What could go wrong 1. Uncaught TypeError: Cannot read property 'oops' of undefined 2. TypeError: 'undefined' is not an object (evaluating 'undefined.oops') 3. TypeError: null is not an object (evaluating 'null.oops') 5. TypeError: Object doesn’t support property or method 'oops' 6. Uncaught TypeError: undefined is not a function 8. Uncaught TypeError: Cannot read property 'length' of undefined 9. Uncaught TypeError: Cannot set property 'oops' of undefined 10. ReferenceError: oops is not defined 🥴

Slide 36

Slide 36 text

Uncertainty is the enemy of confidence.

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

JavaScript syntax shout.js function shout(message) { return message.toUpperCase(); } shout('hello'); //=> 'HELLO'

Slide 39

Slide 39 text

shout.ts function shout(message: string) { return message.toUpperCase(); } shout('hello'); //=> 'HELLO' TypeScript syntax

Slide 40

Slide 40 text

TypeScript compiler (tsc) shout.ts function shout(message: string) { return message.toUpperCase(); } shout('hello'); //=> 'HELLO' Terminal $ yarn tsc shout.ts shout.js (compiled) function shout(message) { return message.toUpperCase(); } shout('hello'); //=> 'HELLO'

Slide 41

Slide 41 text

TypeScript compiler (tsc) shout.ts function shout(message: string) { return message.toUpperCase(); } shout('hello'); //=> 'HELLO' shout(42); Terminal $ yarn tsc shout.ts $ yarn tsc shout.ts shout.ts:8:7 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. 8 shout(42); ~~ Found 1 error. shout.js (compiled) function shout(message) { return message.toUpperCase(); } shout('hello'); //=> ‘HELLO' shout(42); //=> Uncaught TypeError: message.toUpperCase is not a function

Slide 42

Slide 42 text

shout.ts TypeScript in your text editor

Slide 43

Slide 43 text

shout.ts TypeScript in your text editor

Slide 44

Slide 44 text

shout.ts TypeScript in your text editor

Slide 45

Slide 45 text

dom-stuff.ts TypeScript in your text editor

Slide 46

Slide 46 text

TypeScript is the answer key shout.ts shout.ts shout.ts

Slide 47

Slide 47 text

JavaScript: loose, dynamic TypeScript: strict, static

Slide 48

Slide 48 text

Statically Typed Terminal $ yarn tsc shout.ts shout.ts:8:7 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. 8 shout(42); ~~ Found 1 error.

Slide 49

Slide 49 text

statically-typed/inference.ts Statically Typed

Slide 50

Slide 50 text

statically-typed/inference.ts Statically Typed

Slide 51

Slide 51 text

statically-typed/annotation.ts Statically Typed

Slide 52

Slide 52 text

statically-typed/annotation.ts Statically Typed

Slide 53

Slide 53 text

Statically Typed* (*but also dynamically typed because it compiles to JavaScript)

Slide 54

Slide 54 text

Don’t shoot the messenger.

Slide 55

Slide 55 text

strict/reassignment-error.ts Strict

Slide 56

Slide 56 text

Strict strict/coercion-error.ts

Slide 57

Slide 57 text

strict/coercion-allowed.ts 2 + '2'; //=> '22' 2 + document.querySelector('input[type="number"]').value; //=> '22' Strict, but not TOO strict 😬

Slide 58

Slide 58 text

strict/coercion-allowed.ts let itemCount = 42; throw 'Too many items in queue! Item count: ' + itemCount; //=> Error: Too many items in queue! Item count: 42 Strict, but not TOO strict idiomatic: (adj.) everyone does it

Slide 59

Slide 59 text

strict/coercion-allowed.ts Strict, but not TOO strict tscon fi g.json { "compilerOptions": { "noImplicitAny": true, "noImplicitThis": true, "alwaysStrict": true, "strictNullChecks": true, "strictPropertyInitialization": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true,

Slide 60

Slide 60 text

Strictness → fewer bugs → confidence

Slide 61

Slide 61 text

primitives.ts let myVariable: number = 1; let myVariable: bigint = 9007199254740991n; let myVariable: string = 'Hello, EmberConf!'; let myVariable: boolean = true; let myVariable: symbol = Symbol(); let myVariable: undefined; let myVariable: null = null; Primitives in TypeScript

Slide 62

Slide 62 text

primitives.ts let myVariable = 1; let myVariable = 9007199254740991n; let myVariable = 'Hello, EmberConf!'; let myVariable = true; let myVariable = Symbol(); let myVariable; let myVariable = null; Primitives in TypeScript

Slide 63

Slide 63 text

structural/array.ts let myVariable: Array = ['Hello', 'EmberConf!']; Structural types in TypeScript generic: (adj.) a reusable type that takes another type as an argument

Slide 64

Slide 64 text

structural/function.ts function sayHello(crowd: string): string { return `Hello, ${crowd}!`; } sayHello('EmberConf'); //=> 'Hello, EmberConf!' Structural types in TypeScript

Slide 65

Slide 65 text

structural/object.ts Structural types in TypeScript interface: (noun) a syntax that enforces the shape of an object

Slide 66

Slide 66 text

Moar types!

Slide 67

Slide 67 text

Additional-types/any.ts Into the void…

Slide 68

Slide 68 text

additional-types/unknown.ts function prettyPrint(raw: unknown): string { if (typeof raw === 'string') { // TypeScript now knows that `raw` is a string return raw; } if (Array.isArray(raw)) { // TypeScript now knows that `raw` is an array return raw.join(', '); } throw '`prettyPrint` not implemented for this type'; } Into the unknown… narrow: (verb) to re fi ne a type to a more speci fi c type

Slide 69

Slide 69 text

Additional-types/any.ts let yolo: any = 'hehehe'; // TypeScript won't yell at you here yolo = null; // or here yolo.meaningOfLife; //=> TypeError: Cannot read property 'meaningOfLife' of null Into the abyss… Pro tip: You can disallow any with tsconfig and eslint-typescript rules!

Slide 70

Slide 70 text

Converting your legacy app: a meta-tutorial

Slide 71

Slide 71 text

ember-super-rentals.netlify.app

Slide 72

Slide 72 text

Ember + TypeScript = ?

Slide 73

Slide 73 text

Ember Octane + TypeScript = 🧡

Slide 74

Slide 74 text

Terminal $ ember install ember-cli-typescript 🚧 Installing packages… ember-cli-typescript, typescript, @types/ember, @types/ember-data, Etc… create tsconfig.json create app/config/environment.d.ts create types/super-rentals/index.d.ts create types/ember-data/types/registries/model.d.ts create types/global.d.ts ember-cli-typescript

Slide 75

Slide 75 text

Terminal $ ember install ember-cli-typescript 🚧 Installing packages… ember-cli-typescript, typescript, @types/ember, @types/ember-data, Etc… create tsconfig.json create app/config/environment.d.ts create types/super-rentals/index.d.ts create types/ember-data/types/registries/model.d.ts create types/global.d.ts Installed addon package. ember-cli-typescript

Slide 76

Slide 76 text

Gradual typing hacks

Slide 77

Slide 77 text

Gradual typing hacks

Slide 78

Slide 78 text

Gradual typing hacks • TypeScript declaration fi les (.d.ts) • unknown • any • @ts-expect-error

Slide 79

Slide 79 text

tscon fi g.json { "compilerOptions": { // "alwaysStrict": true, // "noImplicitAny": true, // "noImplicitThis": true, // "strictBindCallApply": true, // "strictFunctionTypes": true, // "strictNullChecks": true, // "strictPropertyInitialization": true, // ... } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 80

Slide 80 text

tscon fi g.json { "compilerOptions": { "alwaysStrict": true, // "noImplicitAny": true, // "noImplicitThis": true, // "strictBindCallApply": true, // "strictFunctionTypes": true, // "strictNullChecks": true, // "strictPropertyInitialization": true, // ... } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 81

Slide 81 text

tscon fi g.json { "compilerOptions": { "alwaysStrict": true, "noImplicitAny": true, // "noImplicitThis": true, // "strictBindCallApply": true, // "strictFunctionTypes": true, // "strictNullChecks": true, // "strictPropertyInitialization": true, // ... } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 82

Slide 82 text

tscon fi g.json { "compilerOptions": { "alwaysStrict": true, "noImplicitAny": true, "noImplicitThis": true, // "strictBindCallApply": true, // "strictFunctionTypes": true, // "strictNullChecks": true, // "strictPropertyInitialization": true, // … } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 83

Slide 83 text

tscon fi g.json { "compilerOptions": { "alwaysStrict": true, "noImplicitAny": true, "noImplicitThis": true, "strictBindCallApply": true, // "strictFunctionTypes": true, // "strictNullChecks": true, // "strictPropertyInitialization": true, // ... } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 84

Slide 84 text

tscon fi g.json { "compilerOptions": { "alwaysStrict": true, "noImplicitAny": true, "noImplicitThis": true, "strictBindCallApply": true, "strictFunctionTypes": true, // "strictNullChecks": true, // "strictPropertyInitialization": true, // ... } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 85

Slide 85 text

tscon fi g.json { "compilerOptions": { "alwaysStrict": true, "noImplicitAny": true, "noImplicitThis": true, "strictBindCallApply": true, "strictFunctionTypes": true, "strictNullChecks": true, // "strictPropertyInitialization": true, // ... } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 86

Slide 86 text

tscon fi g.json { "compilerOptions": { "alwaysStrict": true, "noImplicitAny": true, "noImplicitThis": true, "strictBindCallApply": true, "strictFunctionTypes": true, "strictNullChecks": true, "strictPropertyInitialization": true, // ... } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 87

Slide 87 text

tscon fi g.json { "compilerOptions": { "strict": true, // ... } } Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Less strict More strict

Slide 88

Slide 88 text

Terminal $ yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin Gradual strictness Or: How I Learned to Stop Worrying and Love the Strictness Ludicrous mode! 🤯 Less strict More strict

Slide 89

Slide 89 text

Where do we start?

Slide 90

Slide 90 text

Where do we start? Outer Leaves Models Routes, Services, Adapters, Serializers Inner Leaves Components, Controllers

Slide 91

Slide 91 text

Where do we start? Outer Leaves Models Routes, Services, Adapters, Serializers Inner Leaves Components, Controllers

Slide 92

Slide 92 text

Where do we start? Outer Leaves Models Routes, Services, Adapters, Serializers Inner Leaves Components, Controllers 👉 👇 👈

Slide 93

Slide 93 text

Where do we start? Outer Leaves Models Routes, Services, Adapters, Serializers Inner Leaves Components, Controllers 🥸 🤠 🤩 🤡

Slide 94

Slide 94 text

Where do we start? Outer Leaves Models Routes, Services, Adapters, Serializers Inner Leaves Components, Controllers 🥸 🤠 🤩 🤡 👉 👇 👈

Slide 95

Slide 95 text

app/models/rental.js import Model, { attr } from '@ember-data/model'; const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; export default class RentalModel extends Model { @attr title; @attr owner; @attr city; @attr location; @attr category; @attr image; @attr bedrooms; @attr description; get type() { if (COMMUNITY_CATEGORIES.includes(this.category)) { return 'Community'; } else { return 'Standalone'; } } } Models

Slide 96

Slide 96 text

app/models/rental.js import Model, { attr } from '@ember-data/model'; const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; export default class RentalModel extends Model { @attr title; @attr owner; @attr city; @attr location; @attr category; @attr image; @attr bedrooms; @attr description; get type() { if (COMMUNITY_CATEGORIES.includes(this.category)) { return 'Community'; } else { return 'Standalone'; } } } Models

Slide 97

Slide 97 text

app/models/rental.js import Model, { attr } from '@ember-data/model'; const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; export default class RentalModel extends Model { @attr title; @attr owner; @attr city; @attr location; @attr category; @attr image; @attr bedrooms; @attr description; get type() { if (COMMUNITY_CATEGORIES.includes(this.category)) { return 'Community'; } else { return 'Standalone'; } } } Models

Slide 98

Slide 98 text

app/models/rental.js import Model, { attr } from '@ember-data/model'; const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; export default class RentalModel extends Model { @attr title; @attr owner; @attr city; @attr location; @attr category; @attr image; @attr bedrooms; @attr description; get type() { if (COMMUNITY_CATEGORIES.includes(this.category)) { return 'Community'; } else { return 'Standalone'; } } } Models Terminal $ mv app/models/rental.js app/models/rental.ts

Slide 99

Slide 99 text

app/models/rental.ts import Model, { attr } from '@ember-data/model'; const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; export default class RentalModel extends Model { @attr title; @attr owner; @attr city; @attr location; @attr category; @attr image; @attr bedrooms; @attr description; get type() { if (COMMUNITY_CATEGORIES.includes(this.category)) { return 'Community'; } else { return 'Standalone'; } } } Models

Slide 100

Slide 100 text

app/models/rental.ts Models

Slide 101

Slide 101 text

app/models/rental.ts Models

Slide 102

Slide 102 text

app/models/rental.ts Models public/api/rentals.json { "data": [ { "type": "rentals", "id": "grand-old-mansion", "attributes": { "title": "Grand Old Mansion", "owner": "Veruca Salt", "city": "San Francisco", "location": { "lat": 37.7749, "lng": -122.4194 }, "category": "Estate", "image": "https://upload.wikimedia.org/mansion.jpg", "bedrooms": 15, "description": "This grand old mansion sits..." } }, // ... ] }

Slide 103

Slide 103 text

app/models/rental.ts Models

Slide 104

Slide 104 text

app/models/rental.ts Models

Slide 105

Slide 105 text

app/models/rental.ts Models

Slide 106

Slide 106 text

app/models/rental.ts Models

Slide 107

Slide 107 text

app/models/rental.ts Models

Slide 108

Slide 108 text

app/models/rental.ts Models

Slide 109

Slide 109 text

app/models/rental.ts Models

Slide 110

Slide 110 text

app/models/rental.ts Models

Slide 111

Slide 111 text

app/models/rental.ts Models

Slide 112

Slide 112 text

app/models/rental.ts Models

Slide 113

Slide 113 text

app/models/rental.ts Models Terminal $ git commit -a -m “Convert Rental Model to TS”

Slide 114

Slide 114 text

relationship-example.ts import Model, { AsyncBelongsTo, AsyncHasMany, belongsTo, hasMany, } from '@ember-data/model'; import Comment from 'my-app/models/comment'; import User from 'my-app/models/user'; export default class PostModel extends Model { @belongsTo('user') declare author: AsyncBelongsTo; @hasMany('comments') declare comments: AsyncHasMany; } Model Relationships

Slide 115

Slide 115 text

app/routes/index.js import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; export default class IndexRoute extends Route { @service store; model() { return this.store.findAll('rental'); } } Routes

Slide 116

Slide 116 text

app/routes/index.js import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; export default class IndexRoute extends Route { @service store; model() { return this.store.findAll('rental'); } } Routes Terminal $ mv app/routes/index.js app/routes/index.ts

Slide 117

Slide 117 text

app/routes/index.ts Routes

Slide 118

Slide 118 text

app/routes/index.ts Routes

Slide 119

Slide 119 text

app/routes/index.ts Routes

Slide 120

Slide 120 text

app/routes/index.ts Routes

Slide 121

Slide 121 text

app/routes/index.ts Routes

Slide 122

Slide 122 text

app/routes/index.ts Routes

Slide 123

Slide 123 text

app/routes/index.ts Routes app/models/rental.ts import Model, { attr } from '@ember-data/model'; export default class RentalModel extends Model { // ... } declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { rental: RentalModel; } }

Slide 124

Slide 124 text

app/routes/index.ts Routes

Slide 125

Slide 125 text

app/routes/index.ts Routes

Slide 126

Slide 126 text

app/routes/index.ts Routes Terminal $ git commit -a -m “Convert Index Route to TS”

Slide 127

Slide 127 text

Components ember-super-rentals.netlify.app

Slide 128

Slide 128 text

app/components/rentals/ fi lter.js import Component from '@glimmer/component'; export default class RentalsFilterComponent extends Component { get results() { let { rentals, query } = this.args; if (query) { rentals = rentals.filter((rental) => rental.title.includes(query)); } return rentals; } } Components Terminal $ mv app/components/rentals/filter.js app/components/rentals/filter.ts

Slide 129

Slide 129 text

app/components/rentals/ fi lter.ts Components

Slide 130

Slide 130 text

app/components/rentals/ fi lter.ts Components

Slide 131

Slide 131 text

app/components/rentals/ fi lter.ts Components

Slide 132

Slide 132 text

app/components/rentals/ fi lter.ts Components

Slide 133

Slide 133 text

app/components/rentals/ fi lter.ts Components

Slide 134

Slide 134 text

app/components/rentals/ fi lter.ts Components app/components/rentals.hbs {{#each results as |rental|}}
  • {{/each}} app/templates/index.hbs

    Slide 135

    Slide 135 text

    app/components/rentals/ fi lter.ts Components

    Slide 136

    Slide 136 text

    app/components/rentals/ fi lter.ts Components 🐛

    Slide 137

    Slide 137 text

    app/components/rentals/ fi lter.ts Components 🐛

    Slide 138

    Slide 138 text

    app/components/rentals/ fi lter.ts Components

    Slide 139

    Slide 139 text

    app/components/rentals.ts Components import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import IndexRoute from 'super-rentals/routes'; import { ModelFrom } from 'super-rentals/types/util'; interface RentalsArgs { rentals: ModelFrom; } export default class RentalsComponent extends Component { @tracked query = ''; }

    Slide 140

    Slide 140 text

    app/components/rentals/ fi lter.ts Components

    Slide 141

    Slide 141 text

    app/components/rentals/ fi lter.ts Components Terminal $ git commit -a -m “Convert Rentals::Filter to TS”

    Slide 142

    Slide 142 text

    Components app/components/map.js import Component from '@glimmer/component'; import ENV from 'super-rentals/config/environment'; export default class MapComponent extends Component { get src() { let { lng, lat } = this.args; return `${ENV.MAPBOX_URL}/${lng},${lat}`; } }

    Slide 143

    Slide 143 text

    Components app/components/map.js import Component from '@glimmer/component'; import ENV from 'super-rentals/config/environment'; export default class MapComponent extends Component { get src() { let { lng, lat } = this.args; return `${ENV.MAPBOX_URL}/${lng},${lat}`; } } Terminal $ mv app/components/map.js app/components/map.ts

    Slide 144

    Slide 144 text

    Components app/components/map.ts

    Slide 145

    Slide 145 text

    Components app/components/map.ts

    Slide 146

    Slide 146 text

    Components app/components/map.ts app/components/rental.hbs

    Slide 147

    Slide 147 text

    Components app/components/map.ts Thinking in TypeScript

    Slide 148

    Slide 148 text

    Components app/components/map.ts Thinking in TypeScript

    Slide 149

    Slide 149 text

    Components app/components/map.ts Thinking in TypeScript (Once we have template type-checking, this won’t be necessary.)

    Slide 150

    Slide 150 text

    Components app/components/map.ts Terminal $ git commit -a -m “Convert Map to TS”

    Slide 151

    Slide 151 text

    No content

    Slide 152

    Slide 152 text

    app/components/map.js /** * @typedef {object} MapArgs * @property {number | undefined} lng * @property {number | undefined} lat */ /** @type {Component} */ export default class MapComponent extends Component { /** @type {number} */ get lng() { assert('Please provide `lng` arg', this.args.lng); return this.args.lng; } /** @type {number} */ get lat() { assert('Please provide `lat` arg', this.args.lat); return this.args.lat; } /** @type {string} */ get src() { return `${ENV.MAPBOX_URL}/${this.lng},${this.lat}`; } } TS without TS JSDoc + VSCode

    Slide 153

    Slide 153 text

    app/components/map.js // @ts-check /** * @typedef {object} MapArgs * @property {number | undefined} lng * @property {number | undefined} lat */ /** @type {Component} */ export default class MapComponent extends Component { /** @type {number} */ get lng() { assert('Please provide `lng` arg', this.args.lng); return this.args.lng; } /** @type {number} */ get lat() { assert('Please provide `lat` arg', this.args.lat); return this.args.lat; } /** @type {string} */ get src() { return `${ENV.MAPBOX_URL}/${this.lng},${this.lat}`; } } TS without TS JSDoc + VSCode

    Slide 154

    Slide 154 text

    The real benefits of TypeScript

    Slide 155

    Slide 155 text

    Confidence to refactor the crufty stuff

    Slide 156

    Slide 156 text

    Confidence that your code will JustWorkTM

    Slide 157

    Slide 157 text

    Confidence to contribute to open source

    Slide 158

    Slide 158 text

    Confidence to try other strictly typed languages

    Slide 159

    Slide 159 text

    Confidence to make you listen to this talk