Completely restructure all files to use Lerna, Webpack and Babel.
gitea/icedream/vizon-countdown-website/feature%2Fwebpack This commit looks good Details

We're completely moving away from nwb with this commit and fully set up
on developing a backend and a frontend as two separate code bases.
feature/webpack
Icedream 2017-08-29 00:44:56 +02:00
parent 3c633d23ea
commit 93a86f8122
Signed by: icedream
GPG Key ID: 1573F6D8EFE4D0CF
85 changed files with 876 additions and 13964 deletions

View File

@ -4,13 +4,12 @@
"targets": { "targets": {
"node": true, "node": true,
"uglify": false "uglify": false
}, }
"spec": true,
"debug": false
}] }]
], ],
"plugins": [ "plugins": [
"babel-plugin-transform-class-properties", "babel-plugin-transform-class-properties",
"babel-plugin-syntax-dynamic-import" "babel-plugin-transform-runtime",
"babel-plugin-dynamic-import-node"
] ]
} }

View File

@ -22,11 +22,12 @@ build/Release
# Dependency directory # Dependency directory
# Deployed apps should consider commenting this line out: # Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules/** **/node_modules/**
!node_modules/.gitkeep !**/node_modules/.gitkeep
# Webpack output # Webpack output
/dist packages/*/dist
packages/*/lib
# Intermediate build files (cache, etc.) # Intermediate build files (cache, etc.)
/build /build

View File

@ -22,11 +22,12 @@ build/Release
# Dependency directory # Dependency directory
# Deployed apps should consider commenting this line out: # Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules/** **/node_modules/**
!node_modules/.gitkeep !**/node_modules/.gitkeep
# Webpack output # Webpack output
/dist packages/*/dist
packages/*/lib
# Intermediate build files (cache, etc.) # Intermediate build files (cache, etc.)
/build /build

View File

@ -12,8 +12,19 @@ env:
es6: true es6: true
rules: rules:
no-underscore-dangle: 'off' no-console: off
no-plusplus: no-plusplus:
- error - error
- allowForLoopAfterthoughts: true - allowForLoopAfterthoughts: true
no-underscore-dangle: 'off'
react/no-array-index-key: 0 react/no-array-index-key: 0
overrides:
- files:
- "config/**"
- "**/webpack.config*.js"
- "**/.babelrc*"
rules:
import/no-extraneous-dependencies:
- error
- devDependencies: true

7
.gitignore vendored
View File

@ -22,11 +22,12 @@ build/Release
# Dependency directory # Dependency directory
# Deployed apps should consider commenting this line out: # Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules/** **/node_modules/**
!node_modules/.gitkeep !**/node_modules/.gitkeep
# Webpack output # Webpack output
/dist packages/*/dist
packages/*/lib
# Intermediate build files (cache, etc.) # Intermediate build files (cache, etc.)
/build /build

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

View File

@ -1,16 +1,16 @@
sudo: false # sudo: false
#
language: node_js # language: node_js
node_js: # node_js:
- 6 # - 6
#
before_install: # before_install:
- npm install codecov.io coveralls # - npm install codecov.io coveralls
#
after_success: # after_success:
- cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js # - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js # - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
#
branches: # branches:
only: # only:
- master # - master

View File

@ -1,4 +0,0 @@
FROM icedream/caddy
WORKDIR /data
COPY ./dist/ /data

58
Jenkinsfile vendored
View File

@ -1,29 +1,29 @@
node("docker && linux && amd64") { // node("docker && linux && amd64") {
checkout scm // checkout scm
//
docker.image("node:8.2.1").inside { // docker.image("node:8.2.1").inside {
// Install dependencies // // Install dependencies
sh "npm install" // sh "npm install"
//
// Build website with npm // // Build website with npm
sh "npm run build" // sh "npm run build"
archive "dist/**" // archive "dist/**"
} // }
//
// Build docker image to be deployed // // Build docker image to be deployed
def image = docker.build("docker.dreamnetwork.oss:5000/icedream/carl-kittelberger-website:${env.BRANCH_NAME ?: "latest"}", "-f Dockerfile.dist .") // def image = docker.build("docker.dreamnetwork.oss:5000/icedream/carl-kittelberger-website:${env.BRANCH_NAME ?: "latest"}", "-f Dockerfile.dist .")
//
// @NOTE - https://issues.jenkins-ci.org/browse/JENKINS-42152?focusedCommentId=307976&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-307976 // // @NOTE - https://issues.jenkins-ci.org/browse/JENKINS-42152?focusedCommentId=307976&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-307976
image.push() // image.push()
if (env.BRANCH_NAME) { // if (env.BRANCH_NAME) {
switch(env.BRANCH_NAME) { // switch(env.BRANCH_NAME) {
case "master": // case "master":
image.push("latest") // image.push("latest")
break // break
default: // default:
image.push() // image.push()
break // break
} // }
} // }
image.push("${sh(script: "git describe --tags --always", returnStdout: true).trim()}") // image.push("${sh(script: "git describe --tags --always", returnStdout: true).trim()}")
} // }

View File

