Frontend Starter Kit
This seed repository provides the following features:
- ---------- Essentials ----------
- User interface components with Material.
- Backend cloud services with Firebase.
- Routing and navigation with Page.
- Utility functions with Lodash.
- Reactive extensions with ReactiveX.
- Scalable state management with MobX.
- Immutable collections with Immer.
- Data visualizations with D3.
- 3D scene graphs with Three.
- ---------- Tools ----------
- Module bundler with Parcel.
- HTML transformations with PostHTML.
- Future CSS features with PostCSS.
- Next generation JavaScript with Babel.
- Synchronised browser with BrowserSync.
- HTML static code analyzer with HTMLHint.
- CSS static code analyzer with StyleLint.
- JavaScript static code analyzer with ESLint.
- Type annotations with Flow.
- Testing platform with Jest.
- UI testing with Puppeteer.
- Test coverage integration with Codecov.
- Error tracking with Sentry.
- ---------- Environments ----------
- Client-side platform with HTML5.
- Operating system with Linux.
- Text editor with Atom.
- Version control with Git.
- Code repository with GitHub.
- Fast and deterministic builds with Yarn.
- Software container with Docker.
- Continuous integration with CircleCI.
Here are some related seed repositories:
- ---------- Client-side ----------
- Frontend Starter Kit - Make for Progressive Web Apps.
- Cordova Mobile Starter - Make for Cross-platform Mobile Apps.
- Electron Desktop Starter - Make for Cross-platform Desktop Apps.
- ---------- Server-side ----------
- Backend Starter Kit - Make for Flexible Cloud Platform.
- Firebase Functions Starter - Make for Serverless Cloud Functions.
- Kubernetes Engine Starter - Make for Cloud Infrastructure Container.
Table of Contents
Getting Started
- Clone this Boilerplate
$ git clone --depth 1 https://github.com/Shyam-Chen/Frontend-Starter-Kit.git <PROJECT_NAME>
$ cd <PROJECT_NAME>
- Install Dependencies
$ yarn install
# then install types
$ yarn typed
- Run the Application
$ yarn start
# or
$ yarn dev
- Run the Test
# unit testing
$ yarn test
# or
$ yarn unit
# lint the code
$ yarn lint
# check the type
$ yarn flow
# e2e testing
$ yarn e2e # need to run `yarn dev` first
Practical Examples
- CRUD Operations
- Static
- REST (
axios
) - GraphQL (
apollo-client
)
- Form Controls
- Template-driven (
mobx
) - Reactive Forms (
rxjs
)
- Template-driven (
- Data Table
- Static
- REST (
axios
) - GraphQL (
apollo-client
)
- Globalization
- Internationalization
- Localization
- Authorization
- REST (
axios
) - GraphQL (
apollo-client
)
- REST (
- Data Chart
- SVG (
d3
) - Canvas (
d3
) - WebGL (
three
)
- SVG (
- Realtime
- WebSockets (
socket.io-client
) - GraphQL Subscriptions (
subscriptions-transport-ws
)
- WebSockets (
- Playground
- Counter
- State Management (
mobx
) - Asynchronous (
rxjs
)
- State Management (
- ...
- Counter
Dockerization
- Build and run the Container
$ docker-compose up
- Run a command in a running container
$ docker-compose exec app <COMMAND>
- Remove the old container before creating the new one
$ docker-compose rm -fs
Configuration
Default configuration
// tools/env.js
export const SITE_URL = process.env.SITE_URL || 'https://web-go-demo.firebaseapp.com';
export const FUNC_URL = process.env.FUNC_URL || 'https://us-central1-web-go-demo.cloudfunctions.net';
export const INDEX_ENV = {
APP_BASE: process.env.APP_BASE || '/',
GOOGLE_ANALYTICS: process.env.GOOGLE_ANALYTICS || 'UA-84381641-2'
};
export const APP_ENV = {
FIREBASE_CONFIG: {
apiKey: process.env.FIREBASE_KEY || 'AIzaSyDBA0yVS0JuIqGaoN9nafvPFxPSVgmxwnw',
authDomain: process.env.FIREBASE_DOMAIN || 'web-go-demo.firebaseapp.com',
databaseURL: process.env.FIREBASE_PROJECT || 'https://web-go-demo.firebaseio.com',
projectId: process.env.FIREBASE_DATABASE || 'web-go-demo',
storageBucket: process.env.FIREBASE_STORAGE || 'web-go-demo.appspot.com',
messagingSenderId: process.env.FIREBASE_MESSAGING || '584431831746'
},
SENTRY_URL: process.env.SENTRY_URL || 'https://70484e0dda784a1081081ca9c8237792@sentry.io/236866',
FUNC_URL
};
export const DEV_PORT = process.env.DEV_PORT || 8000;
export const TEST_PORT = process.env.TEST_PORT || 8080;
export const PROXY_URL = process.env.PROXY_URL || 'http://localhost:3000'
Using Libraries
- Example of Component
<!-- src/shared/new-component/new-component.html -->
<div class="<%= style['card'] %>">
<div class="<%= style['card-title'] %>"><%= title %></div>
<div class="<%= style['card-content'] %>"><%= content %></div>
</div>
<div class="<%= style['card'] %>">
<div class="<%= style['card-title'] %> <%= style['card-title--unfancy'] %>"><%= title %></div>
<div class="<%= style['card-content'] %>"><%= content %></div>
</div>
/* src/shared/new-component/new-component.css */
.card { // element
// ...
&-title { // element
// ...
&--unfancy { // modifier
// ...
}
}
&-content { // element
// ...
}
}
// src/shared/new-component/new-component.js
import { template as _ } from 'lodash';
import template from './new-component.html';
import style from './new-component.css';
export const newComponent = (name, data) =>
document.querySelector(`#${name}`)
.innerHTML = _(template, { imports: { style } })(data);
// src/shared/new-component/index.js
export * from './new-component';
// use the new component
import { newComponent } from '~/components/new-component';
newComponent('ex-1', { title: 'Title 1', content: 'Content 1' });
newComponent('ex-2', { title: 'Title 2', content: 'Content 2' });
<!-- use the new component -->
<div id="ex-1"></div>
<div id="ex-2"></div>
- Example of Route
// src/pages/new-route/new-route.js
import { template as _ } from 'lodash';
import { layout } from '~/components/layout';
export const newRoute = () => {
page('/ex', () => {
layout(_(`<p>New Route</p>`)(), 'ex');
});
};
// src/pages/new-route/index.js
export * from './new-route';
// src/app.js
import { newRoute } from './pages/new-route';
newRoute();
- Example of REST
import axios from 'axios';
const API_LIST = 'https://web-go-demo.herokuapp.com/__/list';
axios.get(API_LIST)
.then(res => console.log(res.data))
.then(() => console.log('done'));
axios.get(API_LIST, { params: { text: 'a' } })
.then(res => console.log(res.data))
.then(() => console.log('done'));
axios.post(API_LIST, { text: 'Web GO' })
.then(res => console.log(res.data))
.then(() => console.log('done'));
axios.put(`${API_LIST}/5943881e058f440012d4ae47`, { text: 'Web GO' })
.then(res => console.log(res.data))
.then(() => console.log('done'));
axios.delete(`${API_LIST}/594388af058f440012d4ae49`)
.then(res => console.log(res.data))
.then(() => console.log('done'));
- Example of GraphQL
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import gql from 'graphql-tag';
const client = new ApolloClient({
networkInterface: createNetworkInterface({
uri: 'https://web-go-demo.herokuapp.com/__/graphql'
})
});
client
.query({
query: gql`
query List {
list { _id text }
}
`
})
.then(res => console.log(res.data));
client
.query({
query: gql`
query List {
list(text: "a") { _id text }
}
`
})
.then(res => console.log(res.data));
client
.mutate({
mutation: gql`
mutation List {
addText(text: "Web GO") { _id text }
}
`
})
.then(res => console.log(res.data));
client
.mutate({
mutation: gql`
mutation List {
updateText(_id: "5943881e058f440012d4ae47", text: "Web GO") { _id text }
}
`
})
.then(res => console.log(res.data));
client
.mutate({
mutation: gql`
mutation List {
deleteText(_id: "594388af058f440012d4ae49") { _id text }
}
`
})
.then(res => console.log(res.data));
- Example of Socket Client
import io from 'socket.io-client';
const socket = io('wss://web-go-demo.herokuapp.com/');
socket.on('connect', () => console.log('Accept a connection.'));
socket.on('A', data => {
console.log(data);
socket.emit('B', { foo: 'baz' });
});
- Example of GraphQL Subscriptions
import { SubscriptionClient } from 'subscriptions-transport-ws';
import ApolloClient from 'apollo-client';
const client = new SubscriptionClient(
'wss://web-go-demo.herokuapp.com/__/graphql',
{ reconnect: true }
);
const apolloClient = new ApolloClient({
networkInterface: client
});
- Example of Lodash
import { lowerFirst, pad } from 'lodash';
import { Observable } from 'rxjs';
import { of } from 'rxjs/observable';
Observable::of(lowerFirst('Hello'), pad('World', 5))
.subscribe(value => console.log(value));
// hello
// World
- Example of ReactiveX
import { Observable } from 'rxjs';
import { timer, of } from 'rxjs/observable';
import { mapTo, combineAll } from 'rxjs/operator';
Observable::timer(2000)
::mapTo(Observable::of('Hello', 'World'))
::combineAll()
.subscribe(value => console.log(value));
// ["Hello"]
// ["World"]
- Example of MobX
import { observable, action, autorun } from 'mobx';
const store = observable({
value: 0,
increment: action(() => store.value++),
decrement: action(() => store.value--),
incrementAsync: action(() => setTimeout(() => store.increment(), 1000)),
incrementIfOdd: action(() => {
if (Math.abs(store.value % 2) === 1) {
store.increment();
}
}),
get evenOrOdd() {
return store.value % 2 === 0 ? 'even' : 'odd';
}
});
autorun(() => {
console.log(store.value, store.evenOrOdd); // 0, even
store.increment(); // 0 -> 1
console.log(store.value, store.evenOrOdd); // 1, odd
store.incrementAsync(); // 1 -> 2
store.incrementAsync(); // 2 -> 3
console.log(store.value, store.evenOrOdd); // 3, odd
store.incrementIfOdd(); // 3 -> 4
store.incrementIfOdd(); // 4 -> 4
onsole.log(store.value, store.evenOrOdd); // 4, even
});
- Example of Immer
import produce from 'immer';
const baseState = [
{ label: 'Babel', done: true },
{ label: 'Flow', done: false }
];
const nextState = produce(baseState, draftState => {
draftState.push({ label: 'TypeScript' });
draftState[1].done = true;
});
baseState.length; // 2
nextState.length; // 3
- Example of D3
import { select } from 'd3-selection';
const exWidth = 400;
const exHeight = 200;
const exDataset = [5, 10, 15, 20, 15, 10, 15, 20, 15, 10, 5];
const exSvgEl = select('#ex')
.append('svg')
.attr('width', exWidth)
.attr('height', exHeight);
exSvgEl.selectAll('rect')
.data(exDataset)
.enter()
.append('rect')
.attr('x', (data, index) => index * (exWidth / exDataset.length))
.attr('fill', data => `rgba(200, 100, 200, ${data / 25})`)
.attr('y', data => exHeight - (data * 4))
.attr('width', exWidth / exDataset.length - 1)
.attr('height', data => data * 4);
<div id="ex"></div>
- Example of Three
import { PerspectiveCamera, Scene, BoxGeometry, MeshBasicMaterial, Mesh, WebGLRenderer } from 'three';
let [camera, scene, renderer, geometry, material, mesh] = [];
const init = () => {
camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 1000;
scene = new Scene();
geometry = new BoxGeometry(200, 200, 200);
material = new MeshBasicMaterial({ color: 0xff0000, wireframe: true });
mesh = new Mesh(geometry, material);
scene.add(mesh);
renderer = new WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.querySelector('#ex')
.appendChild(renderer.domElement);
};
const animate = () => {
requestAnimationFrame(animate);
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
};
init();
animate();
<div id="ex"></div>
Directory Structure
.
βββ flow-typed -> module types
βββ src
β βββ assets -> audios, datas, fonts, images, styles, videos
β βββ pages
β β βββ <feature>
β β βββ _languages -> internationalization
β β βββ _includes -> lite components
β β β βββ <feature>.html
β β βββ _components -> feature components
β β β βββ <feature>
β β β βββ <feature>.{html,css,js,spec.js}
β β β βββ index.js
β β βββ _<custom> -> private object
β β βββ <feature>.{html,css,js,spec.js}
β β βββ index.js
β βββ shared -> shared components
β βββ utils -> utility functions
β βββ app.js
β βββ index.html
β βββ polyfills.js -> shims, pre-vendor
β βββ vendor.js -> third-party libraries
βββ test -> E2E testing
βββ tools
β βββ config
β β βββ {babel,postcss,reshape,rollup}.js
β βββ rules
β β βββ database.json,storage
β βββ tasks
β β βββ {app,build,chunkhash,copy,entrypoint,lint,polyfills,precache,serve,sitemap,vendor,watch}.js
β βββ utils
β β βββ {empty-mapper,handle-errors,index,inject-service,resolve-id,service-worker,setup-files}.js
β βββ env.js
βββ .babelrc
βββ .editorconfig
βββ .eslintrc
βββ .firebaserc
βββ .flowconfig
βββ .gitattributes
βββ .gitignore
βββ .htmlhintrc
βββ .stylelintrc
βββ Dockerfile
βββ LICENSE
βββ README.md
βββ circle.yml
βββ docker-compose.yml
βββ firebase.json
βββ gulpfile.babel.js
βββ jest.config.js
βββ package.json
βββ yarn.lock