Generate scaffolding with preact-cli default
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"test": {
|
||||||
|
"presets": [
|
||||||
|
["preact-cli/babel", { "modules": "commonjs" }]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/*.log
|
||||||
|
*.lock
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# preact-publicpath
|
||||||
|
|
||||||
|
## CLI Commands
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
# install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# serve with hot reload at localhost:8080
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# build for production with minification
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# test the production build locally
|
||||||
|
npm run serve
|
||||||
|
|
||||||
|
# run tests with jest and preact-render-spy
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md).
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "preact-publicpath",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"start": "per-env",
|
||||||
|
"start:production": "npm run -s serve",
|
||||||
|
"start:development": "npm run -s dev",
|
||||||
|
"build": "preact build",
|
||||||
|
"serve": "preact build && preact serve",
|
||||||
|
"dev": "preact watch",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "eslint-config-synacor"
|
||||||
|
},
|
||||||
|
"eslintIgnore": [
|
||||||
|
"build/*"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^4.9.0",
|
||||||
|
"eslint-config-synacor": "^2.0.2",
|
||||||
|
"identity-obj-proxy": "^3.0.0",
|
||||||
|
"per-env": "^1.0.2",
|
||||||
|
"jest": "^21.2.1",
|
||||||
|
"preact-cli": "^2.1.0",
|
||||||
|
"preact-render-spy": "^1.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"preact": "^8.2.6",
|
||||||
|
"preact-compat": "^3.17.0",
|
||||||
|
"preact-router": "^2.5.7"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"verbose": true,
|
||||||
|
"setupFiles": [
|
||||||
|
"<rootDir>/tests/__mocks__/browserMocks.js"
|
||||||
|
],
|
||||||
|
"testRegex": "(/(__tests__|tests)/.*|(\\.|/)(test|spec))\\.jsx?$",
|
||||||
|
"testPathIgnorePatterns": [
|
||||||
|
"/node_modules/",
|
||||||
|
"<rootDir>/tests/__mocks__/*"
|
||||||
|
],
|
||||||
|
"testURL": "http://localhost:8080",
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"jsx"
|
||||||
|
],
|
||||||
|
"moduleDirectories": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/tests/__mocks__/fileMock.js",
|
||||||
|
"\\.(css|less|scss)$": "identity-obj-proxy",
|
||||||
|
"^./style$": "identity-obj-proxy",
|
||||||
|
"^preact$": "<rootDir>/node_modules/preact/dist/preact.min.js",
|
||||||
|
"^react$": "preact-compat",
|
||||||
|
"^react-dom$": "preact-compat",
|
||||||
|
"^create-react-class$": "preact-compat/lib/create-react-class",
|
||||||
|
"^react-addons-css-transition-group$": "preact-css-transition-group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 626 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,32 @@
|
|||||||
|
import { h, Component } from 'preact';
|
||||||
|
import { Router } from 'preact-router';
|
||||||
|
|
||||||
|
import Header from './header';
|
||||||
|
|
||||||
|
// Code-splitting is automated for routes
|
||||||
|
import Home from '../routes/home';
|
||||||
|
import Profile from '../routes/profile';
|
||||||
|
|
||||||
|
export default class App extends Component {
|
||||||
|
|
||||||
|
/** Gets fired when the route changes.
|
||||||
|
* @param {Object} event "change" event from [preact-router](http://git.io/preact-router)
|
||||||
|
* @param {string} event.url The newly routed URL
|
||||||
|
*/
|
||||||
|
handleRoute = e => {
|
||||||
|
this.currentUrl = e.url;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id="app">
|
||||||
|
<Header />
|
||||||
|
<Router onChange={this.handleRoute}>
|
||||||
|
<Home path="/" />
|
||||||
|
<Profile path="/profile/" user="me" />
|
||||||
|
<Profile path="/profile/:user" />
|
||||||
|
</Router>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import { Link } from 'preact-router/match';
|
||||||
|
import style from './style';
|
||||||
|
|
||||||
|
const Header = () => (
|
||||||
|
<header class={style.header}>
|
||||||
|
<h1>Preact App</h1>
|
||||||
|
<nav>
|
||||||
|
<Link activeClassName={style.active} href="/">Home</Link>
|
||||||
|
<Link activeClassName={style.active} href="/profile">Me</Link>
|
||||||
|
<Link activeClassName={style.active} href="/profile/john">John</Link>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Header;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
.header {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 56px;
|
||||||
|
padding: 0;
|
||||||
|
background: #673AB7;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
float: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 15px;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 56px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header nav {
|
||||||
|
float: right;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header nav a {
|
||||||
|
display: inline-block;
|
||||||
|
height: 56px;
|
||||||
|
line-height: 56px;
|
||||||
|
padding: 0 15px;
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(255,255,255,0);
|
||||||
|
text-decoration: none;
|
||||||
|
color: #FFF;
|
||||||
|
will-change: background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header nav a:hover,
|
||||||
|
.header nav a:active {
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header nav a.active {
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import './style';
|
||||||
|
import App from './components/app';
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "preact-publicpath",
|
||||||
|
"short_name": "preact-publicpath",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"background_color": "#fff",
|
||||||
|
"theme_color": "#673ab8",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/assets/icons/android-chrome-192x192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/assets/icons/android-chrome-512x512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import style from './style';
|
||||||
|
|
||||||
|
const Home = () => (
|
||||||
|
<div class={style.home}>
|
||||||
|
<h1>Home</h1>
|
||||||
|
<p>This is the Home component.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Home;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.home {
|
||||||
|
padding: 56px 20px;
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { h, Component } from 'preact';
|
||||||
|
import style from './style';
|
||||||
|
|
||||||
|
export default class Profile extends Component {
|
||||||
|
state = {
|
||||||
|
time: Date.now(),
|
||||||
|
count: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
// update the current time
|
||||||
|
updateTime = () => {
|
||||||
|
this.setState({ time: Date.now() });
|
||||||
|
};
|
||||||
|
|
||||||
|
increment = () => {
|
||||||
|
this.setState({ count: this.state.count+1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
// gets called when this route is navigated to
|
||||||
|
componentDidMount() {
|
||||||
|
// start a timer for the clock:
|
||||||
|
this.timer = setInterval(this.updateTime, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets called just before navigating away from the route
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: `user` comes from the URL, courtesy of our router
|
||||||
|
render({ user }, { time, count }) {
|
||||||
|
return (
|
||||||
|
<div class={style.profile}>
|
||||||
|
<h1>Profile: {user}</h1>
|
||||||
|
<p>This is the user profile for a user named { user }.</p>
|
||||||
|
|
||||||
|
<div>Current time: {new Date(time).toLocaleString()}</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button onClick={this.increment}>Click Me</button>
|
||||||
|
{' '}
|
||||||
|
Clicked {count} times.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.profile {
|
||||||
|
padding: 56px 20px;
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: #FAFAFA;
|
||||||
|
font-family: 'Helvetica Neue', arial, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #444;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage
|
||||||
|
/**
|
||||||
|
* An example how to mock localStorage is given below 👇
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Mocks localStorage
|
||||||
|
const localStorageMock = (function() {
|
||||||
|
let store = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getItem: (key) => store[key] || null,
|
||||||
|
setItem: (key, value) => store[key] = value.toString(),
|
||||||
|
clear: () => store = {}
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'localStorage', {
|
||||||
|
value: localStorageMock
|
||||||
|
}); */
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// This fixed an error related to the CSS and loading gif breaking my Jest test
|
||||||
|
// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets
|
||||||
|
module.exports = 'test-file-stub';
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import Header from '../src/components/header';
|
||||||
|
import { Link } from 'preact-router/match';
|
||||||
|
// See: https://github.com/mzgoddard/preact-render-spy
|
||||||
|
import { shallow } from 'preact-render-spy';
|
||||||
|
|
||||||
|
describe('Initial Test of the Header', () => {
|
||||||
|
test('Header renders 3 nav items', () => {
|
||||||
|
const context = shallow(<Header />);
|
||||||
|
expect(context.find('h1').text()).toBe('Preact App');
|
||||||
|
expect(context.find(<Link />).length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||