├── .editorConfig ├── .gitattributes ├── .gitignore ├── .npmrc ├── .storybook ├── addons.js ├── config.js └── webpack.config.ts ├── .travis.yml ├── LICENSE ├── README.md ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── integration │ └── smoke.spec.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── package-lock.json ├── package.json ├── preprocessor.js ├── src ├── __tests__ │ └── sample-test-spec.ts ├── components │ ├── stories │ │ └── todo-item.stories.tsx │ ├── todo-item.tsx │ └── todo-list.tsx ├── index.html └── index.tsx ├── tsconfig.json ├── tslint.json └── webpack.config.ts /.editorConfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | #Unix-style newlines with a newline ending every file 7 | end_of_line = lf 8 | insert_final_newline = true 9 | 10 | [*] 11 | # Indentation style 12 | indent_style = space 13 | indent_size = 2 14 | 15 | # File character encoding 16 | charset = utf-8 17 | 18 | # Denotes whether to trim whitespace at the end of lines 19 | trim_trailing_whitespace = true 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts linguist-language=TypeScript 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Yarn Integrity file 47 | .yarn-integrity 48 | 49 | dist/ 50 | 51 | cypress/screenshots/ 52 | cypress/videos/ -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/preact'; 2 | 3 | // Automatically import all files ending in *.stories.tsx 4 | const req = require.context('../src/components', true, /\.stories\.tsx$/); 5 | 6 | function loadStories() { 7 | req.keys().forEach(filename => req(filename)); 8 | } 9 | 10 | configure(loadStories, module); 11 | -------------------------------------------------------------------------------- /.storybook/webpack.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This extends the default webpack configuration used in storybook. 3 | */ 4 | import { resolve } from 'path'; 5 | 6 | module.exports = { 7 | resolve: { 8 | extensions: ['.ts', '.tsx', '.js'] 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | enforce: 'pre', 14 | test: /\.tsx?$/, 15 | exclude: /node_modules/, 16 | use: [ 17 | { 18 | loader: 'tslint-loader', 19 | options: { 20 | configFile: resolve(__dirname, '../tslint.json'), 21 | emitErrors: true, 22 | failOnHint: true, 23 | typeCheck: true 24 | } 25 | } 26 | ] 27 | }, 28 | { 29 | test: /\.tsx?$/, 30 | exclude: /node_modules/, 31 | use: [ 32 | { 33 | loader: 'ts-loader' 34 | } 35 | ] 36 | } 37 | ] 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | script: 4 | - npm test 5 | - npm run build 6 | 7 | cache: 8 | yarn: true 9 | directories: 10 | - node_modules 11 | 12 | node_js: 13 | - "12" 14 | - "10" 15 | - "8" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Nick Taylor 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 | # ts-preact-starter 2 | 3 | **This project is no longer maintained as there are better projects for TypeScript and Preact that have popped up since this project was created** 4 | 5 | [![Build status](https://img.shields.io/travis/nickytonline/ts-preact-starter.svg)](https://travis-ci.org/nickytonline/ts-preact-starter) 6 | [![Netlify Status](https://api.netlify.com/api/v1/badges/05030e94-4c6a-4699-9c36-552a2e345f35/deploy-status)](https://app.netlify.com/sites/fervent-newton-a3b969/deploys) 7 | 8 | Dependabot Badge 9 | 10 | This is a barebones starter kit for Preact with TypeScript. Click the green "Use this template" button at the top of this page and enter a name and description for your repository. 11 | 12 | To get up and running 13 | 14 | 1. `npm install` 15 | 1. From the command line run `npm start` 16 | 1. Navigate to [http://localhost:3000](http://localhost:3000) 17 | 18 | To run tests: 19 | 20 | 1. `npm test` 21 | 1. To run in watch mode, run `npm run test:watch` 22 | 1. Tests are set up to run out of the `__tests__` folder. I put this by default as this appears to be part of the Jest defaults, but if you prefer to have your tests beside the code you want to test, simply modify the regex in the Jest configuration in `package.json`. 23 | 24 | To run Cypress: 25 | 26 | 1. Run `npm run start` to start the webpack dev server. 27 | 2. Run `npm run e2e:dev` to open the Cypress test runner. For more information on Cypress, see their [official documentation](https://docs.cypress.io) 28 | 29 | To run Storybook: 30 | 31 | 1. `npm run storybook` 32 | 1. Navigate to [http://localhost:6006](http://localhost:6006) 33 | 1. For more information on using Storybook, see the [Storybook for Preact](https://storybook.js.org/docs/guides/guide-preact) guide. 34 | 35 | A deployed demo can be found at [typescript-preact-starter.iamdeveloper.com](https://typescript-preact-starter.iamdeveloper.com). 36 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8080" 3 | } 4 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /cypress/integration/smoke.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe('Smoke test site', () => { 4 | it('Should load the TODO app', () => { 5 | cy.visit('/'); 6 | 7 | // Ensure we have an input to add a new todo 8 | cy.get('[data-cy="new-todo-input"]').should('be.visible'); 9 | 10 | // Ensure Add TODO button is present 11 | cy.get('[data-cy="todo-submit"]').should('be.visible'); 12 | 13 | cy.get('[data-cy="todo-list"]').then(([todoList]) => { 14 | // No TODOs when the app initializes. 15 | expect(todoList.children.length).to.equal(0); 16 | }) 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | return config 16 | }; 17 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-preact-starter", 3 | "version": "1.0.0", 4 | "description": "Barebones starter project for Preact with TypeScript", 5 | "main": "index.js", 6 | "author": "Nick Taylor ", 7 | "license": "MIT", 8 | "keywords": [ 9 | "preact", 10 | "react", 11 | "typescript", 12 | "boilerplate" 13 | ], 14 | "engines": { 15 | "node": ">=8" 16 | }, 17 | "scripts": { 18 | "prebuild": "CI=1 npm i cypress", 19 | "build": "webpack --mode=production", 20 | "postbuild": "http-server -p 8080 ./dist & npm run e2e && fkill -f :8080", 21 | "start": "webpack-dev-server --mode=development --progress --config ./webpack.config.ts", 22 | "test": "jest", 23 | "test:watch": "npm run test -- --watch", 24 | "storybook": "start-storybook -p 6006", 25 | "build-storybook": "build-storybook", 26 | "e2e": "[ ! -z \"$DEPLOY_URL\" ] && cypress run || echo 'e2e only runs on Netlify'", 27 | "e2e:dev": "CYPRESS_baseUrl=http://localhost:9000 cypress open" 28 | }, 29 | "jest": { 30 | "moduleFileExtensions": [ 31 | "ts", 32 | "tsx", 33 | "js" 34 | ], 35 | "transform": { 36 | "^.+\\.(ts|tsx)$": "/preprocessor.js" 37 | }, 38 | "testRegex": "/__tests__/.*\\.(ts|tsx|js)$" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "7.8.6", 42 | "@storybook/addon-actions": "5.1.11", 43 | "@storybook/addon-links": "5.1.11", 44 | "@storybook/addons": "5.1.11", 45 | "@storybook/preact": "5.1.11", 46 | "@types/jest": "25.1.3", 47 | "@types/webpack": "4.41.7", 48 | "@types/webpack-dev-server": "3.10.0", 49 | "babel-loader": "8.0.6", 50 | "cypress": "3.8.1", 51 | "fkill-cli": "5.2.0", 52 | "html-webpack-plugin": "3.2.0", 53 | "http-server": "0.12.1", 54 | "jest": "25.1.0", 55 | "preact-compat": "3.19.0", 56 | "ts-loader": "6.2.1", 57 | "ts-node": "8.6.2", 58 | "tslint": "6.0.0", 59 | "tslint-loader": "3.5.4", 60 | "typescript": "3.8.3", 61 | "webpack": "4.41.5", 62 | "webpack-cli": "3.3.10", 63 | "webpack-dev-server": "3.10.2" 64 | }, 65 | "dependencies": { 66 | "preact": "8.5.3", 67 | "tslib": "1.11.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /preprocessor.js: -------------------------------------------------------------------------------- 1 | const tsc = require('typescript'); 2 | const tsConfig = require('./tsconfig.json'); 3 | 4 | module.exports = { 5 | process(src, path) { 6 | if (path.endsWith('.ts') || path.endsWith('.tsx')) { 7 | return tsc.transpile( 8 | src, 9 | tsConfig.compilerOptions, 10 | path, 11 | [] 12 | ); 13 | } 14 | return src; 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/__tests__/sample-test-spec.ts: -------------------------------------------------------------------------------- 1 | describe ('WHEN this sample test runs', () => { 2 | it('SHOULD return true', () => { 3 | // Arrange 4 | const testData: string = 'some data'; 5 | const expected = true; 6 | 7 | // Act 8 | const actual = testData.includes('a'); 9 | 10 | // Assert 11 | expect(expected).toBe(actual); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/stories/todo-item.stories.tsx: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/preact'; 2 | import { h } from 'preact'; 3 | import TodoItem from '../todo-item'; 4 | 5 | storiesOf('Todo Item', module) 6 | .add('with text', () => { 7 | 8 | return ( 9 | 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/todo-item.tsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | 3 | interface TodoItemProps { 4 | text: string; 5 | } 6 | 7 | const TodoItem = ({text}: TodoItemProps) => (
  • {text}
  • ); 8 | 9 | export default TodoItem; 10 | -------------------------------------------------------------------------------- /src/components/todo-list.tsx: -------------------------------------------------------------------------------- 1 | import { Component, h } from 'preact'; 2 | import TodoItem from './todo-item'; 3 | 4 | interface TodoListState { 5 | todos: { text: string }[]; 6 | text: string; 7 | } 8 | 9 | export default class TodoList extends Component<{}, TodoListState> { 10 | state = { todos: [], text: '' }; 11 | 12 | setText = (e: Event) => { 13 | this.setState({ 14 | text: (e.target as HTMLInputElement).value 15 | }); 16 | } 17 | 18 | addTodo = () => { 19 | const { todos, text } = this.state; 20 | 21 | this.setState({ 22 | todos: [...todos, { text }], 23 | text: '' 24 | }); 25 | } 26 | 27 | render({}, { todos, text }) { 28 | return ( 29 |
    30 | 31 | 32 |
      {todos.map(todo => )}
    33 |
    34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ts-preact-starter 6 | 7 | 8 |
    9 | 10 | 11 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render, h } from 'preact'; 2 | import TodoList from './components/todo-list'; 3 | 4 | render( 5 | , 6 | document.querySelector('#root') 7 | ); 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "jsx": "react", 5 | "target": "es5", 6 | "module": "commonjs", 7 | "lib": ["dom", "es2015"], 8 | "jsxFactory": "h", 9 | "noEmitHelpers": true, 10 | "importHelpers": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": [ 4 | true, 5 | "parameters", 6 | "arguments", 7 | "statements" 8 | ], 9 | "ban": false, 10 | "class-name": true, 11 | "comment-format": [ 12 | true, 13 | "check-space" 14 | ], 15 | "curly": false, 16 | "eofline": true, 17 | "forin": true, 18 | "indent": [ 19 | true, 20 | "spaces" 21 | ], 22 | "interface-name": false, 23 | "jsdoc-format": true, 24 | "label-position": true, 25 | "max-line-length": [ 26 | true, 27 | 140 28 | ], 29 | "member-ordering": [ 30 | true, 31 | "public-before-private", 32 | "static-before-instance", 33 | "variables-before-functions" 34 | ], 35 | "no-any": false, 36 | "no-arg": true, 37 | "no-bitwise": true, 38 | "no-console": [ 39 | true, 40 | "debug", 41 | "info", 42 | "time", 43 | "timeEnd", 44 | "trace" 45 | ], 46 | "no-construct": true, 47 | "no-parameter-properties": false, 48 | "no-debugger": true, 49 | "no-shadowed-variable": true, 50 | "no-duplicate-variable": true, 51 | "no-empty": true, 52 | "no-eval": true, 53 | "no-internal-module": true, 54 | "no-require-imports": true, 55 | "no-string-literal": true, 56 | "no-switch-case-fall-through": true, 57 | "trailing-comma": [ 58 | true, 59 | { 60 | "multiline": "never", 61 | "singleline": "never" 62 | } 63 | ], 64 | "no-trailing-whitespace": true, 65 | "no-unused-expression": true, 66 | "no-use-before-declare": true, 67 | "no-var-keyword": true, 68 | "no-var-requires": false, 69 | "one-line": [ 70 | true, 71 | "check-open-brace", 72 | "check-catch", 73 | "check-else", 74 | "check-whitespace" 75 | ], 76 | "quotemark": [ 77 | true, 78 | "single", 79 | "jsx-double" 80 | ], 81 | "radix": true, 82 | "semicolon": [ 83 | true, 84 | "always" 85 | ], 86 | "switch-default": true, 87 | "triple-equals": [ 88 | true, 89 | "allow-null-check" 90 | ], 91 | "typedef": [ 92 | false, 93 | "call-signature", 94 | "parameter", 95 | "property-declaration", 96 | "member-variable-declaration" 97 | ], 98 | "typedef-whitespace": [ 99 | true, 100 | { 101 | "call-signature": "nospace", 102 | "index-signature": "nospace", 103 | "parameter": "nospace", 104 | "property-declaration": "nospace", 105 | "variable-declaration": "nospace" 106 | } 107 | ], 108 | "variable-name": [ 109 | true, 110 | "ban-keywords" 111 | ], 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ] 120 | } 121 | } -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | import * as webpack from 'webpack'; 2 | import { resolve, join } from 'path'; 3 | import * as HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | 5 | const { HotModuleReplacementPlugin } = webpack; 6 | const port = 3000; 7 | const context = __dirname + '/src'; 8 | 9 | interface WebpackEnvironment { 10 | NODE_ENV: string; 11 | } 12 | 13 | module.exports = (env: WebpackEnvironment, argv: { mode: string }) => { 14 | const appEntryPoints = argv.mode === 'production' 15 | ? ['./index'] 16 | : [ 17 | `webpack-dev-server/client?http://localhost:${port}`, 18 | 'webpack/hot/only-dev-server', 19 | './index' 20 | ]; 21 | 22 | const config: webpack.Configuration = { 23 | name: 'client', 24 | target: 'web', 25 | context, 26 | entry: { 27 | app: appEntryPoints 28 | }, 29 | output: { 30 | filename: '[name].js', 31 | path: resolve(__dirname, 'dist') 32 | }, 33 | resolve: { 34 | extensions: ['.ts', '.tsx', '.js', 'jsx'] 35 | }, 36 | devtool: argv.mode === 'production' ? 'source-map' : 'cheap-eval-source-map', 37 | module: { 38 | rules: [ 39 | { 40 | enforce: 'pre', 41 | test: /\.tsx?$/, 42 | loader: 'tslint-loader', 43 | exclude: /node_modules/, 44 | options: { 45 | configFile: resolve(__dirname, './tslint.json'), 46 | emitErrors: true, 47 | failOnHint: true, 48 | typeCheck: true 49 | } 50 | }, 51 | { 52 | test: /\.tsx?$/, 53 | loader: 'ts-loader', 54 | exclude: /node_modules/ 55 | } 56 | ] 57 | }, 58 | plugins: [ 59 | new HtmlWebpackPlugin({ 60 | template: './index.html', 61 | hash: true, 62 | filename: 'index.html', 63 | inject: 'body' 64 | }), 65 | new HotModuleReplacementPlugin() 66 | ] 67 | }; 68 | 69 | if (argv.mode === 'development') { 70 | config.devServer = { 71 | contentBase: join(__dirname, 'dist'), 72 | compress: true, 73 | port: 9000 74 | }; 75 | } 76 | 77 | return config; 78 | }; 79 | --------------------------------------------------------------------------------