@ -0,0 +1,133 @@
import autoprefixer from 'autoprefixer';
export default class Environment {
constructor(options) {
this.development = true;
this.production = false;
this.server = false;
this.autoprefixerTargets = [
'> 1%',
'last 4 versions',
'Firefox ESR',
'ios >= 8',
];
this.locales = ['en'];
if (options !== undefined && options !== null) {
this.input(options);
}
}
input(options) {
if (options) {
switch (true) {
case typeof (options) === 'string': // string
this.inputString(options);
break;
case Array.isArray(options): // array
options.forEach((arg) => { this.input(arg); });
break;
default: // object
Object.keys(options).forEach((k) => {
this[k] = options[k] || this[k];
});
break;
}
} else if (process.env.NODE_ENV) {
this.inputString(process.env.NODE_ENV);
}
}
inputString(env) {
switch (env.toLowerCase()) {
case 'development':
this.development = true;
this.production = false;
break;
case 'production':
this.development = false;
this.production = true;
break;
case 'server':
this.server = true;
break;
default:
console.warn('Unknown environment:', env);
break;
}
}
styleLoaders(...preprocessingLoaders) {
const {
production,
autoprefixerTargets,
server,
ExtractTextPlugin, // @HACK
} = this;
if (!ExtractTextPlugin) {
throw new Error('Need a valid ExtractTextPlugin fed into the environment object.');
}
let cssLoaders = [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true,
modules: true,
localIdentName: production
? '[name]__[local]--[hash:base64:5]'
: '[name]__[local]--[hash:base64:5]',
},
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
autoprefixer({
browsers: autoprefixerTargets,
grid: false,
}),
],
sourceMap: true,
},
},
].filter(loader => loader !== false);
if (preprocessingLoaders && preprocessingLoaders.length > 0) {
cssLoaders.push(
{
loader: 'resolve-url-loader',
options: {
fail: true,
silent: false,
},
},
...preprocessingLoaders.map(loader => Object.assign({}, loader, {
options: Object.assign({}, loader.options || {}, {
sourceMap: true,
}),
})),
);
}
if (!server) {
const fallback = cssLoaders.shift();
cssLoaders = ExtractTextPlugin.extract({
fallback,
use: cssLoaders,
});
}
return cssLoaders;
}
}

View File

@ -0,0 +1,26 @@
/**
* Plugin for HtmlPlugin which inlines content for an extracted Webpack manifest
* into the HTML in a <script> tag before other emitted asssets are injected by
* HtmlPlugin itself.
*/
export default function injectManifestPlugin() {
this.plugin('compilation', (compilation) => {
compilation.plugin('html-webpack-plugin-before-html-processing', (data, cb) => {
Object.keys(compilation.assets).forEach((key) => {
if (!key.startsWith('manifest.')) { return; }
const { children } = compilation.assets[key];
if (children && children[0]) {
// eslint-disable-next-line no-param-reassign
data.html = data.html.replace(/^(\s*)<\/body>/m, `$1<script>${children[0]._value}</script>\n$1</body>`);
// Remove the manifest from HtmlPlugin's assets to prevent a <script>
// tag being created for it.
const manifestIndex = data.assets.js.indexOf(data.assets.publicPath + key);
data.assets.js.splice(manifestIndex, 1);
// eslint-disable-next-line no-param-reassign
delete data.assets.chunks.manifest;
}
});
cb(null, data);
});
});
}

View File

@ -31,10 +31,13 @@ services:
- ".:/src" - ".:/src"
- "web_npm_cache:/var/cache/npm" - "web_npm_cache:/var/cache/npm"
- "/src/node_modules" - "/src/node_modules"
- "/src/packages/frontend/node_modules"
- "/src/packages/backend/node_modules"
ports: ports:
- "3000:3000" - "3000:3000"
working_dir: /src working_dir: /src
environment: environment:
NODE_ENV: development
NPM_CONFIG_CACHE: /var/cache/npm NPM_CONFIG_CACHE: /var/cache/npm
PORT: "3000" PORT: "3000"
MYSQL_HOST: db MYSQL_HOST: db

View File

@ -1,4 +1,4 @@
FROM node:7-alpine FROM node:8.2-alpine
RUN \ RUN \
apk add --no-cache \ apk add --no-cache \
@ -11,7 +11,7 @@ RUN \
tar \ tar \
make make
RUN npm install npm@5.x \ # RUN npm install npm@5.x \
&& rm -rf /usr/local/lib/node_modules \ # && rm -rf /usr/local/lib/node_modules \
&& mv node_modules /usr/local/lib \ # && mv node_modules /usr/local/lib \
&& npm install -g npm@5.x # && npm install -g npm@5.x

View File

@ -1,3 +1,3 @@
#!/bin/sh -e #!/bin/sh -e
npm install npm install --unsafe-perm
npm start npm start

134
index.js
View File

