├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src ├── 1-Course-Introduction │ ├── index.html │ └── index.ts ├── 2-Getting-Started-With-TypeScript │ ├── index.html │ └── index.ts ├── 3-Using-Variables-Types-And-Enums │ ├── index.html │ └── index.ts ├── 4-Creating-And-Using-Functions │ ├── index.html │ ├── index.ts │ └── output.ts ├── 5-Creating-And-Using-Interfaces │ ├── index.html │ ├── index.ts │ └── output.ts ├── 6-Creating-And-Using-Classes │ ├── index.html │ ├── index.ts │ └── output.ts ├── 7-Creating-And-Using-Generics │ ├── index.html │ └── index.ts ├── _hello-ts │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── api │ └── products.json ├── css │ ├── _bulma.scss │ ├── _styles.scss │ ├── _variables.scss │ └── style.scss ├── images │ └── ts.svg └── lib │ ├── config.ts │ ├── index.ts │ ├── interfaces.ts │ ├── product.ts │ └── products.ts ├── tsconfig.json └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#3399ff", 4 | "activityBar.activeBorder": "#bf0060", 5 | "activityBar.background": "#3399ff", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#bf0060", 9 | "activityBarBadge.foreground": "#e7e7e7", 10 | "statusBar.background": "#007fff", 11 | "statusBar.foreground": "#e7e7e7", 12 | "statusBarItem.hoverBackground": "#3399ff", 13 | "titleBar.activeBackground": "#007fff", 14 | "titleBar.activeForeground": "#e7e7e7", 15 | "titleBar.inactiveBackground": "#007fff99", 16 | "titleBar.inactiveForeground": "#e7e7e799" 17 | }, 18 | "peacock.color": "007fff" 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dan Wahlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript Fundamentals 2 | 3 | Code samples from the TypeScript Fundamentals course on [Pluralsight.com](https://pluralsight.com). Samples for each module from the course can be found in the `src` directory. 4 | 5 | ## Running the Project 6 | 7 | 1. Install Node.js LTS ([https://nodejs.org](https://nodejs.org)) 8 | 1. Open a command window at the root of this project 9 | 1. Run `npm install` 10 | 1. Run `npm start` to start the web server and run the app 11 | 12 | **NOTE:** 13 | 14 | Running `npm start` compiles your TypeScript and loads the JavaScript bundle into memory (you won't see a `dist` folder created in this case). 15 | If you want to compile your TypeScript and create a bundle in the `dist` folder you can run `npm run webpack` or 16 | `npm run webpack:w` (if you want webpack to watch for changes to files). 17 | 18 | ## Credits 19 | 20 | Font Awesome Free icons are being used as per the kit guidelines https://fontawesome.com/kits 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 24 |
25 | 26 |
27 |
28 |
29 |

TypeScript Fundamentals

30 |
31 | 66 |
67 |
68 |
69 | 70 | 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-fundamentals", 3 | "version": "1.0.0", 4 | "description": "TypeScript Fundamentals course examples.", 5 | "contributors": [ 6 | { 7 | "name": "Dan Wahlin", 8 | "url": "https://www.twitter.com/danwahlin" 9 | }, 10 | { 11 | "name": "John Papa", 12 | "url": "https://www.twitter.com/john_papa" 13 | } 14 | ], 15 | "scripts": { 16 | "webpack": "webpack --mode development", 17 | "webpack:w": "webpack --mode development --watch", 18 | "start": "webpack serve --mode development --open", 19 | "pretty": "prettier -w ./src/*" 20 | }, 21 | "author": "", 22 | "private": true, 23 | "license": "MIT", 24 | "devDependencies": { 25 | "bulma": "^0.9.3", 26 | "copy-webpack-plugin": "^9.0.1", 27 | "css-loader": "^6.2.0", 28 | "html-webpack-plugin": "^5.3.2", 29 | "mini-css-extract-plugin": "^2.2.0", 30 | "node-sass": "^6.0.1", 31 | "prettier": "^2.3.2", 32 | "sass-loader": "^12.1.0", 33 | "style-loader": "^3.2.1", 34 | "ts-loader": "^9.2.3", 35 | "typescript": "^4.3.5", 36 | "webpack-cli": "^4.7.2", 37 | "webpack-dev-server": "^3.11.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/1-Course-Introduction/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 20 |
21 | 22 |
23 |
24 |
25 |

Course Introduction

