diff --git a/.babelrc b/.babelrc index eddb7bc..9a981a8 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,5 @@ { - "presets": ["es2015", "stage-0", "react"], + "presets": ["es2015", "stage-1", "react"], "plugins": [ "transform-runtime", "add-module-exports", diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index de11b05..0000000 --- a/.eslintrc +++ /dev/null @@ -1,35 +0,0 @@ ---- -extends: - - eslint:recommended - - plugin:react/recommended -parser: babel-eslint -parserOptions: - ecmaVersion: 6 - ecmaFeatures: - jsx: true - sourceType: module -env: - es6: true - node: true - mocha: true - jquery: true - browser: true -plugins: - - react -globals: - device: false - IScroll: false - Velocity: false - ScrollMagic: false -rules: - comma-dangle: [warn, only-multiline] - comma-spacing: [warn, {before: false, after: true}] - comma-style: [warn, last, {exceptions: {VariableDeclaration: true}}] - semi: [warn, always, {omitLastInOneLineBlock: true}] - - no-console: warn - no-debugger: warn - no-unused-vars: warn - - react/display-name: off - react/prop-types: off diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 0000000..87dc9c6 --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,33 @@ +--- +root: true +extends: + - "eslint:recommended" + - "plugin:react/recommended" +parser: "babel-eslint" +parserOptions: + ecmaVersion: 6 + ecmaFeatures: + jsx: true + sourceType: "module" +plugins: + - "react" +env: + es6: true + node: true + browser: true +globals: + device: false + IScroll: false + Velocity: false + ScrollMagic: false +rules: + comma-dangle: ["warn", "only-multiline"] + comma-spacing: ["warn", {before: false, after: true}] + comma-style: ["warn", "last", {exceptions: {VariableDeclaration: true}}] + + no-console: "warn" + no-debugger: "warn" + no-unused-vars: "warn" + + react/display-name: "off" + react/prop-types: "off" diff --git a/.gitignore b/.gitignore index a9d3935..9eb8254 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,11 @@ .DS_Store .idea/ -.imdone/ -.vscode/ + .nyc_output/ coverage/ -node_modules/ + npm-debug* +node_modules/ + output/ -/public/assets/*.js -/public/assets/*.css -/public/assets/*.map +public/assets/*.* diff --git a/client/index.js b/client/index.js index 9a16422..59964a1 100644 --- a/client/index.js +++ b/client/index.js @@ -1,26 +1,25 @@ -import './motions'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import Provider from 'react-redux/lib/components/Provider'; -import browserHistory from 'react-router/lib/browserHistory'; -import createStore from 'common/store'; +import React from 'react' +import ReactDOM from 'react-dom' +import Provider from 'react-redux/lib/components/Provider' +import {Router, browserHistory} from 'react-router' +import createStore from 'common/store' -const store = createStore(window.__INITIAL_STATE__); -const rootElement = document.getElementById('root'); -document.body.removeChild(document.querySelector('[data-recycle]')); +const store = createStore(window.__INITIAL_STATE__) +const rootElement = document.getElementById('root') +document.body.removeChild(document.querySelector('[data-recycle]')) function render() { if ('production' !== process.env.NODE_ENV) { - ReactDOM.unmountComponentAtNode(rootElement); + ReactDOM.unmountComponentAtNode(rootElement) } ReactDOM.render( - {require('common/routes')(browserHistory)} + , rootElement - ); + ) } -render(); +render() -module.hot && module.hot.accept('common/routes', render); +module.hot && module.hot.accept('common/routes', render) diff --git a/client/motions.js b/client/motions.js deleted file mode 100644 index 3ce87c9..0000000 --- a/client/motions.js +++ /dev/null @@ -1,22 +0,0 @@ -import 'ScrollMagic'; -import 'ScrollMagic.velocity'; - -if ('production' !== process.env.NODE_ENV) { - require('debug.addIndicators'); -} - -Velocity.RegisterEffect('cform.standOut', { - defaultDuration: 300, - calls: [ - [{translateY: -60}, 0.66], - [{translateY: 0}, 0.34] - ], -}); - -Velocity.RegisterEffect('cform.standBack', { - defaultDuration: 300, - calls: [ - [{translateY: 60}, 0.34], - [{translateY: 0}, 0.66] - ], -}); diff --git a/cmrh.conf.js b/cmrh.conf.js index 1104eac..62e72b4 100644 --- a/cmrh.conf.js +++ b/cmrh.conf.js @@ -1,3 +1,3 @@ export default { generateScopedName: '[name]_[local]-[hash:base64:4]', -}; +} diff --git a/common/middleware.js b/common/middleware.js index 5ff4996..a59afcb 100644 --- a/common/middleware.js +++ b/common/middleware.js @@ -1,11 +1,11 @@ -import loggerMiddleware from 'redux-logger'; -import promiseMiddleware from 'redux-promise-middleware'; +import loggerMiddleware from 'redux-logger' +import promiseMiddleware from 'redux-promise-middleware' -const promiseTypeSuffixes = ['读取', '成功', '失败']; +const promiseTypeSuffixes = ['读取', '成功', '失败'] export default (('production' === process.env.NODE_ENV) || global.global) ? [ promiseMiddleware({promiseTypeSuffixes}), ] : [ promiseMiddleware({promiseTypeSuffixes}), loggerMiddleware({level: 'info', duration: true}) -]; +] diff --git a/common/reducer.js b/common/reducer.js index 6edb89f..53341c4 100644 --- a/common/reducer.js +++ b/common/reducer.js @@ -1,7 +1,7 @@ -import combineReducers from 'redux/lib/combineReducers'; +import combineReducers from 'redux/lib/combineReducers' export default combineReducers({ application(state = "Initial State") { - return state; + return state }, -}); +}) diff --git a/common/routes.js b/common/routes.js index cb27dae..d44fe06 100644 --- a/common/routes.js +++ b/common/routes.js @@ -1,9 +1,7 @@ -import React from 'react'; -import {Router, Route/*, IndexRoute*/} from 'react-router'; +import React from 'react' +import {Route/*, IndexRoute*/} from 'react-router' import { Application, -} from './ui'; +} from './ui' -export default history => - -; +export default diff --git a/common/store.js b/common/store.js index 64d1be2..4ab6734 100644 --- a/common/store.js +++ b/common/store.js @@ -1,7 +1,7 @@ -import compose from 'redux/lib/compose'; -import createStore from 'redux/lib/createStore'; -import applyMiddleware from 'redux/lib/applyMiddleware'; -import middleware from './middleware'; +import compose from 'redux/lib/compose' +import createStore from 'redux/lib/createStore' +import applyMiddleware from 'redux/lib/applyMiddleware' +import middleware from './middleware' export default (initialState = {}) => { const store = createStore(require('./reducer'), initialState, compose( @@ -10,11 +10,11 @@ export default (initialState = {}) => { && typeof window.devToolsExtension !== 'undefined' && 'production' !== process.env.NODE_ENV) ? window.devToolsExtension() : _ => _ - )); + )) module.hot && module.hot.accept('./reducer', function() { - store.replaceReducer(require('./reducer')); - }); + store.replaceReducer(require('./reducer')) + }) - return store; -}; + return store +} diff --git a/common/ui/Application/index.jsx b/common/ui/Application/index.jsx index 30c81d4..f01f07d 100644 --- a/common/ui/Application/index.jsx +++ b/common/ui/Application/index.jsx @@ -1,7 +1,7 @@ -import React, {Component} from 'react'; -import Helmet from 'react-helmet'; -import Link from 'react-router/lib/Link'; -import './styles.css'; +import React, {Component} from 'react' +import Helmet from 'react-helmet' +import Link from 'react-router/lib/Link' +import './styles.css' const defaultHelmet = { defaultTitle: '巧思', @@ -11,7 +11,7 @@ const defaultHelmet = { {"http-equiv": "X-UA-Compatible", "content": "IE=edge, chrome=1"}, {"name": "viewport", "content": "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"}, ] -}; +} export default class Application extends Component { render() { @@ -20,11 +20,11 @@ export default class Application extends Component {
{this.props.children}
- ; + } } diff --git a/common/ui/Application/styles.css b/common/ui/Application/styles.css index cb03448..cf3cce9 100644 --- a/common/ui/Application/styles.css +++ b/common/ui/Application/styles.css @@ -1 +1,5 @@ @import 'common/styles/globals.css'; + +.test { + color: yellow; +} diff --git a/common/ui/index.js b/common/ui/index.js index a9b5a99..033fb3c 100644 --- a/common/ui/index.js +++ b/common/ui/index.js @@ -1 +1 @@ -export Application from './Application'; +export Application from './Application' diff --git a/config/index.js b/config/index.js deleted file mode 100644 index c9af51d..0000000 --- a/config/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import {resolve} from 'path'; - -export default { - __root: resolve(), - __config: resolve('config'), - __client: resolve('client'), - __common: resolve('common'), - __public: resolve('public'), - __server: resolve('server'), -}; diff --git a/config/testing-setup.js b/config/testing-setup.js index b2035fd..177403f 100644 --- a/config/testing-setup.js +++ b/config/testing-setup.js @@ -1,7 +1,7 @@ -require('babel-register'); -require('babel-polyfill'); -require('css-modules-require-hook/preset'); +require('babel-register') +require('babel-polyfill') +require('css-modules-require-hook/preset') -global.document = require('jsdom').jsdom(); -global.window = document.defaultView; -global.navigator = window.navigator; +global.document = require('jsdom').jsdom() +global.window = document.defaultView +global.navigator = window.navigator diff --git a/config/webpack.babel.js b/config/webpack.babel.js index 01330a7..54eb1e9 100644 --- a/config/webpack.babel.js +++ b/config/webpack.babel.js @@ -1,7 +1,8 @@ -import path from 'path'; -import webpack from 'webpack'; +import path from 'path' +import webpack from 'webpack' +import AssetsPlugin from 'assets-webpack-plugin' -const isProduction = 'production' == process.env.NODE_ENV; +const isProduction = 'production' == process.env.NODE_ENV const defaults = { context: process.cwd(), @@ -10,8 +11,7 @@ const defaults = { entry: { client: [path.resolve('client/index.js')], vendor: [ - 'babel-polyfill', 'isomorphic-fetch', 'device.js', - 'velocity-animate', 'velocity-animate/velocity.ui', 'jquery', + 'babel-polyfill', 'device.js', 'isomorphic-fetch', 'react', 'react-dom', 'react-router', 'redux', 'react-redux', ], }, @@ -26,10 +26,6 @@ const defaults = { alias: { 'fetch': 'isomorphic-fetch', 'common': path.resolve('common'), - 'velocity': path.resolve('node_modules/velocity-animate/velocity.js'), - 'ScrollMagic': path.resolve('node_modules/scrollmagic/scrollmagic/uncompressed/ScrollMagic.js'), - 'debug.addIndicators': path.resolve('node_modules/scrollmagic/scrollmagic/uncompressed/plugins/debug.addIndicators.js'), - 'ScrollMagic.velocity': path.resolve('node_modules/scrollmagic/scrollmagic/uncompressed/plugins/animation.velocity.js'), }, fallback: path.resolve('public'), extensions: ['', '.js', '.json', '.jsx'], @@ -49,28 +45,30 @@ const defaults = { 'css?modules&localIdentName=[name]_[local]-[hash:base64:4]', 'postcss', ], - }, { - test: require.resolve('jquery'), loader: 'expose?$!expose?jQuery' - }, { - test: require.resolve('scrollmagic'), loader: 'expose?ScrollMagic' }], }, plugins: [ new webpack.EnvironmentPlugin(['NODE_ENV']), new webpack.optimize.OccurrenceOrderPlugin(true), - new webpack.ProvidePlugin({ - 'Promise': 'bluebird', - 'IScroll': 'iscroll/build/iscroll-probe' + new webpack.ProvidePlugin({'Promise': 'bluebird'}), + new webpack.optimize.CommonsChunkPlugin( + 'vendor', isProduction ? 'vendor.js?[hash]' : 'vendor.js' + ), + new AssetsPlugin({ + filename: 'manifest.json', + fullpath: true, + path: path.resolve('public/assets'), + prettyPrint: !isProduction, + update: true }), - new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), ], postcss(webpack) { return [ require('postcss-import')({addDependencyTo: webpack, path: ['./']}), - require('postcss-clearfix')({display: 'table'}), require('postcss-hexrgba'), require('postcss-position'), require('postcss-responsive-type'), + require('postcss-clearfix')({display: 'table'}), require('postcss-cssnext')({browsers: '> 1%, last 2 versions'}), require('postcss-assets')({ cachebuster: true, @@ -81,9 +79,9 @@ const defaults = { require('laggard')({opacity: false, pixrem: false, pseudo: false}), require('cssnano')({autoprefixer: false, safe: true}), require('postcss-browser-reporter'), - ]; + ] }, -}; +} export default { ...defaults, @@ -112,4 +110,4 @@ export default { new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), ], -}; +} diff --git a/package.json b/package.json index a32169d..9dc7dff 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,8 @@ }, "license": "MIT", "scripts": { - "clean": "rimraf output", + "clean": "rimraf output public/assets/*.*", "slate": "rimraf node_modules && npm install", - "debug": "cross-env NODE_ENV=debug devtool server --console", "start": "cross-env NODE_ENV=development nodemon --exec node server", "build": "npm run build:server && npm run build:assets", "build:server": "cross-env NODE_ENV=production npm run clean && babel server -d output -qs", @@ -44,79 +43,72 @@ "lint" ], "dependencies": { - "bluebird": "^3.4.0", + "bluebird": "^3.4.1", "device.js": "github:matthewhudson/device.js", - "iscroll": "^5.2.0", "isomorphic-fetch": "^2.2.1", - "jquery": "^2.2.4", "koa": "^2.0.0", "koa-compress": "^2.0.0", + "koa-logger": "^2.0.0", "koa-router": "^7.0.1", "koa-static": "^3.0.0", - "lodash": "^4.13.1", - "react": "^15.1.0", - "react-dom": "^15.1.0", + "lodash": "^4.14.0", + "react": "^15.2.1", + "react-dom": "^15.2.1", "react-helmet": "^3.1.0", "react-redux": "^4.4.5", - "react-router": "^2.4.1", + "react-router": "^2.6.0", "redux": "^3.5.2", - "redux-actions": "^0.9.1", - "redux-promise-middleware": "^3.0.0", - "scrollmagic": "^2.0.5", - "velocity-animate": "^1.2.3" + "redux-actions": "^0.10.1", + "redux-promise-middleware": "^3.3.2" }, "devDependencies": { - "ava": "^0.15.1", - "babel-cli": "^6.9.0", - "babel-core": "^6.9.1", - "babel-eslint": "^6.0.4", + "assets-webpack-plugin": "^3.4.0", + "ava": "^0.15.2", + "babel-cli": "^6.11.4", + "babel-core": "^6.11.4", + "babel-eslint": "^6.1.2", "babel-loader": "^6.2.4", "babel-plugin-add-module-exports": "^0.2.1", - "babel-plugin-module-alias": "^1.3.0", - "babel-plugin-transform-runtime": "^6.7.5", + "babel-plugin-module-alias": "^1.6.0", + "babel-plugin-transform-runtime": "^6.9.0", "babel-polyfill": "^6.9.1", "babel-preset-es2015": "^6.9.0", - "babel-preset-react": "^6.5.0", + "babel-preset-react": "^6.11.1", "babel-preset-react-optimize": "^1.0.1", - "babel-preset-stage-0": "^6.5.0", - "babel-register": "^6.9.0", - "chokidar": "^1.5.1", - "coveralls": "^2.11.9", - "cross-env": "^1.0.8", + "babel-preset-stage-1": "^6.5.0", + "babel-register": "^6.11.5", + "coveralls": "^2.11.11", + "cross-env": "^2.0.0", "css-loader": "^0.23.1", "css-modules-require-hook": "^4.0.1", - "cssnano": "^3.6.2", - "devtool": "^2.0.1", - "enzyme": "^2.3.0", - "eslint": "^2.11.0", - "eslint-loader": "^1.3.0", - "eslint-plugin-react": "^5.1.1", + "cssnano": "^3.7.3", + "enzyme": "^2.4.1", + "eslint": "^3.1.1", + "eslint-loader": "^1.4.1", + "eslint-plugin-react": "^5.2.2", "expose-loader": "^0.7.1", - "file-loader": "^0.8.5", - "jsdom": "^9.2.1", - "koa-logger": "^2.0.0", + "file-loader": "^0.9.0", + "jsdom": "^9.4.1", "laggard": "^0.1.0", - "localtunnel": "^1.8.1", - "nodemon": "^1.9.1", - "nyc": "^6.4.0", - "postcss-advanced-variables": "^1.2.2", + "nodemon": "^1.10.0", + "nyc": "^7.1.0", "postcss-assets": "^4.1.0", "postcss-browser-reporter": "^0.5.0", "postcss-clearfix": "^1.0.0", - "postcss-cssnext": "^2.5.2", + "postcss-cssnext": "^2.7.0", "postcss-hexrgba": "^0.2.0", - "postcss-import": "^8.1.0", + "postcss-import": "^8.1.2", "postcss-loader": "^0.9.1", - "postcss-position": "^0.4.0", - "postcss-responsive-type": "^0.3.3", + "postcss-position": "^0.5.0", + "postcss-responsive-type": "^0.4.0", "pre-commit": "^1.1.3", - "react-addons-test-utils": "^15.1.0", + "react-addons-test-utils": "^15.2.1", "redux-logger": "^2.6.1", - "rimraf": "^2.5.2", + "rimraf": "^2.5.4", "style-loader": "^0.13.1", "url-loader": "^0.5.7", "webpack": "^1.13.1", "webpack-dev-middleware": "^1.6.1", - "webpack-hot-middleware": "^2.10.0" + "webpack-hot-middleware": "^2.12.1" } } diff --git a/server/index.js b/server/index.js index 08727ed..9a2e18e 100644 --- a/server/index.js +++ b/server/index.js @@ -1,4 +1,3 @@ -require('babel-register'); -require('babel-polyfill'); -global.config = require('../config'); -require('./server'); +require('babel-register') +require('babel-polyfill') +require('./server') diff --git a/server/modules/extract-language/index.js b/server/modules/extract-language/index.js index ee68126..e5a3dfd 100644 --- a/server/modules/extract-language/index.js +++ b/server/modules/extract-language/index.js @@ -1,5 +1,5 @@ export default async function extractLanguage(context, next) { // TODO: what about '*'? It's better to enable passing default language here - context.state.language = context.request.acceptsLanguages()[0]; - await next(); + context.state.language = context.request.acceptsLanguages()[0] + await next() } diff --git a/server/modules/favicon/index.js b/server/modules/favicon/index.js index 002dc28..c9f75d8 100644 --- a/server/modules/favicon/index.js +++ b/server/modules/favicon/index.js @@ -1,30 +1,30 @@ -import {resolve} from 'path'; -import {readFileSync} from 'fs'; +import {resolve} from 'path' +import {readFileSync} from 'fs' -const A_DAY_IN_MS = 86400000; -const A_YEAR_IN_MS = 31556926000; -const DEFAULT_PATH = '/favicon.ico'; +const A_DAY_IN_MS = 86400000 +const A_YEAR_IN_MS = 31556926000 +const DEFAULT_PATH = '/favicon.ico' -let icon; +let icon export default (path = resolve(DEFAULT_PATH), options = {}) => { const maxAge = options.maxAge == null ? A_DAY_IN_MS - : Math.min(Math.max(0, options.maxAge), A_YEAR_IN_MS); + : Math.min(Math.max(0, options.maxAge), A_YEAR_IN_MS) return async function favicon(context, next) { if (DEFAULT_PATH != context.path) { - return await next(); + return await next() } if ('GET' != context.method && 'HEAD' != context.method) { - context.set('Allow', 'GET, HEAD, OPTIONS'); - context.status = 'OPTIONS' == context.method ? 200 : 405; + context.set('Allow', 'GET, HEAD, OPTIONS') + context.status = 'OPTIONS' == context.method ? 200 : 405 } else { - icon = icon || readFileSync(path); - context.set('Cache-Control', `public, max-age=${maxAge / 1000 | 0}`); - context.type = 'image/x-icon'; - context.body = icon; + icon = icon || readFileSync(path) + context.set('Cache-Control', `public, max-age=${maxAge / 1000 | 0}`) + context.type = 'image/x-icon' + context.body = icon } - }; -}; + } +} diff --git a/server/modules/response-time/index.js b/server/modules/response-time/index.js index f5a5b7f..68bb127 100644 --- a/server/modules/response-time/index.js +++ b/server/modules/response-time/index.js @@ -1,6 +1,6 @@ export default () => async function responseTime(context, next) { - const start = Date.now(); - await next(); - const delta = Math.ceil(Date.now() - start); - context.set('X-Response-Time', `${delta}ms`); -}; + const start = Date.now() + await next() + const delta = Math.ceil(Date.now() - start) + context.set('X-Response-Time', `${delta}ms`) +} diff --git a/server/modules/server-side-render/index.js b/server/modules/server-side-render/index.js index 5e1c743..8fad9ac 100644 --- a/server/modules/server-side-render/index.js +++ b/server/modules/server-side-render/index.js @@ -1,2 +1,2 @@ -export ssrRouteHandler from './route-handler'; -export ssrTemplate from './template'; +export ssrRouteHandler from './route-handler' +export ssrTemplate from './template' diff --git a/server/modules/server-side-render/match-async-prefetch.js b/server/modules/server-side-render/match-async-prefetch.js index 66cd64f..18972c4 100644 --- a/server/modules/server-side-render/match-async-prefetch.js +++ b/server/modules/server-side-render/match-async-prefetch.js @@ -1,56 +1,53 @@ -import createRoutes from 'common/routes'; -import createMemoryHistory from 'react-router/lib/createMemoryHistory'; -import match from 'react-router/lib/match'; -import createStore from 'common/store'; -import React from 'react'; -import {renderToString} from 'react-dom/server'; -import Provider from 'react-redux/lib/components/Provider'; -import RouterContext from 'react-router/lib/RouterContext'; -import Helmet from 'react-helmet'; -import ssrTemplate from './template'; +import {Router, createMemoryHistory, match} from 'react-router' +import routes from 'common/routes' +import createStore from 'common/store' +import React from 'react' +import {renderToString} from 'react-dom/server' +import Provider from 'react-redux/lib/components/Provider' +import Helmet from 'react-helmet' +import ssrTemplate from './template' export default function matchAsyncPrefetch(context, options) { - const location = context.url; - const routes = createRoutes(createMemoryHistory()); + const location = context.url return new Promise(resolve => { match({routes, location}, function(error, redirectLocation, renderProps) { if (error) { - context.status = 500; - context.body = error.message; - resolve(true); + console('error') + context.status = 500 + context.body = error.message + resolve(true) } if (redirectLocation) { - context.status = 302; - const {pathname, search} = redirectLocation; - context.redirect(`${pathname + search}`); - resolve(true); + console('redirect') + context.status = 302 + const {pathname, search} = redirectLocation + context.redirect(`${pathname + search}`) + resolve(true) } if (renderProps) { - const store = createStore(); + const store = createStore() const fetch = Promise.all( renderProps.components .filter(c => c && c.fetchData) .map(c => store.dispatch(c.fetchData())) - ); + ) resolve(fetch.then(() => { - options.state = JSON.stringify(store.getState()); - options.content = renderToString( + options.state = JSON.stringify(store.getState()) + options.body = renderToString( - + - ); - options.head = Helmet.rewind(); - context.status = 200; - context.body = ssrTemplate(options); - })); - } else { - context.status = 404; - context.body = 'Not Found'; - resolve(true); + ) + options.head = Helmet.rewind() + context.status = 200 + context.body = ssrTemplate(options) + })) } - }); - }); + + resolve(true) + }) + }) } diff --git a/server/modules/server-side-render/route-handler.js b/server/modules/server-side-render/route-handler.js index 0ba464b..d063690 100644 --- a/server/modules/server-side-render/route-handler.js +++ b/server/modules/server-side-render/route-handler.js @@ -1,15 +1,15 @@ -import 'css-modules-require-hook/preset'; +import 'css-modules-require-hook/preset' -import matchAsyncPrefetch from './match-async-prefetch'; +import matchAsyncPrefetch from './match-async-prefetch' export default (options = {}) => async function ssrRouteHandler(context, next) { if (!options.language || !('string' === typeof options.language)) { - options.language = context.state.language; + options.language = context.state.language } - context.set('Content-Language', options.language); - options.name = options.name || context.app.name; - options.compact = options.compact || 'production' === context.app.env; + context.set('Content-Language', options.language) + options.name = options.name || context.app.name + options.compact = options.compact || 'production' === context.app.env - const hasNext = await matchAsyncPrefetch(context, options); - hasNext && await next(); -}; + const hasNext = await matchAsyncPrefetch(context, options) + hasNext && await next() +} diff --git a/server/modules/server-side-render/template.js b/server/modules/server-side-render/template.js index 2abc6cc..8ef2410 100644 --- a/server/modules/server-side-render/template.js +++ b/server/modules/server-side-render/template.js @@ -1,22 +1,11 @@ -/** - * Render whatever passed in to plain HTML text as response body. - * - * @param {Object} [options] - * @param {Boolean} [options.compact] - Determine whether to return compact text - * which all `\r\n` will be trimmed. - * @param {String} [options.body=''] - Usually the text from any server-side - * rendering function such as `ReactDOMServer.renderToString()`. - * @returns {XML|string} - */ +import {resolve} from 'path' + export default function ssrTemplate(options) { - const context = Object.assign({}, { - compact: options.env === 'production', - body: '' - }, options); + options.assets = require(resolve('public/assets/manifest.json')) - return context.compact - ? renderHTML(context).replace(/^\s+|\s*(?:\r?\n)+/gm, '') - : renderHTML(context).replace(/^\s+/gm, ''); + return options.compact + ? renderHTML(options).replace(/^\s+|\s*(?:\r?\n)+/gm, '') + : renderHTML(options).replace(/^\s+/gm, '') } const renderHTML = context => ` @@ -29,8 +18,8 @@ const renderHTML = context => `
${context.body}
- - + + -`; +` diff --git a/server/modules/webpack/dev.js b/server/modules/webpack/dev.js index de5650f..8e3fbb4 100644 --- a/server/modules/webpack/dev.js +++ b/server/modules/webpack/dev.js @@ -1,31 +1,31 @@ -import webpackDevMiddleware from 'webpack-dev-middleware'; +import webpackDevMiddleware from 'webpack-dev-middleware' // TODO: extract stats out as individual configuration. -const stats = {chunkModules: false, colors: false}; +const stats = {chunkModules: false, colors: false} export default (compiler, publicPath, options = {}) => { - options = Object.assign({}, {publicPath, stats}, options); - const middleware = webpackDevMiddleware(compiler, options); + options = Object.assign({}, {publicPath, stats}, options) + const middleware = webpackDevMiddleware(compiler, options) return async function webpackDevMiddleware(context, next) { const hasNext = await applyMiddleware(middleware, context.req, { send: content => context.body = content, setHeader: function() {context.set.apply(context, arguments)} - }); + }) - hasNext && await next(); - }; -}; + hasNext && await next() + } +} function applyMiddleware(middleware, req, res) { - const _send = res.send; + const _send = res.send return new Promise((resolve, reject) => { try { - res.send = function() {_send.apply(null, arguments) && resolve(false)}; - middleware(req, res, resolve.bind(null, true)); + res.send = function() {_send.apply(null, arguments) && resolve(false)} + middleware(req, res, resolve.bind(null, true)) } catch (error) { - reject(error); + reject(error) } - }); + }) } diff --git a/server/modules/webpack/hot.js b/server/modules/webpack/hot.js index 6165c4f..c974b34 100644 --- a/server/modules/webpack/hot.js +++ b/server/modules/webpack/hot.js @@ -1,23 +1,23 @@ -import webpackHotMiddleware from 'webpack-hot-middleware'; +import webpackHotMiddleware from 'webpack-hot-middleware' export default compiler => { - const middleware = webpackHotMiddleware(compiler); + const middleware = webpackHotMiddleware(compiler) return async function webpackHotMiddleware(context, next) { - const hasNext = await applyMiddleware(middleware, context.req, context.res); - hasNext && await next(); - }; -}; + const hasNext = await applyMiddleware(middleware, context.req, context.res) + hasNext && await next() + } +} function applyMiddleware(middleware, req, res) { - const _send = res.send; + const _send = res.send return new Promise((resolve, reject) => { try { - res.send = function() {_send.apply(null, arguments) && resolve(false)}; - middleware(req, res, resolve.bind(null, true)); + res.send = function() {_send.apply(null, arguments) && resolve(false)} + middleware(req, res, resolve.bind(null, true)) } catch (error) { - reject(error); + reject(error) } - }); + }) } diff --git a/server/modules/webpack/index.js b/server/modules/webpack/index.js index 23f381f..718746a 100644 --- a/server/modules/webpack/index.js +++ b/server/modules/webpack/index.js @@ -1,2 +1,2 @@ -export webpackDevMiddleware from './dev'; -export webpackHotMiddleware from './hot'; +export webpackDevMiddleware from './dev' +export webpackHotMiddleware from './hot' diff --git a/server/routes/index.js b/server/routes/index.js index 9e360bd..2eb1ec6 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -1,10 +1,10 @@ -import Router from 'koa-router'; -import extractLanguage from '../modules/extract-language'; -import {ssrRouteHandler} from '../modules/server-side-render'; +import Router from 'koa-router' +import extractLanguage from '../modules/extract-language' +import {ssrRouteHandler} from '../modules/server-side-render' -const router = new Router(); -const ssr = ssrRouteHandler(); +const router = new Router() +const ssr = ssrRouteHandler() -router.use(extractLanguage).get('ssr', '*', ssr); +router.use(extractLanguage).get('ssr', '*', ssr) -export default router.routes(); +export default router.routes() diff --git a/server/server.js b/server/server.js index 93455d1..1992358 100644 --- a/server/server.js +++ b/server/server.js @@ -1,63 +1,33 @@ -/* eslint-disable no-console */ -import Koa from 'koa'; - -const server = new Koa(); -const {__root, __public} = global.config; -server.port = process.env.PORT || 3000; -server.name = process.env.NAME = require(`${__root}/package.json`).name; - -server.use(require('./modules/response-time')()); - -if ('production' != server.env) { - server.use(require('koa-logger')()); - - const webpackConfig = require('config/webpack.babel'); - const compiler = require('webpack')(webpackConfig); - const {publicPath} = webpackConfig.output; - const koaWebpack = require('./modules/webpack'); +import Koa from 'koa' +import {resolve} from 'path' - server.use(koaWebpack.webpackDevMiddleware(compiler, publicPath)); - server.use(koaWebpack.webpackHotMiddleware(compiler)); +const server = new Koa() +server.port = process.env.PORT || 3000 +server.name = process.env.NAME = require(resolve('package.json'))['name'] - // const watcher = require('chokidar').watch(require('path').resolve()); +server.use(require('koa-compress')()) +server.use(require('./modules/response-time')()) +server.use(require('./modules/favicon')(resolve('public/favicon.ico'))) - // watcher.on('ready', function() { - // watcher.on('all', function() { - // console.info(`💦 从服务端清除 /server/ 模块缓存`); - // Object.keys(require.cache).forEach(id => { - // /[\/\\]server[\/\\]/.test(id) && delete require.cache[id]; - // }); - // }); - // }); +if ('production' !== server.env) { + server.use(require('koa-logger')()) - // compiler.plugin('done', function() { - // console.info(`💦 从客户端清除 /client/ 模块缓存`); - // Object.keys(require.cache).forEach(id => { - // /[\/\\]client[\/\\]/.test(id) && delete require.cache[id]; - // }); - // }); + const webpackConfig = require('config/webpack.babel') + const compiler = require('webpack')(webpackConfig) + const {publicPath} = webpackConfig.output + const koaWebpack = require('./modules/webpack') - // TODO: figure out the best way to expose localtunnel service - // require('localtunnel')( - // server.port, {subdomain: server.name}, (error, tunnel) => { - // error && console.error(error); - // console.info(`🌏 外网地址:${tunnel.url}\n`); - // } - // ); + server.use(koaWebpack.webpackDevMiddleware(compiler, publicPath)) + server.use(koaWebpack.webpackHotMiddleware(compiler)) } -server.use(require('koa-compress')()); - -server.use(require('./modules/favicon')(`${__public}/favicon.ico`)); - -server.use(require('koa-static')(`${__public}`, {maxage: 604800000})); - -server.use(require('./routes')); +server.use(require('./routes')) +if ('production' === server.env) { + // ISSUE: What if we use Nginx as static file server? + server.use(require('koa-static')(resolve('public'), {gzip: true})) +} +/* eslint-disable no-console */ server.listen(server.port, function() { - console.info(`💻 本地地址:http://localhost:${server.port}`); - require('dns').lookup(require('os').hostname(), (error, address) => { - error && console.error(error); - console.info(`🚧 内网地址:http://${address}:${server.port}`); - }); -}); + console.info(`💻 本地地址:http://localhost:${server.port}`) +})