├── .editorconfig ├── .gitignore ├── config ├── babel.js ├── setup.js ├── styles.js ├── uglify.js └── webpack.js ├── license ├── package.json ├── readme.md ├── src ├── index.html ├── index.js ├── index.sass ├── static │ ├── favicon.ico │ ├── icon │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── mstile-150x150.png │ ├── img │ │ └── lighthouse.jpg │ └── manifest.json ├── styles │ ├── all.sass │ ├── base │ │ ├── doc.sass │ │ └── reset.sass │ ├── config │ │ ├── extns.sass │ │ ├── mixins.sass │ │ └── vars.sass │ ├── pages │ │ ├── article.sass │ │ ├── errors │ │ │ └── 404.sass │ │ └── home.sass │ └── tags │ │ ├── card-link.sass │ │ ├── card.sass │ │ ├── header.sass │ │ ├── layout.sass │ │ └── page.sass └── views │ ├── index.js │ ├── pages │ ├── article.js │ ├── blog.js │ ├── credit.js │ ├── errors │ │ └── 404.js │ └── home.js │ └── tags │ ├── card-link.js │ ├── card.js │ ├── header.js │ └── layout.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,yml,md}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.log 4 | dist 5 | -------------------------------------------------------------------------------- /config/babel.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['es2015', { loose:true, modules:false }], 4 | 'stage-2' 5 | ], 6 | plugins: [ 7 | ['transform-react-jsx', { pragma:'h' }] 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /config/setup.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | const webpack = require('webpack'); 3 | const ExtractText = require('extract-text-webpack-plugin'); 4 | const SWPrecache = require('sw-precache-webpack-plugin'); 5 | const Clean = require('clean-webpack-plugin'); 6 | const Copy = require('copy-webpack-plugin'); 7 | const HTML = require('html-webpack-plugin'); 8 | const uglify = require('./uglify'); 9 | 10 | const root = join(__dirname, '..'); 11 | 12 | module.exports = isProd => { 13 | // base plugins array 14 | const plugins = [ 15 | new Clean(['dist'], { root }), 16 | new Copy([{ context: 'src/static/', from: '**/*.*' }]), 17 | new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), 18 | new HTML({ template: 'src/index.html' }), 19 | new webpack.DefinePlugin({ 20 | 'process.env.NODE_ENV': JSON.stringify(isProd ? 'production' : 'development') 21 | }) 22 | ]; 23 | 24 | if (isProd) { 25 | plugins.push( 26 | new webpack.LoaderOptionsPlugin({ minimize:true }), 27 | new webpack.optimize.ModuleConcatenationPlugin(), 28 | new webpack.optimize.UglifyJsPlugin(uglify), 29 | new ExtractText('styles.[hash].css'), 30 | new SWPrecache({ 31 | minify: true, 32 | filename: 'sw.js', 33 | dontCacheBustUrlsMatching: /./, 34 | navigateFallback: 'index.html', 35 | staticFileGlobsIgnorePatterns: [/\.map$/] 36 | }) 37 | ); 38 | } else { 39 | // dev only 40 | plugins.push( 41 | new webpack.HotModuleReplacementPlugin(), 42 | new webpack.NamedModulesPlugin() 43 | ); 44 | } 45 | 46 | return plugins; 47 | }; 48 | -------------------------------------------------------------------------------- /config/styles.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | module.exports = [ 4 | { 5 | loader: 'css-loader' 6 | }, { 7 | loader: 'postcss-loader', 8 | options: { 9 | plugins: [ 10 | require('autoprefixer')({ 11 | browsers: ['last 3 version'] 12 | }) 13 | ] 14 | } 15 | }, { 16 | loader: 'sass-loader', 17 | options: { 18 | includePaths: [ 19 | join(__dirname, 'src') 20 | ] 21 | } 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /config/uglify.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | output: { 3 | comments: 0 4 | }, 5 | compress: { 6 | unused: 1, 7 | warnings: 0, 8 | comparisons: 1, 9 | conditionals: 1, 10 | negate_iife: 0, // <- for `LazyParseWebpackPlugin()` 11 | dead_code: 1, 12 | if_return: 1, 13 | join_vars: 1, 14 | evaluate: 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config/webpack.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | const ExtractText = require('extract-text-webpack-plugin'); 3 | const babelOpts = require('./babel'); 4 | const styles = require('./styles'); 5 | const setup = require('./setup'); 6 | 7 | const dist = join(__dirname, '..', 'dist'); 8 | const exclude = /(node_modules|bower_components)/; 9 | 10 | module.exports = env => { 11 | const isProd = env && env.production; 12 | 13 | if (isProd) { 14 | babelOpts.presets.push('babili'); 15 | } else { 16 | styles.unshift({ loader:'style-loader' }); 17 | } 18 | 19 | return { 20 | entry: { 21 | app: './src/index.js', 22 | vendor: [ 23 | // pull these to a `vendor.js` file 24 | 'preact' 25 | ] 26 | }, 27 | output: { 28 | path: dist, 29 | filename: '[name].[hash].js', 30 | publicPath: '/' 31 | }, 32 | resolve: { 33 | alias: { 34 | // Run `npm install preact-compat --save` 35 | // 'react': 'preact-compat', 36 | // 'react-dom': 'preact-compat' 37 | } 38 | }, 39 | module: { 40 | rules: [{ 41 | test: /\.jsx?$/, 42 | exclude: exclude, 43 | loader: { 44 | loader: 'babel-loader', 45 | options: babelOpts 46 | } 47 | }, { 48 | test: /\.(sass|scss)$/, 49 | use: isProd ? ExtractText.extract({ fallback:'style-loader', use:styles }) : styles 50 | }] 51 | }, 52 | plugins: setup(isProd), 53 | devtool: !isProd && 'eval', 54 | devServer: { 55 | contentBase: dist, 56 | port: process.env.PORT || 3000, 57 | historyApiFallback: true, 58 | compress: isProd, 59 | inline: !isProd, 60 | hot: !isProd 61 | } 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Luke Edwards (https://lukeed.com) 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "preact-starter", 4 | "version": "1.4.0", 5 | "license": "MIT", 6 | "repository": "lukeed/preact-starter", 7 | "author": { 8 | "name": "Luke Edwards", 9 | "email": "luke.edwards05@gmail.com", 10 | "url": "https://lukeed.com" 11 | }, 12 | "scripts": { 13 | "build": "webpack --env.production --config config/webpack", 14 | "start": "sirv dist -s", 15 | "watch": "webpack-dev-server --config config/webpack" 16 | }, 17 | "dependencies": { 18 | "ganalytics": "^2.0.1", 19 | "md-colors": "^1.0.0", 20 | "preact": "^8.2.5", 21 | "preact-router": "^2.5.7" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^7.1.3", 25 | "babel-core": "^6.26.0", 26 | "babel-loader": "^7.1.2", 27 | "babel-plugin-transform-react-jsx": "^6.22.0", 28 | "babel-preset-babili": "^0.1.4", 29 | "babel-preset-es2015": "^6.22.0", 30 | "babel-preset-stage-2": "^6.22.0", 31 | "clean-webpack-plugin": "^0.1.15", 32 | "copy-webpack-plugin": "^4.0.1", 33 | "css-loader": "^0.28.5", 34 | "extract-text-webpack-plugin": "^3.0.0", 35 | "html-webpack-plugin": "^2.30.1", 36 | "node-sass": "^4.5.0", 37 | "postcss-loader": "^2.0.6", 38 | "sass-loader": "^6.0.3", 39 | "sirv-cli": "^0.2.3", 40 | "style-loader": "^0.18.2", 41 | "sw-precache-webpack-plugin": "^0.11.4", 42 | "webpack": "^3.7.0", 43 | "webpack-dev-server": "^2.9.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # preact-starter 2 | 3 | > Webpack3 boilerplate for building SPA / PWA / offline front-end apps with :atom_symbol: [Preact](https://github.com/developit/preact) 4 | 5 | Using `preact-starter` will kickstart your next application! :100: It is designed to fit the "90% use-case" for those who want to build offline-first web apps (see [features](#features) below). 6 | 7 | > :triangular_flag_on_post: **Important:** This is meant for _client-side_ apps only; there is **no support** included for universal / server-side rendering. I plan to release my own SSR kit shortly! 8 | 9 | **Please note:** Boilerplate _does not_ incorporate the latest ["css-in-js" or "css modules" technique](https://github.com/MicheleBertoli/css-in-js). Instead, the more traditional approach is taken, wherein `styles` and `scripts` are kept separate. That said, you may **certainly and easily** take that route if desired. :white_check_mark: 10 | 11 | --- 12 | 13 |

:warning: Boilerplate & commands will evolve as my own development process does. :warning:

14 | 15 | --- 16 | 17 | ## Install 18 | 19 | ```sh 20 | git clone https://github.com/lukeed/preact-starter 21 | cd preact-starter 22 | npm install 23 | npm run build 24 | npm start 25 | ``` 26 | 27 | > :exclamation: **Pro Tip:** Use [Yarn](https://yarnpkg.com/) to install dependencies 3x faster than NPM! 28 | 29 | ## Features 30 | 31 | * Offline Caching (via `serviceWorker`) 32 | * SASS & Autoprefixer 33 | * Asset Versioning (aka "cache-busting") 34 | * ES2015 (ES6) and ES2016 (ES7) support 35 | * Hot Module Replacement (HMR) for all files 36 | * Preact's [Developer Tools](#preact-developer-tools) 37 | * [Lighthouse](https://github.com/GoogleChrome/lighthouse) certified 38 | 39 | ![lightouse](src/static/img/lighthouse.jpg) 40 | 41 | ## Development 42 | 43 | ### Commands 44 | 45 | Any of the following commands can (and should :wink:) be run from the command line. 46 | 47 | > If using [Yarn](https://yarnpkg.com/), all instances of `npm` can be replaced with `yarn`. :ok_hand: 48 | 49 | #### build 50 | 51 | ``` 52 | $ npm run build 53 | ``` 54 | 55 | Compiles all files. Output is sent to the `dist` directory. 56 | 57 | #### start 58 | 59 | ``` 60 | $ npm start 61 | ``` 62 | 63 | Runs your application (from the `dist` directory) in the browser. 64 | 65 | #### watch 66 | 67 | ``` 68 | $ npm run watch 69 | ``` 70 | 71 | Like [`start`](#start), but will auto-compile & auto-reload the server after any file changes within the `src` directory. 72 | 73 | ### Preact Developer Tools 74 | 75 | You can inspect and modify the state of your Preact UI components at runtime using the [React Developer Tools](https://github.com/facebook/react-devtools) browser extension. 76 | 77 | 1. Install the [React Developer Tools](https://github.com/facebook/react-devtools) extension 78 | 2. [Import the `preact/devtools`](src/index.js#L23) module in your app 79 | 3. Reload and go to the 'React' tab in the browser's development tools 80 | 81 | ## License 82 | 83 | MIT © [Luke Edwards](https://lukeed.com) 84 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Preact Starter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Loading
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { render } from 'preact'; 2 | import GAnalytics from 'ganalytics'; 3 | import './index.sass'; 4 | 5 | let elem, App; 6 | function init() { 7 | App = require('./views').default; 8 | elem = render(App, document.getElementById('root'), elem); 9 | } 10 | 11 | init(); 12 | 13 | if (process.env.NODE_ENV === 'production') { 14 | // cache all assets if browser supports serviceworker 15 | if ('serviceWorker' in navigator && location.protocol === 'https:') { 16 | navigator.serviceWorker.register('/sw.js'); 17 | } 18 | 19 | // add Google Analytics 20 | window.ga = new GAnalytics('UA-XXXXXXXX-X'); 21 | } else { 22 | // use preact's devtools 23 | require('preact/devtools'); 24 | // listen for HMR 25 | if (module.hot) { 26 | module.hot.accept('./views', init); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Styles entry 3 | */ 4 | 5 | @import './styles/all' 6 | -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/preact-starter/fdf363308e81c3499719727614da6821ad11a705/src/static/favicon.ico -------------------------------------------------------------------------------- /src/static/icon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/preact-starter/fdf363308e81c3499719727614da6821ad11a705/src/static/icon/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/static/icon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/preact-starter/fdf363308e81c3499719727614da6821ad11a705/src/static/icon/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/static/icon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/preact-starter/fdf363308e81c3499719727614da6821ad11a705/src/static/icon/apple-touch-icon.png -------------------------------------------------------------------------------- /src/static/icon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/preact-starter/fdf363308e81c3499719727614da6821ad11a705/src/static/icon/favicon-16x16.png -------------------------------------------------------------------------------- /src/static/icon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/preact-starter/fdf363308e81c3499719727614da6821ad11a705/src/static/icon/favicon-32x32.png -------------------------------------------------------------------------------- /src/static/icon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/preact-starter/fdf363308e81c3499719727614da6821ad11a705/src/static/icon/mstile-150x150.png -------------------------------------------------------------------------------- /src/static/img/lighthouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/preact-starter/fdf363308e81c3499719727614da6821ad11a705/src/static/img/lighthouse.jpg -------------------------------------------------------------------------------- /src/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Preact Starter", 3 | "short_name": "Preact", 4 | "start_url": "/?homescreen=1", 5 | "display": "standalone", 6 | "orientation": "portrait", 7 | "background_color": "#F2F2F2", 8 | "theme_color": "#673AB8", 9 | "icons": [{ 10 | "src": "icon/android-chrome-192x192.png", 11 | "type": "image/png", 12 | "sizes": "192x192" 13 | }, { 14 | "src": "icon/android-chrome-512x512.png", 15 | "type": "image/png", 16 | "sizes": "512x512" 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/all.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Main stylesheet 3 | */ 4 | 5 | // '~' denotes npm module 6 | @import '~md-colors' 7 | 8 | @import 'config/vars' 9 | @import 'config/extns' 10 | @import 'config/mixins' 11 | 12 | @import 'base/reset' 13 | @import 'base/doc' 14 | 15 | // tags first; pages can customize 16 | @import 'tags/header' 17 | @import 'tags/layout' 18 | @import 'tags/page' 19 | @import 'tags/card' 20 | @import 'tags/card-link' 21 | 22 | @import 'pages/home' 23 | @import 'pages/article' 24 | @import 'pages/errors/404' 25 | -------------------------------------------------------------------------------- /src/styles/base/doc.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Global document styles 3 | */ 4 | 5 | %fullscreen 6 | width: 100vw 7 | min-height: 100vh 8 | overflow-x: hidden 9 | 10 | body 11 | color: $black 12 | background: $offwhite 13 | font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif 14 | @extend %fullscreen 15 | font-weight: 400 16 | +font-smoothing() 17 | 18 | a 19 | color: $primary 20 | 21 | li, p 22 | margin-bottom: $base-spacing/2 23 | 24 | img 25 | user-select: none 26 | max-width: 100% 27 | 28 | blockquote 29 | border-left: 4px solid md-color('grey', '200') 30 | padding: $base-spacing/2 $base-spacing 31 | padding-right: 0 32 | -------------------------------------------------------------------------------- /src/styles/base/reset.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * simple reset 3 | */ 4 | 5 | * 6 | margin: 0 7 | padding: 0 8 | box-sizing: border-box 9 | -------------------------------------------------------------------------------- /src/styles/config/extns.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared Extensions (careful) 3 | */ 4 | 5 | %shadow 6 | box-shadow: 0 1px 3px 0 rgba($black, 0.2), 0 1px 1px 0 rgba($black, 0.14), 0 2px 1px -1px rgba($black, 0.12) 7 | -------------------------------------------------------------------------------- /src/styles/config/mixins.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Mixins / Helpers 3 | */ 4 | 5 | =font-smoothing 6 | -moz-osx-font-smoothing: grayscale 7 | -webkit-font-smoothing: antialiased 8 | text-rendering: optimizeLegibility 9 | -------------------------------------------------------------------------------- /src/styles/config/vars.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Reusable values 3 | */ 4 | 5 | $purple: md-color('deep-purple') 6 | $black: md-color('grey', '900') 7 | $offwhite: md-color('grey', '100') 8 | $white: #FFF 9 | 10 | $primary: $purple 11 | 12 | $base-unit: 8px 13 | $base-spacing: 2 * $base-unit 14 | -------------------------------------------------------------------------------- /src/styles/pages/article.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * `Article` Page Component 3 | */ 4 | 5 | .page__article 6 | h1 7 | text-transform: capitalize 8 | margin-bottom: $base-spacing 9 | small 10 | display: block 11 | text-indent: $base-spacing 12 | font-size: 65% 13 | opacity: 0.65 14 | font-style: italic 15 | .back, p 16 | display: block 17 | margin-bottom: $base-spacing 18 | -------------------------------------------------------------------------------- /src/styles/pages/errors/404.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * 404 Error Page 3 | */ 4 | 5 | .page__404 6 | text-align: center 7 | -------------------------------------------------------------------------------- /src/styles/pages/home.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * `Home` Page Component 3 | */ 4 | 5 | .page__home 6 | ul 7 | padding-left: 2 * $base-spacing 8 | 9 | img 10 | width: 100% 11 | display: block 12 | margin: $base-unit auto 13 | max-width: 60 * $base-spacing // 960 14 | -------------------------------------------------------------------------------- /src/styles/tags/card-link.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Clickable Card 3 | */ 4 | 5 | a.card 6 | cursor: pointer 7 | text-decoration: none 8 | color: $black 9 | 10 | strong, em 11 | display: block 12 | 13 | em 14 | font-size: 85% 15 | padding-left: $base-spacing/2 16 | -------------------------------------------------------------------------------- /src/styles/tags/card.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Paper Card (pseudo) 3 | */ 4 | 5 | .card 6 | display: block 7 | position: relative 8 | background: $white 9 | border-radius: 2px 10 | margin: 0 0 $base-spacing/2 11 | padding: $base-spacing 12 | @extend %shadow 13 | 14 | h2 15 | margin-bottom: $base-spacing 16 | -------------------------------------------------------------------------------- /src/styles/tags/header.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Nav / Header 3 | */ 4 | 5 | $header-height: 7 * $base-unit 6 | 7 | .header 8 | position: fixed 9 | height: $header-height 10 | width: 100% 11 | padding: 0 12 | left: 0 13 | top: 0 14 | background: $primary 15 | @extend %shadow 16 | z-index: 50 17 | 18 | h1 19 | float: left 20 | margin: 0 21 | padding: 0 $base-spacing 22 | font-size: 24px 23 | line-height: $header-height 24 | font-weight: 400 25 | color: $white 26 | 27 | nav 28 | float: right 29 | font-size: 100% 30 | 31 | a 32 | display: inline-block 33 | height: $header-height 34 | line-height: $header-height 35 | padding: 0 $base-spacing 36 | text-align: center 37 | min-width: 50px 38 | background: rgba($white, 0) 39 | will-change: background-color 40 | text-decoration: none 41 | color: $offwhite 42 | &:hover, 43 | &:active 44 | background: rgba($white, 0.3) 45 | 46 | @media screen and (max-width: 401px) 47 | nav a 48 | padding: 0 $base-spacing/2 49 | min-width: $base-spacing*2 50 | @media screen and (max-width: 321px) 51 | h1 52 | font-size: 18px 53 | -------------------------------------------------------------------------------- /src/styles/tags/layout.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * App Layout 3 | */ 4 | 5 | #app 6 | @extend %fullscreen 7 | 8 | #content 9 | padding-top: $header-height 10 | -------------------------------------------------------------------------------- /src/styles/tags/page.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Base `page` styles 3 | */ 4 | 5 | .page 6 | padding: $base-spacing 2*$base-spacing 7 | min-height: 100% 8 | width: 100% 9 | nav 10 | margin: 0 $base-spacing/2 11 | a 12 | display: block 13 | 14 | @media screen and (max-width: 321px) 15 | padding: $base-spacing $base-spacing/2 16 | -------------------------------------------------------------------------------- /src/views/index.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | import { Router } from 'preact-router'; 3 | 4 | import Home from './pages/home'; 5 | import Layout from './tags/layout'; 6 | import Article from './pages/article'; 7 | import Error404 from './pages/errors/404'; 8 | import Credit from './pages/credit'; 9 | import Blog from './pages/blog'; 10 | 11 | // track pages on route change 12 | const onChange = obj => window.ga && ga.send('pageview', { dp:obj.url }); 13 | 14 | export default ( 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /src/views/pages/article.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Link } from 'preact-router'; 3 | import Card from '../tags/card'; 4 | 5 | export default function (props) { 6 | const title = props.title; 7 | return ( 8 |
9 | 10 |

{ title }: A killer story

11 | Back to Blog 12 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam maiores necessitatibus nihil quo, cupiditate consectetur voluptatem cumque ipsum consequuntur aut repellat repellendus eligendi, placeat inventore perspiciatis dolores ipsa voluptates porro.

13 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam maiores necessitatibus nihil quo, cupiditate consectetur voluptatem cumque ipsum consequuntur aut repellat repellendus eligendi, placeat inventore perspiciatis dolores ipsa voluptates porro.

14 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam maiores necessitatibus nihil quo, cupiditate consectetur voluptatem cumque ipsum consequuntur aut repellat repellendus eligendi, placeat inventore perspiciatis dolores ipsa voluptates porro.

15 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam maiores necessitatibus nihil quo, cupiditate consectetur voluptatem cumque ipsum consequuntur aut repellat repellendus eligendi, placeat inventore perspiciatis dolores ipsa voluptates porro.

16 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam maiores necessitatibus nihil quo, cupiditate consectetur voluptatem cumque ipsum consequuntur aut repellat repellendus eligendi, placeat inventore perspiciatis dolores ipsa voluptates porro.

17 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam maiores necessitatibus nihil quo, cupiditate consectetur voluptatem cumque ipsum consequuntur aut repellat repellendus eligendi, placeat inventore perspiciatis dolores ipsa voluptates porro.

18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/views/pages/blog.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import Card from '../tags/card'; 3 | import CardLink from '../tags/card-link'; 4 | 5 | export default function (props) { 6 | return ( 7 |
8 | 9 |

Blog

10 |

Please select an Article to read.

11 |
12 | 13 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/views/pages/credit.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import Card from '../tags/card'; 3 | 4 | const links = [{ 5 | name: 'lukeed/preact-starter', 6 | desc: 'A Webpack2 starter for building SPA / PWA / offline front-end apps with Preact', 7 | href: 'https://github.com/lukeed/preact-starter' 8 | }, { 9 | name: 'developit/preact', 10 | desc: 'Fast 3kb React alternative with the same ES6 API. Components & Virtual DOM.', 11 | href: 'https://www.npmjs.com/package/preact' 12 | }, { 13 | name: 'developit/preact-router', 14 | desc: 'URL router for Preact.', 15 | href: 'https://www.npmjs.com/package/preact-router' 16 | }, { 17 | name: 'webpack/webpack', 18 | desc: 'A bundler for javascript and friends. Allows for code splitting & asynchronous lazy-loading.', 19 | href: 'https://github.com/webpack/webpack' 20 | }, { 21 | name: 'zeit/now', 22 | desc: 'Free (OSS) realtime global deployments', 23 | href: 'https://zeit.co/now' 24 | }, { 25 | name: 'zeit/serve', 26 | desc: 'Single-command HTTP directory listing and file serving', 27 | href: 'https://github.com/zeit/serve' 28 | }]; 29 | 30 | export default function (props) { 31 | return ( 32 |
33 | 34 |

Credits:

35 |

Resources used within boilerplate:

36 |
37 | 38 | 46 |
47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/views/pages/errors/404.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import Card from '../../tags/card'; 3 | 4 | export default function (props) { 5 | return ( 6 |
7 | 8 |

404 Page

9 |

Looks like you were given a bad link ;-)

10 |
{ props.url }
11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/views/pages/home.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Link } from 'preact-router'; 3 | import Card from '../tags/card'; 4 | 5 | export default function (props) { 6 | return ( 7 |
8 | 9 |

Home

10 |

This is the home page.

11 | 12 |

You should check out:

13 | 17 |
18 | 19 | 20 |

Features:

21 |
    22 |
  • Offline Caching (via `serviceWorker`)
  • 23 |
  • SASS & Autoprefixer
  • 24 |
  • Asset Versioning (aka "cache-busting")
  • 25 |
  • ES2015 (ES6) and ES2016 (ES7) support
  • 26 |
  • Hot Module Replacement (HMR) for all files
  • 27 |
  • Preact Developer Tools
  • 28 |
  • Lighthouse approved (100/100)
  • 29 |
30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/views/tags/card-link.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Link } from 'preact-router'; 3 | 4 | export default function (props) { 5 | return ( 6 | { props.children } 7 | ) 8 | }; 9 | -------------------------------------------------------------------------------- /src/views/tags/card.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | 3 | export default function (props) { 4 | return
{ props.children }
5 | } 6 | -------------------------------------------------------------------------------- /src/views/tags/header.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Link } from 'preact-router'; 3 | 4 | export default function () { 5 | return ( 6 |
7 |

Preact Starter

8 | 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/views/tags/layout.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import Header from './header'; 3 | 4 | export default function (props) { 5 | return ( 6 |
7 |
8 |
9 | { props.children } 10 |
11 |
12 | ); 13 | } 14 | --------------------------------------------------------------------------------