├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
25 |
26 |
27 |
28 |
29 |
Creating and Using Classes
30 |
31 |
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 |
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 |
--------------------------------------------------------------------------------