26 |
27 | Enjoy the TypeScript Fundamentals course! 28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/1-Course-Introduction/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-fundamentals/e0031dbc97aa68399e14514827f95603022cd5b9/src/1-Course-Introduction/index.ts -------------------------------------------------------------------------------- /src/2-Getting-Started-With-TypeScript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 20 |
21 | 22 |
23 |
24 |
25 |

Getting Started with TypeScript

26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/2-Getting-Started-With-TypeScript/index.ts: -------------------------------------------------------------------------------- 1 | let x: number = 7; 2 | let y: number = 11; 3 | const sum: number = x + y; 4 | const result: string = `The sum of ${x} and ${y} is ${sum}`; 5 | console.log(result); 6 | 7 | const output = document.querySelector('#output'); 8 | 9 | if (output) { 10 | output.innerHTML = result; 11 | } 12 | -------------------------------------------------------------------------------- /src/3-Using-Variables-Types-And-Enums/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 20 |
21 | 22 |
23 |
24 |
25 |

Using Variables, Types, and Enums

26 |
27 | View the console in the devtools to view the code output. 28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/3-Using-Variables-Types-And-Enums/index.ts: -------------------------------------------------------------------------------- 1 | // string, number, boolean, array, undefined, null, any 2 | 3 | let firstName: string | null; 4 | firstName = 'Dan'; 5 | 6 | let age: number; 7 | age = 45; 8 | 9 | let hasPurchased = true; 10 | 11 | let productNames: string[] = []; 12 | productNames.push('Basketball'); 13 | 14 | let petCount: number[] = []; 15 | petCount.push(5); 16 | 17 | console.log(firstName, age, hasPurchased, productNames, petCount); 18 | 19 | let productType = 'sports'; // homeGoods, grocery ("magic string") 20 | if (productType === 'sports') { 21 | console.log('Found sports product type.'); 22 | } 23 | 24 | // Using Enums 25 | enum ProductType { 26 | Sports, 27 | HomeGoods, 28 | Groceries, 29 | } 30 | let pt = ProductType.Sports; 31 | if (pt === ProductType.Sports) { 32 | console.log('Found sports product type.'); 33 | } 34 | -------------------------------------------------------------------------------- /src/4-Creating-And-Using-Functions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 |
11 |
12 | 24 |
25 | 26 |
27 |
28 |
29 |

Creating and Using Functions