@ -1,134 +0,0 @@
const path = require('path');
const mysql = require('mysql');
const express = require('express');
const moment = require('moment-timezone');
const fs = require('fs');
const bodyParser = require('body-parser');
/* Database setup */
const dbPool = mysql.createPool({
host: process.env.MYSQL_HOST || 'localhost',
user: process.env.MYSQL_USER || 'root',
password: process.env.MYSQL_PASSWORD || undefined,
database: process.env.MYSQL_DATABASE || 'vizon',
socketPath: process.env.MYSQL_SOCKET_PATH || undefined,
timezone: process.env.TZ || 'local',
debug: process.env.NODE_ENV === 'development',
});
const tableNames = {
drawings: process.env.MYSQL_TABLE_DRAWINGS || 'vizon_drawings',
users: process.env.MYSQL_TABLE_USERS || 'vizon_users',
rankings: 'vizon_web_rankings',
};
/* Frontend server setup */
const frontendDir = path.resolve(__dirname, 'dist');
const app = express();
app.set('port', (process.env.PORT || 3000));
if (process.NODE_ENV !== 'production') {
console.log('Serving development build with webpack dev middleware');
// eslint-disable-next-line import/no-extraneous-dependencies
require('babel-register');
const webpackConfig = require('./webpack.config.babel.js').default({
server: true,
});
const compiler = require('webpack')(webpackConfig);
app.use(require('webpack-dev-middleware')(compiler, webpackConfig.devServer));
app.use(require('webpack-hot-middleware')(compiler, {
log: false,
}));
app.get('/api/dev/webpackConfig', (req, res) => {
res.setHeader('content-type', 'text/plain');
res.send(require('util').inspect(webpackConfig, { depth: null, colors: false }));
});
} else {
console.log(`Serving static build from ${frontendDir}`);
app.use('/', express.static(frontendDir));
}
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Additional middleware which will set headers that we need on each request.
app.use((req, res, next) => {
// Disable caching so we'll always get the latest comments.
res.setHeader('Cache-Control', 'no-cache');
next();
});
function calculateRankings(id) {
db.query('SELECT * FROM `vizon_web_rankings` WHERE `vizon_drawings_id`=?', [id], (err, results, fields) => {
if (err) {
console.error('Failed to query rankings from database:', err);
res.status(500).json({
error: 'Database query failed',
});
}
});
}
app.get('/api/status', (req, res) => {
dbPool.getConnection((err, db) => {
if (err) {
console.error('Failed to connect to database:', err);
res.status(500).json({
error: 'Database connection failed',
});
}
db.query('SELECT * FROM ?? ORDER BY ?? DESC LIMIT 0, 1', [
tableNames.drawings,
'id',
], (qerr, results, fields) => {
if (qerr) {
console.error('Failed to request drawings:', err);
res.status(500).json({
error: 'Database query failed',
});
return;
}
const row = results[0];
const id = row.id;
const date = moment(row.drawing_date);
const numbers = [
row.first,
row.second,
row.third,
row.fourth,
row.fifth,
row.sixth,
].map(i => parseInt(i, 10));
const ranking = [
// @TODO - calculate ranking from mysql tables {drawings} and {bets}
];
res.json({
lastDrawing: {
id,
date,
numbers,
ranking,
},
});
});
});
});
app.listen(app.get('port'), () => {
console.log(`Server started: http://localhost:${app.get('port')}/`);
});

7
lerna.json Normal file
View File

@ -0,0 +1,7 @@
{
"lerna": "2.1.0",
"packages": [
"packages/*"
],
"version": "0.2.0"
}

2
node_modules/.gitkeep generated vendored
View File

@ -0,0 +1,2 @@
This file is placed here to avoid Docker from setting up a root-owned host
volume directory here.

View File

@ -1,105 +0,0 @@
const { ContextReplacementPlugin } = require('webpack');
module.exports = {
type: 'react-app',
babel: {
loose: false,
presets: [
'babel-preset-env',
],
plugins: [
'babel-plugin-transform-class-properties',
],
},
webpack: {
aliases: {
'moment-timezone': 'moment-timezone/builds/moment-timezone-with-data-2012-2022.js',
},
autoprefixer: '> 1%, last 4 versions, Firefox ESR, ios >= 8',
config(config) {
// Change config as you wish
// Select rules that match stylesheet files
const matchingLoaders = [
'sass-loader',
].map(require.resolve);
const newConfig = Object.assign({}, config);
newConfig.module.rules = config.module.rules.map((rule) => {
let enableSourceMap = false;
if (!rule.use) {
return rule;
}
return Object.assign({}, rule, {
use: rule.use.reduce((result, loaderDescriptor) => {
const resolvedLoader = require.resolve(loaderDescriptor.loader);
// Make sure resolve-url-loader is inserted right before compatible loaders
if (matchingLoaders.indexOf(resolvedLoader) >= 0) {
result.push({
loader: require.resolve('resolve-url-loader'),
options: {
fail: true,
silent: false,
},
});
enableSourceMap = true;
}
const finalLoaderDescriptor = Object.assign({}, loaderDescriptor);
// Avoid PostCSS discarding all source map information
if (enableSourceMap || resolvedLoader === require.resolve('postcss-loader')) {
finalLoaderDescriptor.options = finalLoaderDescriptor.options || {};
finalLoaderDescriptor.options.sourceMap = true;
}
result.push(finalLoaderDescriptor);
return result;
}, []),
});
});
return newConfig;
},
extra: {
plugins: [
new ContextReplacementPlugin(/moment[/\\]locale$/, /en\.js/),
],
resolve: {
extensions: [
'.jsx',
],
},
},
publicPath: '',
rules: {
babel: {
test: /\.jsx?/,
},
'sass-css': {
modules: true,
localIdentName: '[name]__[local]__[hash:base64:5]',
},
},
},
};
// @HACK - workaround for https://github.com/webpack/webpack/issues/1866
function batchresolve(arr) {
return arr.map(require.resolve);
}
module.exports.babel.presets = batchresolve(module.exports.babel.presets);
module.exports.babel.plugins = batchresolve(module.exports.babel.plugins);

