initial server-side rendering, restructure source folder
This commit is contained in:
parent
95e2170c7b
commit
27582ef871
@ -15,6 +15,7 @@ This is a complete rewrite of Dubdiff with:
|
|||||||
|
|
||||||
- simpler project architecture
|
- simpler project architecture
|
||||||
- client-side diffing engine and simplified server
|
- client-side diffing engine and simplified server
|
||||||
|
- server-side rendering
|
||||||
- switch to React from Angular
|
- switch to React from Angular
|
||||||
- clean up of diffing engine
|
- clean up of diffing engine
|
||||||
- goal of implementing a HTML diff viewer
|
- goal of implementing a HTML diff viewer
|
||||||
@ -65,5 +66,4 @@ To make the application start on boot, run the following:
|
|||||||
pm2 startup systemd
|
pm2 startup systemd
|
||||||
pm2 save
|
pm2 save
|
||||||
|
|
||||||
[Digital Ocean: How To Set Up a Node.js Application for Production on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04)
|
[Digital Ocean: How To Set Up a Node.js Application for Production on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04)
|
||||||
|
|
2
TODO.md
Normal file
2
TODO.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
- create production mode build and serve settings: `webpack.js` and `src/server/babel.index.js`
|
16204
dist/browser-bundle.js
vendored
16204
dist/browser-bundle.js
vendored
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
@ -4,7 +4,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root">If you see this then something is wrong.</div>
|
<div id="root">If you see this then something is wrong.</div>
|
||||||
<script src="browser-bundle.js"></script>
|
<script src="dist/browser-bundle.js"></script>
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.2/semantic.min.css"></link>
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.2/semantic.min.css"></link>
|
||||||
<link href="main.css" rel="stylesheet"> </body>
|
<link href="dist/main.css" rel="stylesheet"> </body>
|
||||||
</html>
|
</html>
|
@ -2,11 +2,11 @@
|
|||||||
"name": "dubdiff-2",
|
"name": "dubdiff-2",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "src/server/babel.index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ebpack --colors",
|
"build": "ebpack --colors",
|
||||||
"start": "webpack --progress --colors --watch",
|
"start": "webpack --progress --colors --watch",
|
||||||
"serve": "node src/server/index.js",
|
"serve": "node src/server/babel.index.js",
|
||||||
"webpack-stats": "webpack --profile --json > stats.json",
|
"webpack-stats": "webpack --profile --json > stats.json",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
@ -31,12 +31,14 @@
|
|||||||
"uuid": "^3.0.1"
|
"uuid": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.3.26",
|
"babel-core": "^6.18.2",
|
||||||
"babel-loader": "^6.2.0",
|
"babel-loader": "^6.2.0",
|
||||||
"babel-preset-es2015": "^6.3.13",
|
"babel-preset-es2015": "^6.3.13",
|
||||||
"babel-preset-es2015-native-modules": "^6.9.4",
|
"babel-preset-es2015-native-modules": "^6.9.4",
|
||||||
|
"babel-preset-node6": "^11.0.0",
|
||||||
"babel-preset-react": "^6.3.13",
|
"babel-preset-react": "^6.3.13",
|
||||||
"copyfiles": "^0.2.2",
|
"copyfiles": "^0.2.2",
|
||||||
|
"piping": "^1.0.0-rc.4",
|
||||||
"webpack": "^2.1.0-beta.27"
|
"webpack": "^2.1.0-beta.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,13 @@ import ReactDOM from 'react-dom'
|
|||||||
import * as Redux from 'redux'
|
import * as Redux from 'redux'
|
||||||
|
|
||||||
import {Provider} from 'react-redux'
|
import {Provider} from 'react-redux'
|
||||||
import * as localStore from './localStore'
|
|
||||||
|
|
||||||
import * as reducers from './reducers'
|
|
||||||
|
|
||||||
import Main from './components/Main'
|
|
||||||
import Compare from './components/Compare'
|
|
||||||
|
|
||||||
import { Router } from 'react-router'
|
|
||||||
import createBrowserHistory from 'history/lib/createBrowserHistory'
|
import createBrowserHistory from 'history/lib/createBrowserHistory'
|
||||||
import {Route, IndexRoute, Redirect } from 'react-router'
|
import {Router, Route, IndexRoute, Redirect } from 'react-router'
|
||||||
|
|
||||||
|
import * as localStore from '../common/localStore'
|
||||||
|
import * as reducers from '../common/reducers'
|
||||||
|
import routes from '../common/routes'
|
||||||
|
|
||||||
|
|
||||||
//create the redux store
|
//create the redux store
|
||||||
@ -37,8 +34,7 @@ function render() {
|
|||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router history={createBrowserHistory()}>
|
<Router history={createBrowserHistory()}>
|
||||||
<Route path="/" component={Main} />
|
{routes}
|
||||||
<Route path="/**" component={Compare}/>
|
|
||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
</Provider>
|
||||||
, document.getElementById('root'))
|
, document.getElementById('root'))
|
@ -30,6 +30,5 @@ function diffToPre(diff) {
|
|||||||
|
|
||||||
|
|
||||||
const ifNotNewlineSpace = str => {
|
const ifNotNewlineSpace = str => {
|
||||||
console.log(str)
|
|
||||||
return !str.endsWith('\n') ? ' ' : ''
|
return !str.endsWith('\n') ? ' ' : ''
|
||||||
}
|
}
|
@ -27,6 +27,5 @@ function diffToPre(diff) {
|
|||||||
|
|
||||||
|
|
||||||
const ifNotNewlineSpace = str => {
|
const ifNotNewlineSpace = str => {
|
||||||
console.log(str)
|
|
||||||
return !str.endsWith('\n') ? ' ' : ''
|
return !str.endsWith('\n') ? ' ' : ''
|
||||||
}
|
}
|
13
src/common/routes.js
Normal file
13
src/common/routes.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {Route, IndexRout, Redirect } from 'react-router'
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Main from './components/Main'
|
||||||
|
import Compare from './components/Compare'
|
||||||
|
|
||||||
|
var routes = [
|
||||||
|
<Route path="/" component={Main}/>,
|
||||||
|
<Route path="/:compareId" component={Compare}/>
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
export default routes
|
@ -12,7 +12,7 @@ import * as Dubdiff from './util/dubdiff'
|
|||||||
|
|
||||||
const input = (state) => state.input
|
const input = (state) => state.input
|
||||||
const format = (state) => state.format
|
const format = (state) => state.format
|
||||||
const show = (state) => state.show
|
const show = (state) => state.show
|
||||||
|
|
||||||
export const safeInput = createSelector(
|
export const safeInput = createSelector(
|
||||||
[input],
|
[input],
|
||||||
@ -25,7 +25,6 @@ export const safeInput = createSelector(
|
|||||||
export const isMarkdownFormat = createSelector(
|
export const isMarkdownFormat = createSelector(
|
||||||
[format],
|
[format],
|
||||||
(format) => {
|
(format) => {
|
||||||
console.log(format, Format.MARKDOWN)
|
|
||||||
return format == Format.MARKDOWN
|
return format == Format.MARKDOWN
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -10,9 +10,6 @@ export function plaintextDiff(original, final) {
|
|||||||
let diff = JsDiff.diffArrays(arrOriginal, arrFinal)
|
let diff = JsDiff.diffArrays(arrOriginal, arrFinal)
|
||||||
diff = plaintextRestoreSpaces(diff)
|
diff = plaintextRestoreSpaces(diff)
|
||||||
|
|
||||||
|
|
||||||
console.log(diffToLogString(diff))
|
|
||||||
|
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
// return JsDiff.diffWordsWithSpace(original,final)
|
// return JsDiff.diffWordsWithSpace(original,final)
|
34
src/server/babel.index.js
Normal file
34
src/server/babel.index.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
var Path = require('path');
|
||||||
|
|
||||||
|
var srcRoot = Path.join(__dirname, '..')//.replace(/\\/g, "/")
|
||||||
|
|
||||||
|
//there should be some option for distribution / optimization?
|
||||||
|
var config = {
|
||||||
|
/* upgraded to babel 6 to be able to use this preset */
|
||||||
|
presets: ["node6", "react"],
|
||||||
|
sourceMaps: "both",
|
||||||
|
//highlightCode: false,
|
||||||
|
sourceRoot: srcRoot,
|
||||||
|
only: /src/
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
require('babel-core/register')(config);
|
||||||
|
|
||||||
|
|
||||||
|
const PIPING = true
|
||||||
|
|
||||||
|
//!!! Need to guard for production environments
|
||||||
|
//if (process.env.NODE_ENV !== "production") {
|
||||||
|
if (PIPING)
|
||||||
|
if (!require("piping")({hook: true, includeModules: false})) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
|
try {
|
||||||
|
require('./index.js');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error.stack);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
const express = require('express')
|
import express from 'express'
|
||||||
const jf = require('jsonfile')
|
import jf from 'jsonfile'
|
||||||
const fs = require('fs')
|
import fs from 'fs'
|
||||||
const uuid = require('uuid')
|
import uuid from 'uuid'
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
@ -1,32 +1,56 @@
|
|||||||
const express = require('express')
|
import express from 'express'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
const bodyParser = require('body-parser');
|
import bodyParser from 'body-parser'
|
||||||
|
import * as Redux from 'redux'
|
||||||
|
|
||||||
|
import comparisonRouter from './comparison'
|
||||||
|
|
||||||
|
|
||||||
|
import * as reducers from '../common/reducers'
|
||||||
|
import * as actions from '../common/actions'
|
||||||
|
|
||||||
|
import render from './render'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
const comparisonRouter = require('./comparison')
|
//serve the dist static files at /dist
|
||||||
|
app.use('/dist', express.static(path.join(__dirname, '..', '..', 'dist')))
|
||||||
|
|
||||||
app.use(express.static('dist'))
|
//serve the comparison api at /api/compare
|
||||||
app.use('bower_components/*', express.static('bower_components'))
|
|
||||||
|
|
||||||
|
|
||||||
app.use(bodyParser.json())
|
app.use(bodyParser.json())
|
||||||
app.use('/api/compare', comparisonRouter);
|
app.use('/api/compare', comparisonRouter);
|
||||||
|
|
||||||
|
|
||||||
app.route('/*')
|
|
||||||
.get(function(req, res) {
|
//the following routes are for server-side rendering of the app
|
||||||
res.sendFile(path.join(__dirname, '..', '..', 'dist', 'index.html'));
|
|
||||||
});
|
//eventually, we should render the comparison directly from the server
|
||||||
|
/*
|
||||||
|
app.route('/:comparisonId')
|
||||||
|
.get((req, res) => {
|
||||||
|
const store = createSessionStore()
|
||||||
|
...
|
||||||
|
|
||||||
|
})
|
||||||
|
app.route('/'). ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//but for now, let's just render the app and let it fetch comparison data
|
||||||
|
app.use((req, res) => render(createSessionStore(), req, res))
|
||||||
|
|
||||||
app.listen(8080, function () {
|
app.listen(8080, function () {
|
||||||
console.log('Server listening on port 8080.')
|
console.log('Server listening on port 8080.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
//this is pretty much redundant at this point
|
||||||
|
function createSessionStore() {
|
||||||
|
//create the redux store
|
||||||
|
return Redux.createStore(
|
||||||
|
Redux.combineReducers(reducers)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
84
src/server/render.js
Normal file
84
src/server/render.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { renderToString } from 'react-dom/server'
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
import { match, RoutingContext } from 'react-router'
|
||||||
|
|
||||||
|
import routes from '../common/routes.js'
|
||||||
|
|
||||||
|
|
||||||
|
export default function render(store, req, res) {
|
||||||
|
console.log(store.getState())
|
||||||
|
|
||||||
|
// Send the rendered page back to the client
|
||||||
|
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
|
||||||
|
if (error) {
|
||||||
|
res.status(500).send(renderError('Routing Error', error.message))
|
||||||
|
} else if (redirectLocation) {
|
||||||
|
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
|
||||||
|
} else if (renderProps) {
|
||||||
|
// Render the component to a string
|
||||||
|
try {
|
||||||
|
const html = renderToString(
|
||||||
|
<Provider store={store}>
|
||||||
|
<RoutingContext {...renderProps} />
|
||||||
|
</Provider>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Grab the initial state from our Redux store
|
||||||
|
const initialState = store.getState()
|
||||||
|
// and send
|
||||||
|
res.status(200).send(appTemplate(html, initialState))
|
||||||
|
}
|
||||||
|
catch(ex) {
|
||||||
|
res.status(500).send(errorTemplate('Render Exception', ex.message, ex))
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
res.status(404).send(renderError('Not found', ''))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageTemplate = (body) => {
|
||||||
|
return `
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Dubdiff</title>
|
||||||
|
|
||||||
|
<!-- CSS -->
|
||||||
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.2/semantic.min.css"/>
|
||||||
|
<link rel="stylesheet" href="dist/main.css"/>
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<!--<link rel="shortcut icon" href="/assets/favicon.ico">-->
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${body}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorTemplate(title, message, exception) {
|
||||||
|
return pageTemplate(`
|
||||||
|
<h1>${title}</h1>
|
||||||
|
<p>${message}</p>
|
||||||
|
<pre>${exception.toString()}</pre>
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function appTemplate(html, initialState) {
|
||||||
|
return pageTemplate(`
|
||||||
|
<div id="root">${html}</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}
|
||||||
|
</script>
|
||||||
|
<!-- <script>__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__</script> -->
|
||||||
|
<script type="text/javascript" src="dist/browser-bundle.js"></script>
|
||||||
|
`)
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
cache: true,
|
cache: true,
|
||||||
entry: './src/client',
|
entry: './src/client/index.js',
|
||||||
output: {
|
output: {
|
||||||
filename: './dist/browser-bundle.js'
|
filename: './dist/browser-bundle.js'
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user