├── .gitignore ├── LICENSE.md ├── README.md ├── declarations.d.ts ├── package-lock.json ├── package.json ├── preact.config.js ├── src ├── assets │ ├── favicon.ico │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── mstile-150x150.png ├── components │ ├── app.tsx │ └── header │ │ ├── index.tsx │ │ └── style.css ├── index.js ├── manifest.json ├── routes │ ├── home │ │ ├── index.tsx │ │ └── style.css │ └── profile │ │ ├── index.tsx │ │ └── style.css └── style │ └── index.css └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | *.orig 5 | *.log 6 | 7 | build/ 8 | .awcache/ 9 | node_modules/ 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Mike Engel 2 | 3 | This software is released under the MIT license: 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # preact-cli-typescript-example 2 | 3 | > An example of how to use typescript with preact-cli and all its goodies 4 | 5 | # Why? 6 | 7 | [Preact](https://preactjs.com) and [preact-cli](https://github.com/developit/preact-cli) are awesome! [Typescript](https://typescriptlang.org) is awesome! Trying to use them together out of the box, however, is not so awesome. There are some cool projects like [`preact-cli-plugin-typescript`](https://github.com/wub/preact-cli-plugin-typescript) help a little, but I still found myself fighting to get it to build and include ideas like code splitting. So that's why I made this repo while working on a side project. 8 | 9 | # Installing 10 | 11 | Begin by cloning or forking this repo with git or github. Once you have it on your machine, run the following commands. 12 | 13 | ```sh 14 | # install dependencies 15 | npm install 16 | 17 | # start the web app and development server 18 | npm start 19 | ``` 20 | 21 | At this point, you should be able to open your browser and go to [localhost:8080](http://localhost:8080). You'll notice that the app compiles and also includes code splitting! 22 | 23 | # Deploying 24 | 25 | While this app doesn't do much on it's own, if you want to deploy it somewhere to see the lighthouse score or offline capabilities, run the build command and serve/upload those files somewhere. I recommend [now.sh](https://now.sh). 26 | 27 | ```sh 28 | # build the app into a static set of files 29 | npm run build 30 | 31 | # deploy somewhere. This example uses now.sh 32 | now ./build 33 | ``` 34 | 35 | # Contributing 36 | 37 | I'd love to get your feedback and suggestions via issues and pull requests on this repo! All I ask is that you follow the [code of conduct](#code-of-conduct). 38 | 39 | # [Code of Conduct](CODE_OF_CONDUCT.md) 40 | 41 | Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. 42 | 43 | # [License](LICENSE.md) 44 | -------------------------------------------------------------------------------- /declarations.d.ts: -------------------------------------------------------------------------------- 1 | // Prevent typescript from complaining about css requires 2 | declare module "*.css" { 3 | const content: any; 4 | export default content; 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preact-cli-typescript-example", 3 | "version": "1.0.0", 4 | "description": 5 | "An example of how to use typescript with preact-cli and all its goodies", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "eslint src && preact test", 9 | "start": "if-env NODE_ENV=production && npm run -s serve || npm run -s dev", 10 | "build": "preact build", 11 | "serve": "preact build && preact serve", 12 | "dev": "preact watch" 13 | }, 14 | "keywords": ["preact-cli", "typescript", "example", "preact", "webpack"], 15 | "author": "Mike Engel ", 16 | "contributors": [], 17 | "license": "MIT", 18 | "eslintConfig": { 19 | "extends": "eslint-config-synacor" 20 | }, 21 | "devDependencies": { 22 | "eslint": "^4.6.0", 23 | "eslint-config-synacor": "^1.1.1", 24 | "if-env": "^1.0.0", 25 | "preact-cli": "^1.4.1", 26 | "preact-cli-plugin-typescript": "^0.2.2", 27 | "typescript": "^2.5.2" 28 | }, 29 | "dependencies": { 30 | "preact": "^8.2.5", 31 | "preact-compat": "^3.17.0", 32 | "preact-router": "^2.5.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /preact.config.js: -------------------------------------------------------------------------------- 1 | import preactCliTypeScript from "preact-cli-plugin-typescript"; 2 | 3 | /** 4 | * Function that mutates original webpack config. 5 | * Supports asynchronous changes when promise is returned. 6 | * 7 | * @param {object} config original webpack config. 8 | * @param {object} env options passed to CLI. 9 | * @param {WebpackConfigHelpers} helpers object with useful helpers when working with config. 10 | **/ 11 | export default function(config, env, helpers) { 12 | preactCliTypeScript(config); 13 | } 14 | -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike-engel/preact-cli-typescript-sample/8397aa6dae6180cd05f61d169295b41e650fdae5/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike-engel/preact-cli-typescript-sample/8397aa6dae6180cd05f61d169295b41e650fdae5/src/assets/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike-engel/preact-cli-typescript-sample/8397aa6dae6180cd05f61d169295b41e650fdae5/src/assets/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike-engel/preact-cli-typescript-sample/8397aa6dae6180cd05f61d169295b41e650fdae5/src/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike-engel/preact-cli-typescript-sample/8397aa6dae6180cd05f61d169295b41e650fdae5/src/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike-engel/preact-cli-typescript-sample/8397aa6dae6180cd05f61d169295b41e650fdae5/src/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike-engel/preact-cli-typescript-sample/8397aa6dae6180cd05f61d169295b41e650fdae5/src/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/components/app.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from "preact"; 2 | import { Router } from "preact-router"; 3 | import async from "preact-cli/lib/components/async"; 4 | import { RouterOnChangeArgs } from "preact-router/index.d"; 5 | 6 | import Header from "./header"; 7 | 8 | const Home = async(() => 9 | import(/* webpackChunkName: "home" */ "../routes/home") 10 | ); 11 | const Profile = async(() => 12 | import(/* webpackChunkName: "profile" */ "../routes/profile") 13 | ); 14 | 15 | export default class App extends Component { 16 | currentUrl: string = null; 17 | 18 | handleRoute = (evt: RouterOnChangeArgs): void => { 19 | this.currentUrl = evt.url; 20 | }; 21 | 22 | render() { 23 | return ( 24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 |
32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { Link } from "preact-router/match"; 3 | 4 | import style from "./style.css"; 5 | 6 | export default () => ( 7 |
8 |

Preact App

9 | 20 |
21 | ); 22 | -------------------------------------------------------------------------------- /src/components/header/style.css: -------------------------------------------------------------------------------- 1 | .header { 2 | position: fixed; 3 | left: 0; 4 | top: 0; 5 | width: 100%; 6 | height: 56px; 7 | padding: 0; 8 | background: #673AB7; 9 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 10 | z-index: 50; 11 | } 12 | 13 | .header h1 { 14 | float: left; 15 | margin: 0; 16 | padding: 0 15px; 17 | font-size: 24px; 18 | line-height: 56px; 19 | font-weight: 400; 20 | color: #FFF; 21 | } 22 | 23 | .header nav { 24 | float: right; 25 | font-size: 100%; 26 | } 27 | 28 | .header nav a { 29 | display: inline-block; 30 | height: 56px; 31 | line-height: 56px; 32 | padding: 0 15px; 33 | min-width: 50px; 34 | text-align: center; 35 | background: rgba(255,255,255,0); 36 | text-decoration: none; 37 | color: #FFF; 38 | will-change: background-color; 39 | } 40 | 41 | .header nav a:hover, 42 | .header nav a:active { 43 | background: rgba(0,0,0,0.2); 44 | } 45 | 46 | .header nav a.active { 47 | background: rgba(0,0,0,0.4); 48 | } 49 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import "./style"; 2 | 3 | import App from "./components/app"; 4 | 5 | export default App; 6 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preact-cli-typescript-example", 3 | "short_name": "preact ex", 4 | "start_url": "/", 5 | "display": "standalone", 6 | "orientation": "portrait", 7 | "background_color": "#fff", 8 | "theme_color": "#673ab8", 9 | "manifest_version": 2, 10 | "version": "1.0", 11 | "icons": [ 12 | { 13 | "src": "/assets/icons/android-chrome-192x192.png", 14 | "type": "image/png", 15 | "sizes": "192x192" 16 | }, 17 | { 18 | "src": "/assets/icons/android-chrome-512x512.png", 19 | "type": "image/png", 20 | "sizes": "512x512" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/routes/home/index.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from "preact"; 2 | 3 | import style from "./style.css"; 4 | 5 | export default class Home extends Component { 6 | render() { 7 | return ( 8 |
9 |

Home

10 |

This is the Home component.

11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/routes/home/style.css: -------------------------------------------------------------------------------- 1 | .home { 2 | padding: 56px 20px; 3 | min-height: 100%; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/routes/profile/index.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from "preact"; 2 | 3 | import style from "./style.css"; 4 | 5 | interface Props { 6 | user: string; 7 | } 8 | 9 | interface State { 10 | time: number; 11 | count: number; 12 | } 13 | 14 | export default class Profile extends Component { 15 | state: State = { 16 | time: Date.now(), 17 | count: 10 18 | }; 19 | 20 | timer = null; 21 | 22 | // gets called when this route is navigated to 23 | componentDidMount() { 24 | // start a timer for the clock: 25 | this.timer = setInterval(this.updateTime, 1000); 26 | } 27 | 28 | // gets called just before navigating away from the route 29 | componentWillUnmount() { 30 | clearInterval(this.timer); 31 | } 32 | 33 | // update the current time 34 | updateTime = () => { 35 | this.setState({ time: Date.now() }); 36 | }; 37 | 38 | increment = () => { 39 | this.setState({ count: this.state.count + 1 }); 40 | }; 41 | 42 | // Note: `user` comes from the URL, courtesy of our router 43 | render({ user }, { time, count }) { 44 | return ( 45 |
46 |

Profile: {user}

47 |

This is the user profile for a user named {user}.

48 | 49 |
Current time: {new Date(time).toLocaleString()}
50 | 51 |

52 | Clicked {count}{" "} 53 | times. 54 |

55 |
56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/routes/profile/style.css: -------------------------------------------------------------------------------- 1 | .profile { 2 | padding: 56px 20px; 3 | min-height: 100%; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/style/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | padding: 0; 5 | margin: 0; 6 | background: #FAFAFA; 7 | font-family: 'Helvetica Neue', arial, sans-serif; 8 | font-weight: 400; 9 | color: #444; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | #app { 19 | height: 100%; 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "jsx": "react", 9 | "jsxFactory": "h", 10 | "allowJs": true, 11 | "lib": ["es6", "dom"] 12 | } 13 | } 14 | --------------------------------------------------------------------------------