13445
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,10 @@
{ {
"name": "vizon-countdown-website", "name": "@vizonweb/root",
"version": "0.2.0", "version": "0.2.0",
"description": "Website for a countdown to the next draw on VIzon",
"private": true, "private": true,
"main": "./index.js",
"scripts": { "scripts": {
"build": "webpack", "build": "lerna run build",
"build:production": "npm run -s build -- --env production", "clean": "lerna run clean",
"build:development": "npm run -s build -- --env development",
"clean": "rimraf build dist",
"docker-compose": "docker-compose -f docker-compose.local.yml", "docker-compose": "docker-compose -f docker-compose.local.yml",
"docker:down:clean": "npm run -s docker:down -- --rmi all -v", "docker:down:clean": "npm run -s docker:down -- --rmi all -v",
"docker:down": "npm run -s docker-compose -- down", "docker:down": "npm run -s docker-compose -- down",
@ -18,13 +14,13 @@
"docker:up:daemon": "npm run -s docker:up -- -d", "docker:up:daemon": "npm run -s docker:up -- -d",
"docker:up": "npm run -s docker-compose -- up --build", "docker:up": "npm run -s docker-compose -- up --build",
"docker": "npm run -s docker:up", "docker": "npm run -s docker:up",
"lerna": "lerna",
"lint": "eslint .", "lint": "eslint .",
"prepublish": "npm run -s build:production", "postinstall": "lerna bootstrap --hoist",
"start": "node .", "start": "cd packages/backend && npm run start",
"start:development": "webpack-dev-server --env development --env server", "test:coverage": "lerna run test:coverage",
"test:coverage": "nwb test-react --coverage", "test:watch": "lerna run test:watch",
"test:watch": "nwb test-react --server", "test": "lerna run test"
"test": "nwb test-react"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -37,65 +33,24 @@
"draw", "draw",
"countdown" "countdown"
], ],
"bin": "./index.js",
"files": [
"dist",
"index.js",
"README.md"
],
"author": "Carl Kittelberger <icedream@icedream.pw>", "author": "Carl Kittelberger <icedream@icedream.pw>",
"license": "UNLICENSED", "license": "UNLICENSED",
"devDependencies": { "devDependencies": {
"autoprefixer": "^7.1.2", "autoprefixer": "^7.1.2",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-env": "^1.6.0",
"case-sensitive-paths-webpack-plugin": "^2.1.1",
"chalk": "^2.1.0", "chalk": "^2.1.0",
"css-loader": "^0.28.5",
"eslint": "^4.5.0", "eslint": "^4.5.0",
"eslint-config-airbnb": "^15.1.0", "eslint-config-airbnb": "^15.1.0",
"eslint-plugin-babel": "^4.1.2", "eslint-plugin-babel": "^4.1.2",
"eslint-plugin-import": "^2.7.0", "eslint-plugin-import": "^2.7.0",
"eslint-plugin-json": "^1.2.0", "eslint-plugin-json": "^1.2.0",
"eslint-plugin-jsx-a11y": "^6.0.2", "eslint-plugin-jsx-a11y": "5.x",
"eslint-plugin-react": "^7.3.0", "eslint-plugin-react": "^7.3.0",
"extract-text-webpack-plugin": "^3.0.0", "lerna": "^2.1.0",
"file-loader": "^0.11.2", "nodemon": "^1.11.0",
"html-webpack-plugin": "^2.30.1",
"node-sass": "^4.5.3",
"normalize-scss": "^7.0.0",
"nwb": "^0.18.10",
"nwb-sass": "^0.8.1",
"postcss-loader": "^2.0.6",
"preact": "^8.2.4",
"preact-compat": "^3.17.0",
"progress-bar-webpack-plugin": "^1.10.0",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-fontawesome": "^1.6.1",
"react-helmet": "^5.1.3",
"react-hot-loader": "^3.0.0-beta.7",
"react-router": "^4.2.0",
"resolve-url-loader": "^2.1.0",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
"sass-loader": "^6.0.6", "slash": "^1.0.0"
"slash": "^1.0.0",
"style-loader": "^0.18.2",
"url-loader": "^0.5.9",
"webfontloader": "^1.6.28",
"webpack": "^3.5.5",
"webpack-dev-middleware": "^1.12.0",
"webpack-hot-middleware": "^2.18.2",
"webpack-merge": "^4.1.0"
}, },
"dependencies": { "dependencies": {
"body-parser": "^1.17.2", "moment-timezone": "^0.5.13"
"express": "^4.15.4",
"moment-timezone": "^0.5.13",
"mysql": "^2.14.1",
"nodemon": "^1.11.0"
} }
} }

34
packages/backend/.babelrc Normal file
View File

@ -0,0 +1,34 @@
{
"env": {
"es6": {
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"spec": true,
"debug": false,
"modules": false
}]
]
},
"node": {
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"spec": true,
"debug": false,
"modules": "commonjs"
}]
]
}
},
"plugins": [
"babel-plugin-transform-class-properties",
"babel-plugin-dynamic-import-node"
]
}

1
packages/backend/.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=true

View File