30 |
31 |
32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/4-Creating-And-Using-Functions/index.ts: -------------------------------------------------------------------------------- 1 | import updateOutput from './output'; 2 | 3 | updateOutput('output'); 4 | -------------------------------------------------------------------------------- /src/4-Creating-And-Using-Functions/output.ts: -------------------------------------------------------------------------------- 1 | import { productsURL } from '../lib'; 2 | 3 | export default async function updateOutput(id: string) { 4 | const products = await getProducts(); 5 | const output = document.querySelector(`#${id}`); 6 | const html = layoutProducts(products); 7 | 8 | if (output && html) { 9 | output.innerHTML = html; 10 | } 11 | } 12 | 13 | function layoutProducts( 14 | products: { id: number; name: string; icon: string }[], 15 | ) { 16 | const items = products.map((product) => { 17 | const { id, name, icon } = product; 18 | const productHtml = ` 19 | #${id} 20 | 21 | ${name} 22 | `; 23 | const cardHtml = ` 24 |
  • 25 |
    26 |
    27 |
    28 | ${productHtml} 29 |
    30 |
    31 |
    32 |
  • 33 | `; 34 | return cardHtml; 35 | }); 36 | let productsHtml = ``; 37 | return productsHtml; 38 | } 39 | 40 | async function getProducts(): Promise< 41 | { id: number; name: string; icon: string }[] 42 | > { 43 | const response: Response = await fetch(productsURL); 44 | const products: { id: number; name: string; icon: string }[] = 45 | await response.json(); 46 | return products; 47 | } 48 | -------------------------------------------------------------------------------- /src/5-Creating-And-Using-Interfaces/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 |
    11 |
    12 | 24 |
    25 | 26 |
    27 |
    28 |
    29 |

    Creating and Using Interfaces

    30 |
    31 |
    32 |
    33 |
    34 |
    35 | 36 | 37 | -------------------------------------------------------------------------------- /src/5-Creating-And-Using-Interfaces/index.ts: -------------------------------------------------------------------------------- 1 | import updateOutput from './output'; 2 | 3 | updateOutput('output'); 4 | -------------------------------------------------------------------------------- /src/5-Creating-And-Using-Interfaces/output.ts: -------------------------------------------------------------------------------- 1 | import { getProducts, Product } from '../lib'; 2 | 3 | export default async function updateOutput(id: string) { 4 | const products = await getProducts(); 5 | const output = document.querySelector(`#${id}`); 6 | const html = layoutProducts(products); 7 | 8 | if (output && html) { 9 | output.innerHTML = html; 10 | } 11 | } 12 | 13 | function layoutProducts(products: Product[]) { 14 | const items = products.map((product) => { 15 | const { id, name, icon } = product; 16 | const productHtml = ` 17 | #${id} 18 | 19 | ${name} 20 | `; 21 | const cardHtml = ` 22 |
  • 23 |
    24 |
    25 |
    26 | ${productHtml} 27 |
    28 |
    29 |
    30 |
  • 31 | `; 32 | return cardHtml; 33 | }); 34 | let productsHtml = ``; 35 | return productsHtml; 36 | } 37 | -------------------------------------------------------------------------------- /src/6-Creating-And-Using-Classes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 |
    11 |
    12 | 24 |
    25 | 26 |
    27 |
    28 |
    29 |

    Creating and Using Classes

    30 |
    31 |
    32 |
    33 |
    34 |
    35 |
    36 | 75 |
    76 |
    77 |
    78 |
    79 |
    80 | 81 | 82 | -------------------------------------------------------------------------------- /src/6-Creating-And-Using-Classes/index.ts: -------------------------------------------------------------------------------- 1 | import productOutput from './output'; 2 | 3 | productOutput.updateOutput('output'); 4 | -------------------------------------------------------------------------------- /src/6-Creating-And-Using-Classes/output.ts: -------------------------------------------------------------------------------- 1 | import { getProducts, Product } from '../lib'; 2 | import { FoodProduct } from '../lib/product'; 3 | 4 | class ProductOutput { 5 | products: Product[] = []; 6 | form = this.getElement('#form'); 7 | output = this.getElement('#output'); 8 | 9 | constructor() { 10 | this.form.addEventListener('click', (event) => this.submitForm(event)); 11 | } 12 | 13 | async updateOutput(id: string) { 14 | this.products = await getProducts(); 15 | const html = this.layoutProducts(); 16 | 17 | if (this.output && html) { 18 | this.output.innerHTML = html; 19 | 20 | // Add click handler 21 | this.output.addEventListener('click', (event) => { 22 | const node = event.target as HTMLElement; 23 | if (node.id) { 24 | this.showForm(+node.id); 25 | } 26 | }); 27 | } 28 | } 29 | 30 | private layoutProducts() { 31 | const items = this.products.map((product) => { 32 | const { id, name, icon } = product; 33 | const productHtml = ` 34 | #${id} 35 | 36 | ${name} 37 | Edit 38 | `; 39 | 40 | const cardHtml = ` 41 |
  • 42 |
    43 |
    44 |
    45 | ${productHtml} 46 |
    47 |
    48 |
    49 |
  • 50 | `; 51 | return cardHtml; 52 | }); 53 | 54 | let productsHtml = ``; 55 | return productsHtml; 56 | } 57 | 58 | private getProduct(id: number) { 59 | const products = this.products.filter((product) => product.id === id); 60 | if (products.length) { 61 | return products[0]; 62 | } 63 | return null; 64 | } 65 | 66 | private showForm(id: number) { 67 | this.getElement('.notification').classList.add('is-hidden'); 68 | const product = this.getProduct(id); 69 | if (product) { 70 | // Update form fields 71 | this.getElement('.form-wrapper').id = id.toString(); 72 | this.getFormElement('#product-name').value = product.name; 73 | this.getFormElement('#product-icon').value = product.icon; 74 | this.form.classList.remove('is-hidden'); 75 | } 76 | } 77 | 78 | private submitForm(event: Event) { 79 | const target = event.target as HTMLElement; 80 | if (target.tagName.toUpperCase() === 'BUTTON') { 81 | if (target.innerText.toUpperCase() === 'SUBMIT') { 82 | const id = +this.getElement('div.form-wrapper').id; 83 | const name = this.getFormElement('#product-name'); 84 | const icon = this.getFormElement('#product-icon'); 85 | 86 | // Validate data for product 87 | const product = new FoodProduct(id, name.value, icon.value); 88 | if (product.validate()) { 89 | const index = this.products.findIndex((p) => p.id === id); 90 | this.products[index] = product; 91 | const html = this.layoutProducts(); 92 | this.output.innerHTML = html; 93 | this.hideForm(); 94 | } else { 95 | this.getElement('.notification').classList.remove('is-hidden'); 96 | } 97 | } else { 98 | this.hideForm(); 99 | } 100 | } 101 | } 102 | 103 | private hideForm() { 104 | this.form?.classList.add('is-hidden'); 105 | } 106 | 107 | private getElement(selector: string) { 108 | return document.querySelector(selector) as HTMLElement; 109 | } 110 | 111 | private getFormElement(selector: string) { 112 | return this.getElement(selector) as HTMLFormElement; 113 | } 114 | } 115 | 116 | export default new ProductOutput(); 117 | -------------------------------------------------------------------------------- /src/7-Creating-And-Using-Generics/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
    7 |
    8 | 20 |
    21 | 22 |
    23 |
    24 |
    25 |

    Creating and Using Generics

    26 |
    27 | TODO 28 |
    29 |
    30 |
    31 | 32 | 33 | -------------------------------------------------------------------------------- /src/7-Creating-And-Using-Generics/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-fundamentals/e0031dbc97aa68399e14514827f95603022cd5b9/src/7-Creating-And-Using-Generics/index.ts -------------------------------------------------------------------------------- /src/_hello-ts/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains the "hello world" example shown in 3 | * the beginning of the TypeScript Fundamentals course. 4 | * 5 | * This code is not intended to be executed 6 | * and is for demonstration purposes, only. 7 | * 8 | * The code is contained inside of an anonymous function, 9 | * which allows it to compile as its scope is contained to the function. 10 | */ 11 | () => { 12 | let x: number = 7; 13 | let y: number = 11; 14 | const sum: number = x + y; 15 | const result: string = `The sum of ${x} and ${y} is ${sum}`; 16 | console.log(result); 17 | }; 18 | -------------------------------------------------------------------------------- /src/_hello-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-ts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "tsc": "tsc" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "typescript": "^4.3.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/_hello-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true /* Generates corresponding '.map' file. */, 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist" /* Redirect output structure to the directory. */, 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true /* Skip type checking of declaration files. */, 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/api/products.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 10, 4 | "name": "Pizza slice", 5 | "icon": "fas fa-pizza-slice" 6 | }, 7 | { 8 | "id": 20, 9 | "name": "Ice cream", 10 | "icon": "fas fa-ice-cream" 11 | }, 12 | { 13 | "id": 30, 14 | "name": "Cheese", 15 | "icon": "fas fa-cheese" 16 | }, 17 | { 18 | "id": 40, 19 | "name": "Bacon", 20 | "icon": "fas fa-bacon" 21 | }, 22 | { 23 | "id": 50, 24 | "name": "Fish", 25 | "icon": "fas fa-fish" 26 | }, 27 | { 28 | "id": 60, 29 | "name": "Apple", 30 | "icon": "fas fa-apple-alt" 31 | }, 32 | { 33 | "id": 70, 34 | "name": "Lemon", 35 | "icon": "fas fa-lemon" 36 | }, 37 | { 38 | "id": 80, 39 | "name": "Hot pepper", 40 | "icon": "fas fa-pepper-hot" 41 | }, 42 | { 43 | "id": 90, 44 | "name": "Cookie", 45 | "icon": "fas fa-cookie" 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /src/css/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /src/css/_styles.scss: -------------------------------------------------------------------------------- 1 | html > body { 2 | margin: 0 !important; 3 | } 4 | 5 | .main-section { 6 | padding: 1rem 1rem; 7 | } 8 | 9 | .title { 10 | font-size: 2rem; 11 | font-weight: 600; 12 | line-height: 1.125; 13 | word-break: break-word; 14 | } 15 | .sub-title { 16 | font-size: 1.5rem; 17 | font-weight: 600; 18 | line-height: 1; 19 | word-break: break-word; 20 | } 21 | 22 | .main-menu { 23 | margin: 20px; 24 | } 25 | 26 | header { 27 | font-weight: bold; 28 | font-family: Arial; 29 | span { 30 | letter-spacing: 0px; 31 | &.brand-first { 32 | color: #fff; 33 | } 34 | &.brand-second { 35 | color: #ccc; 36 | } 37 | &.brand-third { 38 | color: $typescript-light; 39 | } 40 | } 41 | .navbar-item.nav-home { 42 | border: 3px solid transparent; 43 | border-radius: 0%; 44 | &:hover { 45 | border-right: 3px solid $primary-light; 46 | border-left: 3px solid $primary-light; 47 | } 48 | } 49 | .fab { 50 | font-size: 24px; 51 | &.js-logo { 52 | color: $javascript; 53 | } 54 | } 55 | .buttons { 56 | i.fab { 57 | color: #fff; 58 | margin-left: 20px; 59 | margin-right: 10px; 60 | &:hover { 61 | color: $primary; 62 | } 63 | } 64 | } 65 | } 66 | 67 | .content-title-group { 68 | margin-bottom: 16px; 69 | h2 { 70 | border-left: 16px solid $primary; 71 | border-bottom: 2px solid $primary; 72 | padding-left: 8px; 73 | padding-right: 16px; 74 | display: inline-block; 75 | text-transform: uppercase; 76 | color: #555; 77 | letter-spacing: 0px; 78 | &:hover { 79 | color: $link; 80 | } 81 | } 82 | } 83 | 84 | button.button { 85 | border: 0; 86 | color: $accent-color; 87 | &:hover { 88 | color: $typescript; //$link; 89 | } 90 | } 91 | 92 | div.card-content { 93 | background-color: $shade-light; 94 | color: #999; 95 | .card-name { 96 | font-size: 24px; 97 | color: #000; 98 | padding-right: 20px; 99 | } 100 | .card-id { 101 | font-size: 20px; 102 | padding-right: 20px; 103 | } 104 | .card-icon { 105 | padding-right: 20px; 106 | } 107 | background-color: $shade-light; 108 | } 109 | .card { 110 | margin-bottom: 1em; 111 | width: 400px; 112 | } 113 | 114 | p.card-header-title { 115 | background-color: $primary; 116 | text-transform: uppercase; 117 | letter-spacing: 4px; 118 | color: #fff; 119 | display: block; 120 | padding-left: 24px; 121 | } 122 | 123 | .navbar { 124 | margin-right: -6px; 125 | } 126 | 127 | #message-box { 128 | margin: 2em; 129 | > * { 130 | padding: 12px; 131 | } 132 | } 133 | 134 | nav .field select { 135 | width: 150px; 136 | } 137 | 138 | .modal-card { 139 | width: 400px; 140 | } 141 | .modal-card-foot button { 142 | display: inline-block; 143 | width: 80px; 144 | } 145 | 146 | .modal-card-head, 147 | .modal-card-body { 148 | text-align: center; 149 | } 150 | 151 | .message-body { 152 | white-space: pre; 153 | } 154 | 155 | .progress { 156 | margin: 16px 0; 157 | } 158 | 159 | .button-bar { 160 | margin: 16px 0; 161 | } 162 | -------------------------------------------------------------------------------- /src/css/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #294e80; 4 | $typescript-light: #88adde; //#4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /src/css/style.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 8 | -------------------------------------------------------------------------------- /src/images/ts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/config.ts: -------------------------------------------------------------------------------- 1 | export const productsURL: string = '../api/products.json'; 2 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './interfaces'; 3 | export * from './products'; 4 | -------------------------------------------------------------------------------- /src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | id: number; 3 | name: string; 4 | icon: string; 5 | description?: string; 6 | validate(): boolean; 7 | } 8 | 9 | // Examples of using a type alias 10 | type ProductAlias = 11 | | string 12 | | number 13 | | { 14 | id: number; 15 | name: string; 16 | icon: string; 17 | description?: string; 18 | }; 19 | 20 | let product: ProductAlias = 'Food'; 21 | 22 | // Using a type alias versus an enum 23 | enum ProductType { 24 | Sporting, 25 | Home, 26 | } 27 | 28 | type ProductTypeList = 'SPORTING' | 'HOME'; 29 | let p: ProductTypeList = 'SPORTING'; 30 | -------------------------------------------------------------------------------- /src/lib/product.ts: -------------------------------------------------------------------------------- 1 | import { Product } from './interfaces'; 2 | 3 | // This will act as the foundation for other Product type classes (FoodProduct, SportingProduct) 4 | abstract class ProductBase implements Product { 5 | constructor(public id: number, public name: string, public icon: string) {} 6 | validate(): boolean { 7 | throw new Error('Not implemented'); 8 | } 9 | } 10 | 11 | export class FoodProduct extends ProductBase { 12 | validate(): boolean { 13 | return !!this.id && !!this.name && !!this.icon; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/products.ts: -------------------------------------------------------------------------------- 1 | import { productsURL } from './config'; 2 | import { Product } from './interfaces'; 3 | 4 | export async function getProducts(): Promise { 5 | const response: Response = await fetch(productsURL); 6 | const products: Product[] = await response.json(); 7 | return products; 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true /* Generates corresponding '.map' file. */, 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist" /* Redirect output structure to the directory. */, 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true /* Skip type checking of declaration files. */, 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | const CopyPlugin = require('copy-webpack-plugin'); 6 | 7 | const excludeDirs = ['css', 'images', 'lib', 'api', '_hello-ts']; 8 | const htmlPlugins = generateHtmlPlugins('./src', excludeDirs); 9 | const entryPoints = generateEntryPoints('./src', 'index.ts', excludeDirs); 10 | 11 | module.exports = { 12 | entry: entryPoints, 13 | output: { 14 | filename: '[name]/index.js', 15 | path: path.resolve(__dirname, 'dist'), 16 | }, 17 | devtool: 'inline-source-map', 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.ts?$/, 22 | use: 'ts-loader', 23 | exclude: /node_modules/, 24 | }, 25 | { 26 | test: /\.(sa|sc|c)ss$/, 27 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 28 | }, 29 | ], 30 | }, 31 | resolve: { 32 | extensions: ['.ts', '.js'], 33 | }, 34 | plugins: [ 35 | new MiniCssExtractPlugin({ filename: '[name]/style.css' }), 36 | new CopyPlugin({ 37 | patterns: [ 38 | { from: 'src/images', to: 'images' }, 39 | { from: 'src/api', to: 'api' }, 40 | ], 41 | }), 42 | new HtmlWebpackPlugin({ 43 | template: './index.html', 44 | inject: false, 45 | }), 46 | ].concat(htmlPlugins), 47 | devServer: { 48 | contentBase: './dist', 49 | compress: true, 50 | port: 9000, 51 | }, 52 | }; 53 | 54 | // Create dist/templateDir/*.html file for each templateDir found in root 55 | // Only include bundle/chunk associated with HTML file 56 | function generateHtmlPlugins(root, excludeDirs) { 57 | let plugins = []; 58 | const rootDir = fs.readdirSync(path.resolve(__dirname, root)); 59 | // Find directories in root folder 60 | rootDir.forEach((templateDir) => { 61 | const stats = fs.lstatSync(path.resolve(__dirname, root, templateDir)); 62 | if (stats.isDirectory() && !excludeDirs.includes(templateDir)) { 63 | // Read files in template directory 64 | const dirName = templateDir; 65 | const templateFiles = fs.readdirSync( 66 | path.resolve(__dirname, root, templateDir), 67 | ); 68 | templateFiles.forEach((item) => { 69 | // Split names and extension 70 | const parts = item.split('.'); 71 | const name = parts[0]; 72 | const extension = parts[1]; 73 | // If we find an html file then create an HtmlWebpackPlugin 74 | if (extension === 'html') { 75 | // Create new HTMLWebpackPlugin with options 76 | plugins.push( 77 | new HtmlWebpackPlugin({ 78 | filename: `${dirName}/index.html`, 79 | template: path.resolve( 80 | __dirname, 81 | `${root}/${templateDir}/${name}.${extension}`, 82 | ), 83 | inject: 'body', 84 | // Only include bundle/chunk associated with the templateDir directory 85 | chunks: [`${dirName}`], 86 | }), 87 | ); 88 | } 89 | }); 90 | } 91 | }); 92 | return plugins; 93 | } 94 | 95 | // Create an entry point for each directory found in 'root'. 96 | // This will also create a bundle/chunk for each directory 97 | // and place it in the dist/[templateDir] directory. 98 | function generateEntryPoints(root, entryScript, excludeDirs) { 99 | const rootDir = fs.readdirSync(path.resolve(__dirname, root)); 100 | let entryPoints = { css: './src/css/style.scss' }; 101 | rootDir.forEach((templateDir) => { 102 | const stats = fs.lstatSync(path.resolve(__dirname, root, templateDir)); 103 | if (stats.isDirectory() && !excludeDirs.includes(templateDir)) { 104 | entryPoints[templateDir] = `${root}/${templateDir}/${entryScript}`; 105 | } 106 | }); 107 | return entryPoints; 108 | } 109 | --------------------------------------------------------------------------------