Merge pull request #5 from LordMathis/develop-server
Server side rendering
This commit is contained in:
commit
3ed99bdfbd
17
.babelrc
17
.babelrc
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"presets":[
|
|
||||||
"es2015", "react"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"development": {
|
|
||||||
"presets": ["es2015", "react", "stage-0"],
|
|
||||||
"plugins": ["transform-runtime"],
|
|
||||||
"presets": ["react-hmre"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"build": {
|
|
||||||
"presets": ["es2015", "react", "stage-0"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Matches multiple files with brace expansion notation
|
||||||
|
# Set default charset
|
||||||
|
[*.{js,py}]
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# 4 space indentation
|
||||||
|
[*.py]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# Tab indentation (no size specified)
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
# Indentation override for all JS under lib directory
|
||||||
|
[lib/**.js]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Matches the exact files either package.json or .travis.yml
|
||||||
|
[{package.json,.travis.yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"standard",
|
||||||
|
"plugin:react/recommended"
|
||||||
|
],
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"plugins": [
|
||||||
|
"babel",
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
build
|
||||||
public
|
public
|
||||||
*.log
|
*.log
|
||||||
content
|
content
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
module.exports = function (api) {
|
||||||
|
const presets = [
|
||||||
|
'@babel/preset-env',
|
||||||
|
'@babel/react'
|
||||||
|
]
|
||||||
|
const plugins = [
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-transform-runtime',
|
||||||
|
'@babel/plugin-transform-template-literals',
|
||||||
|
'@babel/plugin-proposal-class-properties'
|
||||||
|
]
|
||||||
|
|
||||||
|
if (api.env() === 'development') {
|
||||||
|
plugins.push([
|
||||||
|
'css-modules-transform', {
|
||||||
|
'generateScopedName': '[name]__[local]___[hash:base64:5]',
|
||||||
|
'preprocessCss': processSass,
|
||||||
|
'extensions': ['.css', '.scss']
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
presets,
|
||||||
|
plugins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sass = require('node-sass')
|
||||||
|
|
||||||
|
function processSass (data, filename) {
|
||||||
|
var result
|
||||||
|
result = sass.renderSync({
|
||||||
|
data: data,
|
||||||
|
file: filename
|
||||||
|
}).css
|
||||||
|
return result.toString('utf8')
|
||||||
|
}
|
|
@ -4,13 +4,13 @@
|
||||||
"name": "Matúš Námešný",
|
"name": "Matúš Námešný",
|
||||||
"email": "matus@namesny.com",
|
"email": "matus@namesny.com",
|
||||||
"social": {
|
"social": {
|
||||||
"twitter": "https://twitter.com/matus_n",
|
|
||||||
"github": "https://github.com/LordMathis",
|
"github": "https://github.com/LordMathis",
|
||||||
"codepen": "https://codepen.io/LordMathis/",
|
"codepen": "https://codepen.io/LordMathis/",
|
||||||
"linkedin": "https://www.linkedin.com/in/mat%C3%BA%C5%A1-n%C3%A1me%C5%A1n%C3%BD-3903b6128/"
|
"linkedin": "https://www.linkedin.com/in/mat%C3%BA%C5%A1-n%C3%A1me%C5%A1n%C3%BD-3903b6128/"
|
||||||
},
|
},
|
||||||
"contentPath": "./content",
|
"contentPath": "./content",
|
||||||
"renderPath": "./renders",
|
"renderPath": "./renders",
|
||||||
|
"dataPath": "./src/utils/data.json",
|
||||||
"files": [
|
"files": [
|
||||||
"about.md", "resume.md"
|
"about.md", "resume.md"
|
||||||
]
|
]
|
||||||
|
|
62
package.json
62
package.json
|
@ -1,12 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "portfolio",
|
"name": "portfolio",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"description": "portfolio",
|
"description": "portfolio",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "NODE_ENV=production webpack -p --progress --config webpack.prod.config.js",
|
"build": "npm run build-client && npm run build-server",
|
||||||
"start": "NODE_ENV=production node ./src/server.js",
|
"build-client": "NODE_ENV=production webpack --config webpack.client.js -p --progress",
|
||||||
"dev": "NODE_ENV=development babel-node ./src/server.js --presets es2015,stage-2 ./srcserver.js"
|
"build-server": "NODE_ENV=production webpack --config webpack.server.js -p --progress",
|
||||||
|
"start": "NODE_ENV=production node ./build/server.js",
|
||||||
|
"start-dev": "NODE_ENV=development babel-node ./src/server.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"porfolio",
|
"porfolio",
|
||||||
|
@ -16,34 +18,45 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^2.5.0",
|
"async": "^2.5.0",
|
||||||
"axios": "^0.17.0",
|
|
||||||
"express": "^4.13.4",
|
"express": "^4.13.4",
|
||||||
|
"express-static-gzip": "^1.1.3",
|
||||||
"front-matter": "^2.2.0",
|
"front-matter": "^2.2.0",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
"markdown-it": "^8.4.0",
|
"markdown-it": "^8.4.2",
|
||||||
"moment": "^2.19.1",
|
"moment": "^2.24.0",
|
||||||
"node-sass": "^4.9.0",
|
"node-sass": "^4.9.0",
|
||||||
"react": "^15.0.1",
|
"prop-types": "^15.7.2",
|
||||||
"react-dom": "^15.0.1",
|
"react": "^16.7.0",
|
||||||
"react-router-dom": "^4.1.1"
|
"react-dom": "^16.7.0",
|
||||||
|
"react-router-dom": "^4.1.1",
|
||||||
|
"serialize-javascript": "^1.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.26.0",
|
"@babel/core": "^7.2.2",
|
||||||
"babel-core": "^6.7.6",
|
"@babel/node": "^7.2.2",
|
||||||
"babel-jest": "*",
|
"@babel/plugin-proposal-class-properties": "^7.3.3",
|
||||||
"babel-loader": "^7.1.4",
|
"@babel/plugin-proposal-object-rest-spread": "^7.2.0",
|
||||||
"babel-plugin-transform-runtime": "^6.7.5",
|
"@babel/plugin-transform-runtime": "^7.2.0",
|
||||||
"babel-polyfill": "^6.26.0",
|
"@babel/plugin-transform-template-literals": "^7.2.0",
|
||||||
"babel-preset-es2015": "^6.6.0",
|
"@babel/preset-env": "^7.2.3",
|
||||||
"babel-preset-react": "^6.5.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"babel-preset-react-hmre": "^1.1.1",
|
"@babel/runtime": "^7.2.0",
|
||||||
"babel-preset-stage-0": "^6.5.0",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-register": "^6.7.2",
|
"babel-loader": "^8.0.5",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-plugin-css-modules-transform": "^1.6.2",
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
"clean-webpack-plugin": "^0.1.19",
|
||||||
"compression-webpack-plugin": "^1.1.11",
|
"compression-webpack-plugin": "^1.1.11",
|
||||||
|
"create-file-webpack": "^1.0.2",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^0.28.11",
|
||||||
"css-modules-require-hook": "^4.0.6",
|
"css-modules-require-hook": "^4.2.3",
|
||||||
|
"eslint": "^5.14.0",
|
||||||
|
"eslint-config-standard": "^12.0.0",
|
||||||
|
"eslint-plugin-babel": "^5.3.0",
|
||||||
|
"eslint-plugin-import": "^2.16.0",
|
||||||
|
"eslint-plugin-node": "^8.0.1",
|
||||||
|
"eslint-plugin-promise": "^4.0.1",
|
||||||
|
"eslint-plugin-react": "^7.12.4",
|
||||||
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"file-loader": "^1.1.11",
|
"file-loader": "^1.1.11",
|
||||||
"mini-css-extract-plugin": "^0.4.0",
|
"mini-css-extract-plugin": "^0.4.0",
|
||||||
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
||||||
|
@ -55,6 +68,7 @@
|
||||||
"webpack-cli": "^2.1.2",
|
"webpack-cli": "^2.1.2",
|
||||||
"webpack-dev-middleware": "^3.1.3",
|
"webpack-dev-middleware": "^3.1.3",
|
||||||
"webpack-hot-middleware": "^2.18.0",
|
"webpack-hot-middleware": "^2.18.0",
|
||||||
"webpack-manifest-plugin": "^2.0.3"
|
"webpack-manifest-plugin": "^2.0.4",
|
||||||
|
"webpack-node-externals": "^1.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
module.exports = {};
|
module.exports = {}
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react'
|
||||||
import {render} from 'react-dom';
|
import { hydrate } from 'react-dom'
|
||||||
import {BrowserRouter as Router} from 'react-router-dom';
|
import { BrowserRouter as Router } from 'react-router-dom'
|
||||||
import {App} from './components';
|
import { App } from './components'
|
||||||
|
|
||||||
const AppClient = () => (
|
hydrate(
|
||||||
<Router>
|
<Router>
|
||||||
<App />
|
<App data={window.__INITIAL_DATA__}/>
|
||||||
</Router>
|
</Router>,
|
||||||
)
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
render(
|
|
||||||
<AppClient />,
|
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
)
|
||||||
};
|
|
||||||
|
|
|
@ -1,24 +1,32 @@
|
||||||
import React, {Component} from 'react';
|
import PropTypes from 'prop-types'
|
||||||
import {Spinner, Header} from '.';
|
import React, { Component } from 'react'
|
||||||
import '../static/stylesheets/globals.scss';
|
import { Spinner, Header } from '.'
|
||||||
import styles from './About.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import contentStyle from '../static/stylesheets/content.scss';
|
import contentStyle from '../static/stylesheets/content.scss'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
|
||||||
export default class About extends Component {
|
export default class About extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
about: PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const md = MarkdownIt()
|
||||||
|
const result = md.render(this.props.about)
|
||||||
|
|
||||||
if (this.props.isLoading) {
|
if (this.props.isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className={contentStyle.contentWrapper} id="about">
|
<div className={contentStyle.contentWrapper} id="about">
|
||||||
<Spinner/>
|
<Spinner/>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={contentStyle.contentWrapper} id="about">
|
<div className={contentStyle.contentWrapper} id="about">
|
||||||
<Header header={"About Me"} />
|
<Header header={'About Me'} />
|
||||||
<div className={contentStyle.content} dangerouslySetInnerHTML={{__html: this.props.about.body}}>
|
<div className={contentStyle.content} dangerouslySetInnerHTML={{ __html: result }}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,26 @@
|
||||||
import React from 'react';
|
import { NotFoundWrapper } from '.'
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import React, { Component } from 'react'
|
||||||
import { Home, NotFoundWrapper } from '.';
|
import routes from '../utils/routes'
|
||||||
import { MainContainer, PostContainer } from '../containers';
|
import { Route, Switch } from 'react-router-dom'
|
||||||
|
|
||||||
export const App = () => (
|
export default class App extends Component {
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={MainContainer} />
|
{routes.map(({ path, exact, component: C, ...rest }) => (
|
||||||
<Route path="/post/:postname" component={PostContainer} />
|
<Route
|
||||||
<Route component={NotFoundWrapper} />
|
key={path}
|
||||||
|
path={path}
|
||||||
|
exact={exact}
|
||||||
|
render={(props) => (
|
||||||
|
<C {...props} {...rest} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Route render={(props) => <NotFoundWrapper {...props} />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
|
}
|
||||||
export default App;
|
}
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import React, {Component} from 'react';
|
import PropTypes from 'prop-types'
|
||||||
import {Spinner, Header} from '.';
|
import React, { Component } from 'react'
|
||||||
import '../static/stylesheets/globals.scss';
|
import { Spinner, Header } from '.'
|
||||||
import styles from './Blog.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import contentStyle from '../static/stylesheets/content.scss';
|
import styles from './Blog.scss'
|
||||||
|
import contentStyle from '../static/stylesheets/content.scss'
|
||||||
|
|
||||||
export default class Blog extends Component {
|
export default class Blog extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
posts: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (this.props.isLoading) {
|
if (this.props.isLoading) {
|
||||||
|
@ -12,10 +17,13 @@ export default class Blog extends Component {
|
||||||
<div className={contentStyle.contentWrapper} id="blog">
|
<div className={contentStyle.contentWrapper} id="blog">
|
||||||
<Spinner/>
|
<Spinner/>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let posts = this.props.posts.map((post) =>
|
let posts = this.props.posts.sort((a, b) => {
|
||||||
|
return new Date(b.published) - new Date(a.published)
|
||||||
|
})
|
||||||
|
let postsHTML = posts.map((post) =>
|
||||||
<tr className={styles.postListItem} key={post.title}>
|
<tr className={styles.postListItem} key={post.title}>
|
||||||
<td>
|
<td>
|
||||||
<span className={styles.postDate}>{post.published}</span>
|
<span className={styles.postDate}>{post.published}</span>
|
||||||
|
@ -28,17 +36,17 @@ export default class Blog extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={contentStyle.contentWrapper} id="blog">
|
<div className={contentStyle.contentWrapper} id="blog">
|
||||||
<Header header={"Blog"} />
|
<Header header={'Blog'} />
|
||||||
|
|
||||||
<div className={contentStyle.content}>
|
<div className={contentStyle.content}>
|
||||||
<table>
|
<table>
|
||||||
<tbody className={styles.postsWrapper}>
|
<tbody className={styles.postsWrapper}>
|
||||||
{posts}
|
{postsHTML}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react'
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom'
|
||||||
import config from '../../config.json';
|
import config from '../../config.json'
|
||||||
import '../static/stylesheets/globals.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import styles from './Home.scss';
|
import styles from './Home.scss'
|
||||||
|
|
||||||
export default class Home extends Component {
|
export default class Home extends Component {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let key = 0;
|
let key = 0
|
||||||
const objKeys = Object.keys(config.social);
|
const objKeys = Object.keys(config.social)
|
||||||
|
|
||||||
const socialLinks = objKeys.map((val) => {
|
const socialLinks = objKeys.map((val) => {
|
||||||
const link = (
|
const link = (
|
||||||
|
@ -16,17 +15,17 @@ export default class Home extends Component {
|
||||||
<i className={`fa fa-${val} fa-3x`} aria-hidden="true" />
|
<i className={`fa fa-${val} fa-3x`} aria-hidden="true" />
|
||||||
<span className="sr-only">{val}</span>
|
<span className="sr-only">{val}</span>
|
||||||
</a>
|
</a>
|
||||||
);
|
)
|
||||||
key += 1;
|
key += 1
|
||||||
return link;
|
return link
|
||||||
});
|
})
|
||||||
|
|
||||||
socialLinks.push(
|
socialLinks.push(
|
||||||
<a key={key} href={`mailto:${config.email}`}>
|
<a key={key} href={`mailto:${config.email}`}>
|
||||||
<i className="fa fa-envelope-o fa-3x" aria-hidden="true" />
|
<i className="fa fa-envelope-o fa-3x" aria-hidden="true" />
|
||||||
<span className="sr-only">e-mail</span>
|
<span className="sr-only">e-mail</span>
|
||||||
</a>,
|
</a>
|
||||||
);
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={styles.coverPage} className={styles.coverPageFull}>
|
<div id={styles.coverPage} className={styles.coverPageFull}>
|
||||||
|
@ -53,6 +52,6 @@ export default class Home extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react'
|
||||||
import config from '../../config.json';
|
import config from '../../config.json'
|
||||||
import '../static/stylesheets/globals.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import styles from './Navbar.scss';
|
import styles from './Navbar.scss'
|
||||||
|
|
||||||
export default class Navbar extends Component {
|
export default class Navbar extends Component {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
let key = 0
|
||||||
let key = 0;
|
const objKeys = Object.keys(config.social)
|
||||||
const objKeys = Object.keys(config.social);
|
|
||||||
|
|
||||||
const socialLinks = objKeys.map((val) => {
|
const socialLinks = objKeys.map((val) => {
|
||||||
const link = (
|
const link = (
|
||||||
|
@ -16,17 +14,18 @@ export default class Navbar extends Component {
|
||||||
<i className={`fa fa-${val}`} aria-hidden="true" />
|
<i className={`fa fa-${val}`} aria-hidden="true" />
|
||||||
<span className="sr-only">{val}</span>
|
<span className="sr-only">{val}</span>
|
||||||
</a>
|
</a>
|
||||||
);
|
)
|
||||||
key += 1;
|
|
||||||
return link;
|
key += 1
|
||||||
});
|
return link
|
||||||
|
})
|
||||||
|
|
||||||
socialLinks.push(
|
socialLinks.push(
|
||||||
<a key={key} href={`mailto:${config.email}`}>
|
<a key={key} href={`mailto:${config.email}`}>
|
||||||
<i className="fa fa-envelope-o" aria-hidden="true" />
|
<i className="fa fa-envelope-o" aria-hidden="true" />
|
||||||
<span className="sr-only">e-mail</span>
|
<span className="sr-only">e-mail</span>
|
||||||
</a>,
|
</a>
|
||||||
);
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.navbar}>
|
<div className={styles.navbar}>
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import React from 'react';
|
import React from 'react'
|
||||||
import {Navbar, Header} from '.';
|
import { Navbar, Header } from '.'
|
||||||
import '../static/stylesheets/globals.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import contentStyle from '../static/stylesheets/content.scss';
|
import contentStyle from '../static/stylesheets/content.scss'
|
||||||
|
|
||||||
export const NotFoundPage = (props) => {
|
export const NotFoundPage = (props) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div className={contentStyle.contentWrapper}>
|
<div className={contentStyle.contentWrapper}>
|
||||||
<Header header={"Uhm... WHAT?"} />
|
<Header header={'Uhm... WHAT?'} />
|
||||||
<div className={contentStyle.content}>
|
<div className={contentStyle.content}>
|
||||||
<p>Looks like you're lost</p>
|
<p>Looks like you're lost</p>
|
||||||
<p>404 Page not found</p>
|
<p>404 Page not found</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default NotFoundPage;
|
export default NotFoundPage
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react'
|
||||||
import {Wrapper, NotFoundPage} from '.';
|
import { Wrapper, NotFoundPage } from '.'
|
||||||
import '../static/stylesheets/globals.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import styles from './Wrapper.scss';
|
|
||||||
|
|
||||||
export default class NotFoundWrapper extends Component {
|
export default class NotFoundWrapper extends Component {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
|
|
|
@ -1,30 +1,44 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react'
|
||||||
import {Spinner, Header, Navbar} from '.';
|
import PropTypes from 'prop-types'
|
||||||
import '../static/stylesheets/globals.scss';
|
import { Spinner, Header, Navbar } from '.'
|
||||||
import contentStyle from '../static/stylesheets/content.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import styles from './Post.scss';
|
import contentStyle from '../static/stylesheets/content.scss'
|
||||||
|
import styles from './Post.scss'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import fm from 'front-matter'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
export default class Post extends Component {
|
export default class Post extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
post: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const md = MarkdownIt()
|
||||||
|
const content = fm(this.props.post)
|
||||||
|
const title = content.attributes.title
|
||||||
|
const date = moment(content.attributes.date, 'YYYY-MM-DD')
|
||||||
|
const body = md.render(content.body)
|
||||||
|
|
||||||
if (this.props.isLoading) {
|
if (this.props.isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className={contentStyle.contentWrapper}>
|
<div className={contentStyle.contentWrapper}>
|
||||||
<Spinner/>
|
<Spinner/>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div className={contentStyle.contentWrapper}>
|
<div className={contentStyle.contentWrapper}>
|
||||||
<Header header={this.props.post.title} />
|
<Header header={title} />
|
||||||
<div className={contentStyle.content}>
|
<div className={contentStyle.content}>
|
||||||
<div className={styles.postDate}>
|
<div className={styles.postDate}>
|
||||||
<h3>{this.props.post.published}</h3>
|
<h3>{date.format('MMMM D, YYYY')}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.postContent} dangerouslySetInnerHTML={{__html: this.props.post.body}}>
|
<div className={styles.postContent} dangerouslySetInnerHTML={{ __html: body }}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react'
|
||||||
import '../static/stylesheets/globals.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import styles from './Spinner.scss';
|
import styles from './Spinner.scss'
|
||||||
|
|
||||||
export default class Spinner extends Component {
|
export default class Spinner extends Component {
|
||||||
render () {
|
render () {
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import React, {Component} from 'react';
|
import PropTypes from 'prop-types'
|
||||||
import {Spinner, Header} from '.';
|
import React, { Component } from 'react'
|
||||||
import '../static/stylesheets/globals.scss';
|
import '../static/stylesheets/globals.scss'
|
||||||
import styles from './Wrapper.scss';
|
import styles from './Wrapper.scss'
|
||||||
|
|
||||||
export default class Wrapper extends Component {
|
export default class Wrapper extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node
|
||||||
|
]).isRequired
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
export { default as Home } from './Home';
|
export { default as Home } from './Home'
|
||||||
export { default as Blog } from './Blog';
|
export { default as Blog } from './Blog'
|
||||||
export { default as About } from './About';
|
export { default as About } from './About'
|
||||||
export { default as Post } from './Post';
|
export { default as Post } from './Post'
|
||||||
export { default as NotFoundPage } from './NotFoundPage';
|
export { default as NotFoundPage } from './NotFoundPage'
|
||||||
export { default as NotFoundWrapper } from './NotFoundWrapper';
|
export { default as NotFoundWrapper } from './NotFoundWrapper'
|
||||||
export { default as Spinner } from './Spinner';
|
export { default as Spinner } from './Spinner'
|
||||||
export { default as Header } from './Header';
|
export { default as Header } from './Header'
|
||||||
export { default as Wrapper } from './Wrapper';
|
export { default as Wrapper } from './Wrapper'
|
||||||
export { default as Navbar } from './Navbar';
|
export { default as Navbar } from './Navbar'
|
||||||
export { default as App } from './App';
|
export { default as App } from './App'
|
||||||
|
|
|
@ -1,34 +1,32 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react'
|
||||||
import axios from 'axios';
|
import PropTypes from 'prop-types'
|
||||||
import {About, Blog, Home, Wrapper} from '../components';
|
import { About, Blog, Home, Wrapper } from '../components'
|
||||||
|
|
||||||
export default class BlogContainer extends Component {
|
export default class MainContainer extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
staticContext: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor (props) {
|
||||||
super();
|
super(props)
|
||||||
|
|
||||||
|
let data
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
if (__isBrowser__) {
|
||||||
|
data = window.__INITIAL_DATA__
|
||||||
|
delete window.__INITIAL_DATA__
|
||||||
|
} else {
|
||||||
|
data = props.staticContext.data
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoadingBlog: true,
|
isLoadingBlog: !data.posts,
|
||||||
isLoadingAbout: true,
|
isLoadingAbout: !data.other.about,
|
||||||
|
about: data.other.about,
|
||||||
|
posts: data.posts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
axios.get('/api/about').then((res) => {
|
|
||||||
this.setState({
|
|
||||||
isLoadingAbout: false,
|
|
||||||
about: res.data,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
axios.get('/api/blog').then((res) => {
|
|
||||||
this.setState({
|
|
||||||
isLoadingBlog: false,
|
|
||||||
posts: res.data,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,38 +1,32 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react'
|
||||||
import axios from 'axios';
|
import PropTypes from 'prop-types'
|
||||||
import {Post, Wrapper, NotFoundPage} from '../components';
|
import { Post, Wrapper, NotFoundPage } from '../components'
|
||||||
|
|
||||||
export default class PostContainer extends Component {
|
export default class PostContainer extends Component {
|
||||||
constructor() {
|
static propTypes = {
|
||||||
super();
|
staticContext: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
let post
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
if (__isBrowser__) {
|
||||||
|
post = window.__INITIAL_DATA__
|
||||||
|
delete window.__INITIAL_DATA__
|
||||||
|
} else {
|
||||||
|
post = props.staticContext.data
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoading: true,
|
isLoading: !post,
|
||||||
error: false,
|
error: false,
|
||||||
};
|
post: post
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const url = '/api/post/' + this.props.match.params.postname;
|
|
||||||
|
|
||||||
axios.get(url).then((res) => {
|
|
||||||
if (res.data.error) {
|
|
||||||
this.setState({
|
|
||||||
error: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.setState({
|
|
||||||
error: false,
|
|
||||||
isLoading: false,
|
|
||||||
post: res.data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
return (
|
return (
|
||||||
<NotFoundPage />
|
<NotFoundPage />
|
||||||
|
@ -44,6 +38,6 @@ export default class PostContainer extends Component {
|
||||||
<Post isLoading={this.state.isLoading}
|
<Post isLoading={this.state.isLoading}
|
||||||
post={this.state.post} />
|
post={this.state.post} />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export { default as MainContainer } from './MainContainer';
|
export { default as MainContainer } from './MainContainer'
|
||||||
export { default as PostContainer } from './PostContainer';
|
export { default as PostContainer } from './PostContainer'
|
||||||
|
|
|
@ -1,59 +1,26 @@
|
||||||
require('babel-register');
|
import express from 'express'
|
||||||
var path = require('path');
|
import expressStaticGzip from 'express-static-gzip'
|
||||||
|
import { serverRender } from './utils/serverRender'
|
||||||
|
import { Scanner } from './utils/scanner'
|
||||||
|
|
||||||
var app = new (require('express'))();
|
const port = process.env.PORT || 3000
|
||||||
var port = process.env.PORT || 3000;
|
const app = express()
|
||||||
|
|
||||||
const sass = require('node-sass');
|
const scanner = new Scanner()
|
||||||
|
scanner.scan()
|
||||||
|
|
||||||
require('css-modules-require-hook')({
|
app.use('/static', expressStaticGzip('public/static'))
|
||||||
generateScopedName: '[name]__[local]___[hash:base64:5]',
|
|
||||||
extensions: ['.scss', '.css'],
|
|
||||||
preprocessCss: (data, filename) => sass.renderSync({
|
|
||||||
data,
|
|
||||||
file: filename,
|
|
||||||
}).css
|
|
||||||
});
|
|
||||||
|
|
||||||
var fs = require('fs');
|
app.get('/favicon.ico', (req, res) => {
|
||||||
var filename = './src/utils/data.json';
|
res.status(404).send('Not Found !!!')
|
||||||
var dataStub = {"posts": [], "other": []};
|
})
|
||||||
fs.writeFileSync(filename, JSON.stringify(dataStub));
|
|
||||||
|
|
||||||
|
app.get('*', serverRender)
|
||||||
// initalize webpack dev middleware if in development context
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
var webpack = require('webpack')
|
|
||||||
var config = require('../webpack.config')
|
|
||||||
|
|
||||||
var devMiddleware = require('webpack-dev-middleware')
|
|
||||||
var hotDevMiddleware = require('webpack-hot-middleware')
|
|
||||||
var compiler = webpack(config)
|
|
||||||
var devMiddlewareConfig = {
|
|
||||||
noInfo: true,
|
|
||||||
stats: {colors: true},
|
|
||||||
publicPath: config.output.publicPath
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(devMiddleware(compiler, devMiddlewareConfig))
|
|
||||||
app.use(hotDevMiddleware(compiler))
|
|
||||||
}
|
|
||||||
|
|
||||||
require('./utils/scanner')();
|
|
||||||
|
|
||||||
var api = require('./utils/api');
|
|
||||||
app.use("/api", api);
|
|
||||||
|
|
||||||
var staticFiles = require('./utils/staticFiles');
|
|
||||||
app.use("/static", staticFiles);
|
|
||||||
|
|
||||||
var serverRender = require('./utils/serverRender');
|
|
||||||
app.get("*", serverRender);
|
|
||||||
|
|
||||||
app.listen(port, function (error) {
|
app.listen(port, function (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error)
|
||||||
} else {
|
} else {
|
||||||
console.info("[Server] Listening on port %s", port);
|
console.info('[Server] Listening on port %s', port)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,56 +1,29 @@
|
||||||
const data = require('./data.json');
|
import fs from 'fs'
|
||||||
const api = require('express').Router();
|
import jsonfile from 'jsonfile'
|
||||||
const fs = require('fs');
|
import path from 'path'
|
||||||
const path = require('path');
|
import config from '../../config.json'
|
||||||
const config = require('../../config.json');
|
|
||||||
|
|
||||||
api.get('/blog', (req, res) => {
|
export function getData (reqPath = '') {
|
||||||
res.set('Cache-Control', 'no-cache');
|
if (reqPath === '') {
|
||||||
data.posts.sort((a,b) => {
|
return readData(config.dataPath)
|
||||||
return new Date(b.published) - new Date(a.published);
|
} else {
|
||||||
|
const fileName = path.join(process.cwd(), config.contentPath, reqPath + '.md')
|
||||||
|
return readFile(fileName, 'utf8')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function readFile (fileName, options) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
fs.readFile(fileName, options, (err, data) => {
|
||||||
|
err ? reject(err) : resolve(data)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
res.json(data.posts);
|
|
||||||
});
|
|
||||||
|
|
||||||
api.get('/about', (req, res) => {
|
|
||||||
const renderPath = path.join(process.cwd(), '/renders', 'about.html');
|
|
||||||
res.set('Cache-Control', 'max-age=86400');
|
|
||||||
fs.readFile(renderPath, 'utf8', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
res.json({
|
|
||||||
error: 404
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.json({
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
api.get('/post/:postname', (req, res) => {
|
function readData (dataPath) {
|
||||||
res.set('Cache-Control', 'no-cache');
|
return new Promise(function (resolve, reject) {
|
||||||
const postname = req.params.postname;
|
jsonfile.readFile(dataPath, (err, data) => {
|
||||||
const post = data.posts.find((el) => {
|
err ? reject(err) : resolve(data)
|
||||||
return el.filename === postname
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
const renderPath = path.join(process.cwd(), '/renders', postname + '.html');
|
|
||||||
fs.readFile(renderPath, 'utf8', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
res.json({
|
|
||||||
error: 404
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.json({
|
|
||||||
published: post.published,
|
|
||||||
link: post.link,
|
|
||||||
title: post.title,
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = api;
|
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
const MarkdownIt = require('markdown-it');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const moment = require('moment');
|
|
||||||
const jsonfile = require('jsonfile');
|
|
||||||
const async = require('async');
|
|
||||||
const fm = require('front-matter');
|
|
||||||
const config = require('../../config.json');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders file using MarkdownIt
|
|
||||||
*/
|
|
||||||
function render(file) {
|
|
||||||
const md = new MarkdownIt({html: true});
|
|
||||||
return md.render(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts file metadata such as parent directory
|
|
||||||
*/
|
|
||||||
function fileMetadata(filepath) {
|
|
||||||
const paths = filepath.split('/');
|
|
||||||
const basename = path.basename(filepath);
|
|
||||||
|
|
||||||
const metadata = {
|
|
||||||
basename,
|
|
||||||
filename: basename.substr(0, basename.lastIndexOf('.')),
|
|
||||||
parrent: paths[paths.length - 2],
|
|
||||||
dirname: path.dirname(filepath),
|
|
||||||
};
|
|
||||||
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compiles file that is a blog post
|
|
||||||
*/
|
|
||||||
function compilePost(filepath, data, fileData, callback) {
|
|
||||||
const frontMatter = fm(fileData);
|
|
||||||
const rendered = render(frontMatter.body);
|
|
||||||
const metadata = fileMetadata(filepath);
|
|
||||||
|
|
||||||
if (frontMatter.attributes.draft) {
|
|
||||||
callback(null, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let published;
|
|
||||||
if (frontMatter.attributes.date) {
|
|
||||||
published = moment(frontMatter.attributes.date);
|
|
||||||
} else {
|
|
||||||
published = moment();
|
|
||||||
}
|
|
||||||
|
|
||||||
const post = {
|
|
||||||
published: published.format('MMMM DD, YYYY'),
|
|
||||||
filename: metadata.filename,
|
|
||||||
title: frontMatter.attributes.title,
|
|
||||||
link: '/post/' + metadata.filename
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderedpath = path.join(process.cwd(), config.renderPath, `${metadata.filename}.html`);
|
|
||||||
|
|
||||||
fs.writeFile(renderedpath, rendered, (err) => {
|
|
||||||
if (err) callback(err);
|
|
||||||
else callback(null, post);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compiles other types of files such as resumes, about me and so on.
|
|
||||||
*/
|
|
||||||
function compileOther(filepath, data, fileData, callback) {
|
|
||||||
|
|
||||||
const frontMatter = fm(fileData);
|
|
||||||
const rendered = render(frontMatter.body);
|
|
||||||
const metadata = fileMetadata(filepath);
|
|
||||||
|
|
||||||
const post = {
|
|
||||||
filename: metadata.filename
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderedpath = path.join(process.cwd(), config.renderPath, `${metadata.filename}.html`);
|
|
||||||
|
|
||||||
fs.writeFile(renderedpath, rendered, (err) => {
|
|
||||||
if (err) callback(err);
|
|
||||||
else callback(null, post);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function Compiler(data) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
Compiler.prototype.addFile = function(filepath, isPost, callback) {
|
|
||||||
|
|
||||||
if (isPost) {
|
|
||||||
async.waterfall([
|
|
||||||
fs.readFile.bind(fs, filepath, 'utf8'),
|
|
||||||
compilePost.bind(compilePost, filepath, this.data),
|
|
||||||
], (err, result) => {
|
|
||||||
if (err) throw err;
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
callback();
|
|
||||||
} else {
|
|
||||||
this.data.posts.push(result);
|
|
||||||
console.log("[Compiler] File %s compiled", filepath);
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
async.waterfall([
|
|
||||||
fs.readFile.bind(fs, filepath, 'utf8'),
|
|
||||||
compileOther.bind(compileOther, filepath, this.data),
|
|
||||||
], (err, result) => {
|
|
||||||
if (err) throw err;
|
|
||||||
|
|
||||||
this.data.other.push(result);
|
|
||||||
console.log("[Compiler] File %s compiled", filepath);
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes updated data to the data file
|
|
||||||
*/
|
|
||||||
Compiler.prototype.writeData = function(callback) {
|
|
||||||
const dataPath = path.join(process.cwd(), 'src/utils/data.json');
|
|
||||||
jsonfile.writeFile(dataPath, this.data, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Compiler;
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { MainContainer, PostContainer } from '../containers'
|
||||||
|
import { getData } from './api'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
exact: true,
|
||||||
|
component: MainContainer,
|
||||||
|
getData: (path = '') => getData(
|
||||||
|
path.split('/').pop()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/post/:postname',
|
||||||
|
component: PostContainer,
|
||||||
|
getData: (path = '') => getData(
|
||||||
|
path.split('/').pop()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default routes
|
|
@ -1,62 +1,127 @@
|
||||||
const fs = require('fs');
|
import fs from 'fs'
|
||||||
const path = require('path');
|
import path from 'path'
|
||||||
const async = require('async');
|
import config from '../../config.json'
|
||||||
const Compiler = require('./compiler');
|
import fm from 'front-matter'
|
||||||
const config = require('../../config.json');
|
import moment from 'moment'
|
||||||
const data = require('./data.json');
|
import jsonfile from 'jsonfile'
|
||||||
|
|
||||||
module.exports = function() {
|
export class Scanner {
|
||||||
|
constructor () {
|
||||||
var compiler = new Compiler(data);
|
this.data = {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the directory and returns it's content
|
|
||||||
*/
|
|
||||||
function readdir(callback) {
|
|
||||||
fs.readdir(config.contentPath, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
readdir (dirname) {
|
||||||
* Calls compile on each file in the directory
|
return new Promise((resolve, reject) => {
|
||||||
*/
|
fs.readdir(dirname, function (err, filenames) {
|
||||||
function compile(files, callback) {
|
if (err) {
|
||||||
console.log("[Scanner] Discovered files: " + files);
|
reject(err)
|
||||||
async.each(files, compileFile, (err) => {
|
|
||||||
if (err) throw err;
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function which calls compile in the Compiler module
|
|
||||||
*/
|
|
||||||
function compileFile(file, callback) {
|
|
||||||
const filePath = path.join(process.cwd(), config.contentPath, file);
|
|
||||||
|
|
||||||
// config.files contains list of file names which are not considered blog posts
|
|
||||||
if (config.files.indexOf(file) == -1) {
|
|
||||||
compiler.addFile(filePath, true, callback);
|
|
||||||
} else {
|
} else {
|
||||||
compiler.addFile(filePath, false, callback);
|
resolve(filenames)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
readfile (filename) {
|
||||||
|
const filePath = path.join(process.cwd(), config.contentPath, filename)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
resolve([filename, data])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
processFile (file, data) {
|
||||||
|
const filePath = path.join(process.cwd(), config.contentPath, file)
|
||||||
|
const metadata = this.fileMetadata(filePath)
|
||||||
|
|
||||||
|
if (config.files.indexOf(file) === -1) {
|
||||||
|
const frontMatter = fm(data)
|
||||||
|
|
||||||
|
if (frontMatter.attributes.draft) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let published
|
||||||
|
if (frontMatter.attributes.date) {
|
||||||
|
published = moment(frontMatter.attributes.date)
|
||||||
|
} else {
|
||||||
|
published = moment()
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = {
|
||||||
|
published: published.format('MMMM DD, YYYY'),
|
||||||
|
filename: metadata.filename,
|
||||||
|
title: frontMatter.attributes.title,
|
||||||
|
link: '/post/' + metadata.filename
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.posts.push(post)
|
||||||
|
} else {
|
||||||
|
this.data.other[metadata.filename] = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
init () {
|
||||||
* Writes updated data into the data file
|
return new Promise((resolve, reject) => {
|
||||||
*/
|
jsonfile.readFile(config.dataPath, (err, data) => {
|
||||||
function writeData(callback) {
|
if (err) {
|
||||||
compiler.writeData(callback);
|
reject(err)
|
||||||
|
} else {
|
||||||
|
this.data = data
|
||||||
|
resolve(data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
writeData (callback) {
|
||||||
* Main function. Scans the directory for files and compiles them into html
|
return new Promise((resolve, reject) => {
|
||||||
* using the Compiler module
|
jsonfile.writeFile(config.dataPath, this.data, (err, data) => {
|
||||||
*/
|
if (err) {
|
||||||
async.waterfall([
|
reject(err)
|
||||||
readdir,
|
} else {
|
||||||
compile,
|
resolve(this.data)
|
||||||
writeData
|
}
|
||||||
], (err) => {
|
})
|
||||||
if(err) throw err;
|
})
|
||||||
});
|
}
|
||||||
|
|
||||||
|
fileMetadata (filepath) {
|
||||||
|
const paths = filepath.split('/')
|
||||||
|
const basename = path.basename(filepath)
|
||||||
|
|
||||||
|
const metadata = {
|
||||||
|
basename,
|
||||||
|
filename: basename.substr(0, basename.lastIndexOf('.')),
|
||||||
|
parrent: paths[paths.length - 2],
|
||||||
|
dirname: path.dirname(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
scan () {
|
||||||
|
this.init()
|
||||||
|
.then(
|
||||||
|
() => this.readdir(config.contentPath)
|
||||||
|
).then(
|
||||||
|
(files) => { return Promise.all(files.map(this.readfile)) }
|
||||||
|
).then(
|
||||||
|
(files) => {
|
||||||
|
files.forEach(
|
||||||
|
(item) => { this.processFile(item[0], item[1]) }
|
||||||
|
)
|
||||||
|
return this.writeData()
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
console.log('[Scanner] Scan complete')
|
||||||
|
).catch(
|
||||||
|
(err) => console.log(err)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,48 @@
|
||||||
//import 'babel-polyfill'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { renderToString } from 'react-dom/server'
|
import { renderToString } from 'react-dom/server'
|
||||||
import { StaticRouter as Router } from 'react-router-dom'
|
import { StaticRouter as Router, matchPath } from 'react-router-dom'
|
||||||
import { App } from '../components/App'
|
import { App } from '../components'
|
||||||
|
import routes from './routes'
|
||||||
|
import serialize from 'serialize-javascript'
|
||||||
import manifest from '../../public/static/manifest.json'
|
import manifest from '../../public/static/manifest.json'
|
||||||
|
|
||||||
function serverRender(req, res) {
|
export function serverRender (req, res, next) {
|
||||||
let markup = '';
|
const activeRoute = routes.find((route) => matchPath(req.url, route)) || {}
|
||||||
let status = 200;
|
|
||||||
|
|
||||||
const context = {}
|
const promise = activeRoute.getData
|
||||||
markup = renderToString(
|
? activeRoute.getData(req.path)
|
||||||
<Router location={req.url} context={context}>
|
: Promise.resolve()
|
||||||
|
|
||||||
|
promise.then((data) => {
|
||||||
|
const markup = renderToString(
|
||||||
|
<Router location={req.url} context={{ data }}>
|
||||||
<App/>
|
<App/>
|
||||||
</Router>,
|
</Router>
|
||||||
);
|
)
|
||||||
|
|
||||||
return res.status(status).send(renderFullPage(markup, manifest));
|
res.status(200).send(renderFullPage(markup, data))
|
||||||
|
}).catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFullPage(html, manifest) {
|
function renderFullPage (html, data) {
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Matúš Námešný</title>
|
<title>Matúš Námešný</title>
|
||||||
<!-- Google Fonts -->
|
<!-- Google Fonts -->
|
||||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Open+Sans+Condensed:700&subset=latin-ext" rel="stylesheet" rel="preload">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Open+Sans+Condensed:700&subset=latin-ext" rel="stylesheet" rel="preload">
|
||||||
<!-- Font Awesome -->
|
<!-- Font Awesome -->
|
||||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" rel="preload" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
|
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" rel="preload" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
|
||||||
<!-- Stylesheet -->
|
<!-- Stylesheet -->
|
||||||
${process.env.NODE_ENV === 'production' ? `<link href=${manifest['bundle.css']} rel="stylesheet">` : ''}
|
<link href=${manifest['bundle.css']} rel="stylesheet" rel="preload">
|
||||||
|
<!-- Initial Data -->
|
||||||
|
<script>window.__INITIAL_DATA__ = ${serialize(data)}</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root">${process.env.NODE_ENV === 'production' ? html : `<div>${html}</div>`}</div>
|
<div id="root">${html}</div>
|
||||||
<script src="${manifest['bundle.js']}" async></script>
|
<script src=${manifest['bundle.js']} async></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = serverRender;
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
const staticFiles = require('express').Router();
|
|
||||||
const path = require('path');
|
|
||||||
import manifest from '../../public/static/manifest.json'
|
|
||||||
|
|
||||||
staticFiles.get('/*.js', (req, res) => {
|
|
||||||
const filename = req.url.split("/").pop();
|
|
||||||
if (req.acceptsEncodings('gzip')) {
|
|
||||||
res.set({
|
|
||||||
'Content-Encoding': 'gzip',
|
|
||||||
'Content-Type': 'text/javascript',
|
|
||||||
'Cache-Control': 'max-age=31536000'
|
|
||||||
});
|
|
||||||
res.sendFile(path.join(process.cwd(), '/public/', manifest[`${filename}.gz`]));
|
|
||||||
} else {
|
|
||||||
res.set('Cache-Control', 'max-age=31536000');
|
|
||||||
res.sendFile(path.join(process.cwd(), '/public/', manifest['bundle.js']));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
staticFiles.get('/*.css', (req, res) => {
|
|
||||||
const filename = req.url.split("/").pop();
|
|
||||||
if (req.acceptsEncodings('gzip')) {
|
|
||||||
res.set({
|
|
||||||
'Content-Encoding': 'gzip',
|
|
||||||
'Content-Type': 'text/css',
|
|
||||||
'Cache-Control': 'max-age=31536000'
|
|
||||||
});
|
|
||||||
res.sendFile(path.join(process.cwd(), '/public/', manifest[`${filename}.gz`]));
|
|
||||||
} else {
|
|
||||||
res.set('Cache-Control', 'max-age=31536000');
|
|
||||||
res.sendFile(path.join(process.cwd(), '/public/', manifest['bundle.css']));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
staticFiles.get('*.jpg', (req, res) => {
|
|
||||||
res.set('Cache-Control', 'max-age=31536000');
|
|
||||||
res.sendFile(path.join(process.cwd(), '/public/static', req.url))
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = staticFiles;
|
|
|
@ -1,18 +1,17 @@
|
||||||
const { resolve, join } = require('path')
|
const { resolve } = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
|
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||||
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
|
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
|
||||||
const CompressionPlugin = require("compression-webpack-plugin")
|
const CompressionPlugin = require('compression-webpack-plugin')
|
||||||
const ManifestPlugin = require('webpack-manifest-plugin')
|
const ManifestPlugin = require('webpack-manifest-plugin')
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin')
|
const CleanWebpackPlugin = require('clean-webpack-plugin')
|
||||||
|
|
||||||
const config = {
|
const browserConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
context: resolve(__dirname, 'src'),
|
|
||||||
entry: {
|
entry: {
|
||||||
bundle: [
|
bundle: [
|
||||||
'./app-client.js'
|
'./src/app-client.js'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
@ -42,7 +41,7 @@ const config = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loader: "postcss-loader"
|
loader: 'postcss-loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loader: 'sass-loader'
|
loader: 'sass-loader'
|
||||||
|
@ -56,25 +55,23 @@ const config = {
|
||||||
options: {
|
options: {
|
||||||
limit: 8192
|
limit: 8192
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimizer: [
|
||||||
new UglifyJsPlugin({
|
new UglifyJsPlugin(),
|
||||||
cache: true,
|
|
||||||
parallel: true,
|
|
||||||
sourceMap: true // set to true if you want JS source maps
|
|
||||||
}),
|
|
||||||
new OptimizeCSSAssetsPlugin({})
|
new OptimizeCSSAssetsPlugin({})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin(['dist', 'public/static'], {}),
|
|
||||||
new MiniCssExtractPlugin({filename: '[name].[contenthash].css'}),
|
|
||||||
new CompressionPlugin({}),
|
|
||||||
new ManifestPlugin(),
|
new ManifestPlugin(),
|
||||||
]
|
new webpack.DefinePlugin({ __isBrowser__: 'true' }),
|
||||||
|
new CleanWebpackPlugin(['public/static', 'build'], {}),
|
||||||
|
new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }),
|
||||||
|
new CompressionPlugin({})
|
||||||
|
],
|
||||||
|
node: { fs: 'empty' }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config
|
module.exports = browserConfig
|
|
@ -1,74 +0,0 @@
|
||||||
const { resolve, join } = require('path')
|
|
||||||
const webpack = require('webpack')
|
|
||||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
mode: 'development',
|
|
||||||
devtool: 'cheap-eval-source-map',
|
|
||||||
context: resolve(__dirname, 'src'),
|
|
||||||
entry: {
|
|
||||||
bundle: [
|
|
||||||
'webpack-hot-middleware/client',
|
|
||||||
'./app-client.js'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: resolve(__dirname,'public/static'),
|
|
||||||
filename: 'bundle.js',
|
|
||||||
publicPath: '/static/'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
use: [
|
|
||||||
'babel-loader'
|
|
||||||
],
|
|
||||||
exclude: '/node_modules/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: "style-loader"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'css-loader',
|
|
||||||
options: {
|
|
||||||
modules: true,
|
|
||||||
importLoaders: 2,
|
|
||||||
localIdentName: '[name]__[local]___[hash:base64:5]'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: "postcss-loader"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: "sass-loader"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|jpg)$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader: 'url-loader'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(html)$/,
|
|
||||||
use: {
|
|
||||||
loader: 'html-loader',
|
|
||||||
options: {
|
|
||||||
attrs: [':data-src']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
|
||||||
new webpack.NamedModulesPlugin(),
|
|
||||||
new ManifestPlugin({'writeToFileEmit': true}),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
module.exports = config
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
const { resolve } = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
const nodeExternals = require('webpack-node-externals')
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||||
|
const CreateFileWebpack = require('create-file-webpack')
|
||||||
|
|
||||||
|
const serverConfig = {
|
||||||
|
entry: './src/server.js',
|
||||||
|
target: 'node',
|
||||||
|
externals: [nodeExternals()],
|
||||||
|
output: {
|
||||||
|
path: resolve(__dirname, 'build'),
|
||||||
|
filename: 'server.js',
|
||||||
|
publicPath: '/'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
use: [
|
||||||
|
'babel-loader'
|
||||||
|
],
|
||||||
|
exclude: '/node_modules/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: [
|
||||||
|
MiniCssExtractPlugin.loader,
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
modules: true,
|
||||||
|
exportOnlyLocals: true,
|
||||||
|
importLoaders: 2,
|
||||||
|
localIdentName: '[name]__[local]___[hash:base64:5]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'postcss-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'sass-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpg)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
limit: 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
__isBrowser__: 'false'
|
||||||
|
}),
|
||||||
|
new MiniCssExtractPlugin(),
|
||||||
|
new CreateFileWebpack({
|
||||||
|
path: './src/utils/',
|
||||||
|
fileName: 'data.json',
|
||||||
|
content: JSON.stringify({
|
||||||
|
'posts': [],
|
||||||
|
'other': {}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = serverConfig
|
Loading…
Reference in New Issue