@ -0,0 +1,86 @@
{
"name": "@vizonweb/backend",
"version": "0.2.0",
"description": "VIzon lottery website",
"private": true,
"main": "./dist/node/index.js",
"jsnext:main": "./dist/es6/index.js",
"module": "./dist/es6/index.js",
"bin": {
"vizonweb": "./dist/node/index.js"
},
"scripts": {
"build:es6": "bnr build:es6",
"build:node": "bnr build:node",
"build": "run-p build:*",
"clean": "rimraf build dist",
"start": "bnr start",
"lint": "eslint .",
"prepublishOnly": "npm run -s build"
},
"betterScripts": {
"start": {
"command": "babel-node ./src",
"env": {
"BABEL_ENV": "node"
}
},
"build:es6": {
"command": "babel --out-dir dist/es6 src/",
"env": {
"BABEL_ENV": "es6"
}
},
"build:node": {
"command": "babel --out-dir dist/node src/",
"env": {
"BABEL_ENV": "node"
}
}
},
"repository": {
"type": "git",
"url": "ssh://git@git.icedream.tech:2222/icedream/vizon-countdown-website.git"
},
"keywords": [
"website",
"vizon",
"rizon",
"draw",
"countdown",
"backend"
],
"files": [
"dist",
"README.md"
],
"author": "Carl Kittelberger <icedream@icedream.pw>",
"license": "UNLICENSED",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
"better-npm-run": "^0.1.0",
"chalk": "^2.1.0",
"debug": "^3.0.1",
"eslint": "^4.5.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-jsx-a11y": "5.x",
"nodemon": "^1.11.0",
"npm-run-all": "^4.1.0",
"rimraf": "^2.6.1",
"slash": "^1.0.0"
},
"dependencies": {
"body-parser": "^1.17.2",
"express": "^4.15.4",
"moment-timezone": "^0.5.13",
"mysql": "^2.14.1",
"@vizonweb/frontend": "0.2.0"
}
}

View File

View File

@ -0,0 +1,127 @@
import path from 'path';
import mysql from 'mysql';
import express from 'express';
import moment from 'moment-timezone';
// const fs = require('fs');
import frontend from '@vizonweb/frontend';
import bodyParser from 'body-parser';
/* Database setup */
const dbPool = mysql.createPool({
host: process.env.MYSQL_HOST || 'localhost',
user: process.env.MYSQL_USER || 'root',
password: process.env.MYSQL_PASSWORD || undefined,
database: process.env.MYSQL_DATABASE || 'vizon',
socketPath: process.env.MYSQL_SOCKET_PATH || undefined,
timezone: process.env.TZ || 'local',
debug: process.env.NODE_ENV === 'development',
});
const tableNames = {
drawings: process.env.MYSQL_TABLE_DRAWINGS || 'vizon_drawings',
users: process.env.MYSQL_TABLE_USERS || 'vizon_users',
rankings: 'vizon_web_rankings',
};
/* Frontend server setup */
const app = express();
app.set('port', (process.env.PORT || 3000));
app.use('/', frontend());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Additional middleware which will set headers that we need on each request.
app.use((req, res, next) => {
// Disable caching so we'll always get the latest comments.
res.setHeader('Cache-Control', 'no-cache');
next();
});
/*
function calculateRankings(id) {
db.query('SELECT * FROM `vizon_web_rankings` WHERE `vizon_drawings_id`=?',
[id],
(err, results, fields) => {
if (err) {
console.error('Failed to query rankings from database:', err);
res.status(500).json({
error: 'Database query failed',
});
}
});
}
*/
app.post('/api/result/:id', (req, res) => {
res.status(501).json({});
});
app.get('/api/result/:id', (req, res) => {
res.status(501).json({});
});
app.get('/api/status', (req, res) => {
res.status(501).json({});
// dbPool.getConnection((err, db) => {
// if (err) {
// console.error('Failed to connect to database:', err);
// res.status(500).json({
// error: 'Database connection failed',
// });
// }
//
// db.query('SELECT * FROM ?? ORDER BY ?? DESC LIMIT 0, 1', [
// tableNames.drawings,
// 'id',
// ], (qerr, results, fields) => {
// if (qerr) {
// console.error('Failed to request drawings:', err);
// res.status(500).json({
// error: 'Database query failed',
// });
// return;
// }
//
// const row = results[0];
//
// const id = row.id;
//
// const date = moment(row.drawing_date);
//
// const numbers = [
// row.first,
// row.second,
// row.third,
// row.fourth,
// row.fifth,
// row.sixth,
// ].map(i => parseInt(i, 10));
//
// const ranking = [
// // @TODO - calculate ranking from mysql tables {drawings} and {bets}
// ];
//
// res.json({
// lastDrawing: {
// id,
// date,
// numbers,
// ranking,
// },
// });
// });
// });
});
app.listen(app.get('port'), () => {
console.log(`Server started: http://localhost:${app.get('port')}/`);
});

View File

@ -0,0 +1,16 @@
{
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"debug": true
}]
],
"plugins": [
"babel-plugin-transform-class-properties",
"babel-plugin-transform-runtime",
"babel-plugin-dynamic-import-node"
]
}

View File

@ -0,0 +1,8 @@
overrides:
- files:
- src/website/**/*.js
rules:
no-console: error
import/no-extraneous-dependencies:
- error
- devDependencies: true

1
packages/frontend/.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

2
packages/frontend/node_modules/.gitkeep generated vendored Normal file
View File

@ -0,0 +1,2 @@
This file is placed here to avoid Docker from setting up a root-owned host
volume directory here.

View File

@ -0,0 +1,127 @@
{
"name": "@vizonweb/frontend",
"version": "0.2.0",
"description": "VIzon lottery website",
"private": true,
"browser": "./browser/index.js",
"main": "./lib/middleware.node/index.js",
"jsnext:main": "./lib/middleware.es6/index.js",
"module": "./lib/middleware.es6/index.js",
"scripts": {
"build:website:development": "npm run -s build:website -- --env development",
"build:website:production": "npm run -s build:website -- --env production",
"build:website:watch": "run-p build:website -- --watch",
"build:website": "webpack",
"build:middleware:es6": "bnr build:middleware:es6",
"build:middleware:node": "bnr build:middleware:node",
"build:middleware": "run-p build:middleware:*",
"build:production": "bnr build:production",
"build": "run-p build:website build:middleware",
"clean": "rimraf build lib",
"lint": "eslint .",
"prepublish": "npm run -s build:middleware",
"prepublishOnly": "npm run -s build:production"
},
"betterScripts": {
"build:middleware:es6": {
"command": "babel --out-dir lib/middleware.es6 src/middleware",
"env": {
"BABEL_ENV": "es6"
}
},
"build:middleware:node": {
"command": "babel --out-dir lib/middleware.node src/middleware",
"env": {
"BABEL_ENV": "node"
}
},
"build:production": {
"command": "npm run -s build",
"env": {
"NODE_ENV": "production"
}
}
},
"repository": {
"type": "git",
"url": "ssh://git@git.icedream.tech:2222/icedream/vizon-countdown-website.git"
},
"keywords": [
"website",
"vizon",
"rizon",
"draw",
"countdown",
"frontend"
],
"files": [
"lib",
"webpack.config.babel.js",
"README.md"
],
"author": "Carl Kittelberger <icedream@icedream.pw>",
"license": "UNLICENSED",
"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-cli": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.2",
"babel-plugin-dynamic-import-node": "^1.0.2",
"babel-plugin-dynamic-import-webpack": "^1.0.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-react-constant-elements": "^6.23.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1",
"better-npm-run": "^0.1.0",
"case-sensitive-paths-webpack-plugin": "^2.1.1",
"chalk": "^2.1.0",
"css-loader": "^0.28.5",
"debug": "^3.0.1",
"eslint": "^4.5.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-jsx-a11y": "5.x",
"eslint-plugin-react": "^7.3.0",
"eventsource-polyfill": "^0.9.6",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^0.11.2",
"html-webpack-plugin": "^2.30.1",
"moment-timezone": "^0.5.13",
"node-sass": "^4.5.3",
"nodemon": "^1.11.0",
"normalize-scss": "^7.0.0",
"npm-run-all": "^4.1.0",
"postcss-loader": "^2.0.6",
"preact": "^8.2.4",
"preact-compat": "^3.17.0",
"progress-bar-webpack-plugin": "^1.10.0",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-fontawesome": "^1.6.1",
"react-helmet": "^5.1.3",
"react-hot-loader": "^3.0.0-beta.7",
"react-router": "^4.2.0",
"resolve-url-loader": "^2.1.0",
"rimraf": "^2.6.1",
"sass-loader": "^6.0.6",
"slash": "^1.0.0",
"style-loader": "^0.18.2",
"url-loader": "^0.5.9",
"webfontloader": "^1.6.28",
"webpack": "^3.5.5",
"webpack-dev-middleware": "^1.12.0",
"webpack-hot-middleware": "^2.18.2",
"webpack-merge": "^4.1.0"
},
"dependencies": {
"body-parser": "^1.17.2"
},
"peerDependencies": {
"express": "^4.0.0"
}
}

View File

@ -0,0 +1,34 @@
{
"env": {
"es6": {
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"spec": true,
"debug": false,
"modules": false
}]
]
},
"node": {
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"spec": true,
"debug": false,
"modules": "commonjs"
}]
]
}
},
"plugins": [
"babel-plugin-transform-class-properties",
"babel-plugin-dynamic-import-node"
]
}

View File

@ -0,0 +1,97 @@
/*
* Implements the express middleware to serve the frontend.
*
* - Should use webpack-dev-server middleware with hot reloading if running in
* development mode.
* - Should just serve the files from the htdocs folder if running in production
* mode.
*/
import { resolve } from 'path';
import { Router, static as staticMiddleware } from 'express';
const websitePath = resolve(__dirname, '..', 'website');
export default function frontendRouter() {
const app = new Router();
switch ((process.env.NODE_ENV || 'production').toString().toLowerCase()) {
case 'development':
console.log('Serving development build with webpack dev middleware');
console.log('Loading development packages...');
Promise.all([
import('babel-register'),
import('babel-polyfill'),
import('webpack'),
import('webpack-dev-middleware'),
import('webpack-hot-middleware'),
import('util'),
import('body-parser'),
])
.then((loadedModules) => {
const babelRegister = loadedModules[0];
// const babelPolyfill = loadedModules[1];
const webpack = loadedModules[2];
const webpackDevMiddleware = loadedModules[3];
const webpackHotMiddleware = loadedModules[4];
const { inspect } = loadedModules[5];
const bodyParser = loadedModules[6];
console.log('Setting up babel-register...');
babelRegister({
babelrc: true,
extensions: ['.es6', '.es', '.babel.js', '.jsx', '.js'],
});
console.log('Loading webpack configuration...');
return import('../../webpack.config.babel')
.then((generateWebpackConfig) => {
let webpackConfig = generateWebpackConfig;
if (webpackConfig.default) {
// ES6 module imported in CJS, default export.
webpackConfig = webpackConfig.default;
}
if (typeof (webpackConfig) === 'function') {
webpackConfig = webpackConfig([
process.env.NODE_ENV,
{
server: true,
},
].filter(v => !!v));
}
console.log('Webpack configuration loaded.');
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, webpackConfig.devServer));
app.use(webpackHotMiddleware(compiler, {
log: false,
}));
app.use(bodyParser.json());
app.get('/webpackConfig', (req, res) => {
res.setHeader('content-type', 'text/plain');
res.send(inspect(webpackConfig, { depth: 7, colors: false }));
});
});
})
.catch((err) => {
console.error(err);
console.warn('The frontend router will not work due to the error above.');
});
break;
default: // production mode
console.log(`Serving static build from ${websitePath}`);
app.use('/', staticMiddleware(websitePath));
break;
}
return app;
}

View File

@ -0,0 +1,22 @@
{
"presets": [
["babel-preset-env", {
"targets": {
"browsers": ">1%",
"uglify": false
},
"modules": false
}],
"babel-preset-react"
],
"plugins": [
"babel-plugin-transform-react-constant-elements",
"babel-plugin-transform-class-properties",
["babel-plugin-transform-runtime", {
"helpers": false,
"polyfill": false,
"regenerator": true
}],
"babel-plugin-syntax-dynamic-import"
]
}

View File

@ -0,0 +1,21 @@
$debug: false
@if ($debug)
*
background-color: rgba(255, 0, 255, 0.1)
border: red 1px solid
box-sizing: border-box
position: relative
&:after
content: attr(class)
z-index: 99999
box-shadow: 0 0 10px black
position: absolute
left: 0
top: 0
background: white
border: black 1px solid
padding: 4px
font-size: 12px
font-weight: bold
font-family: monospace

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1,5 +1,6 @@
@import ~normalize-scss/sass/normalize @import ~normalize-scss/sass/normalize
@import fonts/colaborate @import fonts/colaborate
@import debug
body body
font-family: sans-serif font-family: sans-serif

View File

@ -13,105 +13,45 @@ import {
import path from 'path'; import path from 'path';
import { isatty } from 'tty'; import { isatty } from 'tty';
import chalk from 'chalk'; import chalk from 'chalk';
import _debug from 'debug';
import slash from 'slash'; import slash from 'slash';
import autoprefixer from 'autoprefixer';
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin'; import ExtractTextPlugin from 'extract-text-webpack-plugin';
import HtmlPlugin from 'html-webpack-plugin'; import HtmlPlugin from 'html-webpack-plugin';
import ProgressBarPlugin from 'progress-bar-webpack-plugin'; import ProgressBarPlugin from 'progress-bar-webpack-plugin';
import Environment from '../../config/webpack/environment';
import injectManifestPlugin from '../../config/webpack/injectManifestPlugin';
const debug = _debug('webpack:config');
debug.generated = _debug('webpack:config:generated');
debug.generated('filename:', __filename);
debug.generated('dirname:', __dirname);
const { const {
CommonsChunkPlugin, CommonsChunkPlugin,
UglifyJsPlugin, UglifyJsPlugin,
ModuleConcatenationPlugin, ModuleConcatenationPlugin,
} = optimize; } = optimize;
const locales = ['en']; const websiteSubfolder = 'website';
const autoprefixerTargets = [
'> 1%',
'last 4 versions',
'Firefox ESR',
'ios >= 8',
];
/**
* Plugin for HtmlPlugin which inlines content for an extracted Webpack manifest
* into the HTML in a <script> tag before other emitted asssets are injected by
* HtmlPlugin itself.
*/
function injectManifestPlugin() {
this.plugin('compilation', (compilation) => {
compilation.plugin('html-webpack-plugin-before-html-processing', (data, cb) => {
Object.keys(compilation.assets).forEach((key) => {
if (!key.startsWith('manifest.')) { return; }
const { children } = compilation.assets[key];
if (children && children[0]) {
// eslint-disable-next-line no-param-reassign
data.html = data.html.replace(/^(\s*)<\/body>/m, `$1<script>${children[0]._value}</script>\n$1</body>`);
// Remove the manifest from HtmlPlugin's assets to prevent a <script>
// tag being created for it.
const manifestIndex = data.assets.js.indexOf(data.assets.publicPath + key);
data.assets.js.splice(manifestIndex, 1);
// eslint-disable-next-line no-param-reassign
delete data.assets.chunks.manifest;
}
});
cb(null, data);
});
});
}
export default (options) => { export default (options) => {
const environment = { const environment = new Environment({
development: true, ExtractTextPlugin, // @HACK
production: false,
server: false,
};
function processEnvironmentString(env) {
switch (env.toLowerCase()) {
case 'development':
environment.development = true;
environment.production = false;
break;
case 'production':
environment.development = false;
environment.production = true;
break;
case 'server':
environment.server = true;
break;
default:
console.warn('Unknown environment:', env);
break;
}
}
if (options) {
switch (true) {
case typeof (options) === 'string': // string
processEnvironmentString(options);
break;
case Array.isArray(options): // array
options.forEach(processEnvironmentString);
break;
default: // object
Object.keys(options).forEach((k) => {
environment[k] = options[k] || environment[k];
}); });
break;
} environment.input(options);
} else if (process.env.NODE_ENV) {
processEnvironmentString(process.env.NODE_ENV); debug.generated(environment);
}
const { const {
development, development,
production, production,
server, server,
locales,
autoprefixerTargets,
} = environment; } = environment;
const baseOutputFilename = development const baseOutputFilename = development
@ -138,66 +78,10 @@ export default (options) => {
publicPath: cssOutputRebasePath, publicPath: cssOutputRebasePath,
}; };
function styleLoaders(...preprocessingLoaders) {
let cssLoaders = [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true,
modules: true,
localIdentName: production
? '[name]__[local]--[hash:base64:5]'
: '[name]__[local]--[hash:base64:5]',
},
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
autoprefixer({
browsers: autoprefixerTargets,
grid: false,
}),
],
sourceMap: true,
},
},
].filter(loader => loader !== false);
if (preprocessingLoaders && preprocessingLoaders.length > 0) {
cssLoaders.push(
{
loader: 'resolve-url-loader',
options: {
fail: true,
silent: false,
},
},
...preprocessingLoaders.map(loader => Object.assign({}, loader, {
options: Object.assign({}, loader.options || {}, {
sourceMap: true,
}),
})),
);
}
if (!server) {
const fallback = cssLoaders.shift();
cssLoaders = ExtractTextPlugin.extract({
fallback,
use: cssLoaders,
});
}
return cssLoaders;
}
const config = { const config = {
name: 'frontend',
target: 'web',
devServer: { devServer: {
// inline: true, // inline: true,
headers: { headers: {
@ -213,6 +97,7 @@ export default (options) => {
ignored: /node_modules/, ignored: /node_modules/,
}, },
}, },
module: { module: {
rules: [ rules: [
{ {
@ -225,8 +110,6 @@ export default (options) => {
// Cache transformations to the filesystem (in default temp dir) // Cache transformations to the filesystem (in default temp dir)
cacheDirectory: true, cacheDirectory: true,
forceEnv: 'browser',
presets: [ presets: [
['babel-preset-env', { ['babel-preset-env', {
targets: { targets: {
@ -234,7 +117,7 @@ export default (options) => {
uglify: false, uglify: false,
}, },
// spec: true, // spec: true,
debug: development, // debug: development,
modules: false, // do not transpile modules, webpack 2+ does that modules: false, // do not transpile modules, webpack 2+ does that
}], }],
'babel-preset-react', 'babel-preset-react',
@ -247,7 +130,7 @@ export default (options) => {
polyfill: false, polyfill: false,
regenerator: true, regenerator: true,
}], }],
'babel-plugin-syntax-dynamic-import', 'babel-plugin-dynamic-import-webpack',
], ],
}, },
}, },
@ -265,11 +148,11 @@ export default (options) => {
})), })),
{ {
test: /\.css$/, test: /\.css$/,
use: styleLoaders(), use: environment.styleLoaders(),
}, },
{ {
test: /\.s[ac]ss$/, test: /\.s[ac]ss$/,
use: styleLoaders( use: environment.styleLoaders(
{ {
loader: 'sass-loader', loader: 'sass-loader',
}, },
@ -278,12 +161,14 @@ export default (options) => {
], ],
strictExportPresence: true, strictExportPresence: true,
}, },
output: { output: {
filename: webpackOutputFilename, filename: webpackOutputFilename,
chunkFilename: webpackChunkFilename, chunkFilename: webpackChunkFilename,
path: path.join(__dirname, 'dist'), path: path.join(__dirname, 'lib', websiteSubfolder),
publicPath: '', publicPath: '',
}, },
plugins: [ plugins: [
// Show progress as a bar during build // Show progress as a bar during build
isatty(1) && new ProgressBarPlugin({ isatty(1) && new ProgressBarPlugin({
@ -366,7 +251,7 @@ export default (options) => {
].filter(() => production), ].filter(() => production),
new HtmlPlugin({ new HtmlPlugin({
template: 'src/index.html', template: path.join(__dirname, 'src', websiteSubfolder, 'index.html'),
filename: 'index.html', filename: 'index.html',
hash: false, hash: false,
inject: true, inject: true,
@ -403,8 +288,8 @@ export default (options) => {
'eventsource-polyfill', 'eventsource-polyfill',
'react-hot-loader/patch', 'react-hot-loader/patch',
'webpack-hot-middleware/client', 'webpack-hot-middleware/client',
].filter(() => server), ].filter(() => server).map(require.resolve),
'./src', path.join(__dirname, 'src', websiteSubfolder),
], ],
}, },
}; };
@ -418,5 +303,7 @@ export default (options) => {
}); });
} }
debug.generated(config);
return config; return config;
}; };

View File

@ -1,4 +0,0 @@
rules:
import/no-extraneous-dependencies:
- error
- devDependencies: true