diff --git a/.babelrc b/.babelrc index adb0690..eddb7bc 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,7 @@ { "presets": ["es2015", "stage-0", "react"], "plugins": [ + "transform-runtime", "add-module-exports", [ "babel-plugin-module-alias", [ diff --git a/.eslintrc b/.eslintrc index cf9515f..de11b05 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,7 +17,10 @@ env: plugins: - react globals: + device: false + IScroll: false Velocity: false + ScrollMagic: false rules: comma-dangle: [warn, only-multiline] comma-spacing: [warn, {before: false, after: true}] diff --git a/client/index.js b/client/index.js index b06987b..9a16422 100644 --- a/client/index.js +++ b/client/index.js @@ -1,9 +1,9 @@ +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 'common/animations'; const store = createStore(window.__INITIAL_STATE__); const rootElement = document.getElementById('root'); diff --git a/common/animations.js b/client/motions.js similarity index 68% rename from common/animations.js rename to client/motions.js index 3b42f4a..3ce87c9 100644 --- a/common/animations.js +++ b/client/motions.js @@ -1,3 +1,10 @@ +import 'ScrollMagic'; +import 'ScrollMagic.velocity'; + +if ('production' !== process.env.NODE_ENV) { + require('debug.addIndicators'); +} + Velocity.RegisterEffect('cform.standOut', { defaultDuration: 300, calls: [ diff --git a/common/middleware.js b/common/middleware.js index 96de672..5ff4996 100644 --- a/common/middleware.js +++ b/common/middleware.js @@ -3,7 +3,7 @@ import promiseMiddleware from 'redux-promise-middleware'; const promiseTypeSuffixes = ['读取', '成功', '失败']; -export default (('production' === process.env.NODE_ENV) || global.GLOBAL) ? [ +export default (('production' === process.env.NODE_ENV) || global.global) ? [ promiseMiddleware({promiseTypeSuffixes}), ] : [ promiseMiddleware({promiseTypeSuffixes}), diff --git a/common/reducer.js b/common/reducer.js index 457a67f..6edb89f 100644 --- a/common/reducer.js +++ b/common/reducer.js @@ -1,6 +1,7 @@ import combineReducers from 'redux/lib/combineReducers'; -import arsenal from './ui/Motions/reducer'; export default combineReducers({ - arsenal, + application(state = "Initial State") { + return state; + }, }); diff --git a/common/routes.js b/common/routes.js index 401ece3..cb27dae 100644 --- a/common/routes.js +++ b/common/routes.js @@ -1,17 +1,9 @@ import React from 'react'; -import {Router, Route, IndexRoute} from 'react-router'; +import {Router, Route/*, IndexRoute*/} from 'react-router'; import { Application, - Motions, - MotionsList, - Motion, } from './ui'; export default history => - - - - - - + ; diff --git a/common/styles/helpers.css b/common/styles/helpers.css index 5c9a62e..161fbdb 100644 --- a/common/styles/helpers.css +++ b/common/styles/helpers.css @@ -4,3 +4,10 @@ padding-left: 0; list-style: none; } + +.motion-container { + position: relative; + & > content { + position: absolute auto; + } +} diff --git a/common/ui/Application/index.jsx b/common/ui/Application/index.jsx index 4476d71..30c81d4 100644 --- a/common/ui/Application/index.jsx +++ b/common/ui/Application/index.jsx @@ -1,23 +1,27 @@ import React, {Component} from 'react'; +import Helmet from 'react-helmet'; import Link from 'react-router/lib/Link'; -import styles from './styles.css'; +import './styles.css'; + +const defaultHelmet = { + defaultTitle: '巧思', + titleTemplate: '%s - 巧思', + meta: [ + {"charset": "UTF-8"}, + {"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() { - return
+ return
+
-
{this.props.children}
diff --git a/common/ui/Application/styles.css b/common/ui/Application/styles.css index 740203b..cb03448 100644 --- a/common/ui/Application/styles.css +++ b/common/ui/Application/styles.css @@ -1,34 +1 @@ @import 'common/styles/globals.css'; - -:root { - --cover-image: images/intro-background-full.png; -} - -.container { - /* position: absolute 0 0; */ - padding: 1rem; - /* width: width(var(--cover-image)); */ - /* height: height(var(--cover-image)); */ - /* background: resolve(var(--cover-image)); */ - /* background-size: size(var(--cover-image)); */ - /*composes: absolute-full from '../../styles/helpers.css';*/ -} - -.navigation { - clear: fix; - padding-bottom: 1rem; - border-bottom: 1px solid gray; - - & :any-link { - color: gray; - text-decoration: none; - } - - & .active { - font-weight: bold; - } - - & a:not(:last-child) { - margin-right: 1rem; - } -} diff --git a/common/ui/Motion/index.jsx b/common/ui/Motion/index.jsx deleted file mode 100644 index 8d43a7b..0000000 --- a/common/ui/Motion/index.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, {Component} from 'react'; -import {Link} from 'react-router'; - -export default class Motion extends Component { - render() { - return -
-

Motion Descriptions

- 返回 -
-
; - } -} diff --git a/common/ui/Motions/List.jsx b/common/ui/Motions/List.jsx deleted file mode 100644 index 8cc2492..0000000 --- a/common/ui/Motions/List.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, {Component} from 'react'; -import {Link} from 'react-router'; - -const motionList = [ - {name: 'basic-scrolling', text: '基础滚动触发动效'} -]; - -export default class MotionsList extends Component { - render() { - return -
-

Please select a motion to preview:

-
-
    { - motionList.map(motion =>
  • - {motion.text} -
  • ) - }
-
; - } -} diff --git a/common/ui/Motions/action.js b/common/ui/Motions/action.js deleted file mode 100644 index cafacab..0000000 --- a/common/ui/Motions/action.js +++ /dev/null @@ -1,24 +0,0 @@ -import fetch from 'fetch'; - -export const endpoint = 'http://api.football-data.org/v1'; - -export const opts = { - headers: { - 'X-Response-Control': 'full', - 'X-Auth-Token': '6f71dbba537b44139bedc0edc89d8920', - } -}; - -export const FETCH_ARSENAL_TEAM = '读取-阿森纳俱乐部'; -export const fetchArsenalTeam = () => ({ - type: FETCH_ARSENAL_TEAM, - payload: fetch(`${endpoint}/teams/57`, opts) - .then(response => response.json()) -}); - -export const FETCH_ARSENAL_PLAYERS = '获取-阿森纳运动员'; -export const fetchArsenalPlayers = () => ({ - type: FETCH_ARSENAL_PLAYERS, - payload: fetch(`${endpoint}/teams/57/players`, opts) - .then(response => response.json()) -}); diff --git a/common/ui/Motions/index.jsx b/common/ui/Motions/index.jsx deleted file mode 100644 index 652fdb2..0000000 --- a/common/ui/Motions/index.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, {Component} from 'react'; -import {VelocityTransitionGroup} from 'velocity-react'; -import styles from './styles.css'; - -export default class Motions extends Component { - render() { - /** - * NOTE: the OR condition is only for SSR bootup process, because on - * server-side there's only a basic location object can be use which - * doesn't maintain inner state object. - */ - const {enter, leave} = this.props.location.state || { - enter: 'Right', leave: 'Left' - }; - - return - {React.cloneElement(this.props.children, {key: this.props.location.key})} - ; - } -} diff --git a/common/ui/Motions/reducer.js b/common/ui/Motions/reducer.js deleted file mode 100644 index 5026690..0000000 --- a/common/ui/Motions/reducer.js +++ /dev/null @@ -1,37 +0,0 @@ -import handleActions from 'redux-actions/lib/handleActions'; -import { - FETCH_ARSENAL_TEAM, - FETCH_ARSENAL_PLAYERS -} from './action.js'; - -export default handleActions({ - [`${FETCH_ARSENAL_TEAM}_读取`](state) { - return {...state, loading: true}; - }, - - [`${FETCH_ARSENAL_TEAM}_成功`](state, {payload}) { - const {players, _links} = payload; - return {...state, loading: false, players, meta: _links}; - }, - - [`${FETCH_ARSENAL_TEAM}_失败`](state, {error}) { - return {...state, loading: false, error}; - }, - - [`${FETCH_ARSENAL_PLAYERS}_读取`](state) { - return {...state, loading: true}; - }, - - [`${FETCH_ARSENAL_PLAYERS}_成功`](state, {payload}) { - return {...state, loading: false, ...payload}; - }, - - [`${FETCH_ARSENAL_PLAYERS}_失败`](state, {error}) { - return {...state, loading: false, error}; - }, -}, { - loading: false, - meta: {}, - team: {}, - players: [] -}); diff --git a/common/ui/Motions/styles.css b/common/ui/Motions/styles.css deleted file mode 100644 index 450ffe4..0000000 --- a/common/ui/Motions/styles.css +++ /dev/null @@ -1,13 +0,0 @@ -.motions { - position: relative; - padding-top: 1rem; - composes: reset-list from '../../styles/helpers.css'; -} - -.motions content { - position: absolute; - border-radius: .1rem; - box-shadow: 0 0 .1rem 0 color(lightgray); - padding: 0 1rem 1rem; - width: 100%; -} diff --git a/common/ui/index.js b/common/ui/index.js index b726b42..a9b5a99 100644 --- a/common/ui/index.js +++ b/common/ui/index.js @@ -1,4 +1 @@ export Application from './Application'; -export Motions from './Motions'; -export MotionsList from './Motions/List'; -export Motion from './Motion'; diff --git a/config/webpack.babel.js b/config/webpack.babel.js index ac9eda4..01330a7 100644 --- a/config/webpack.babel.js +++ b/config/webpack.babel.js @@ -10,7 +10,7 @@ const defaults = { entry: { client: [path.resolve('client/index.js')], vendor: [ - 'babel-polyfill', 'isomorphic-fetch', + 'babel-polyfill', 'isomorphic-fetch', 'device.js', 'velocity-animate', 'velocity-animate/velocity.ui', 'jquery', 'react', 'react-dom', 'react-router', 'redux', 'react-redux', ], @@ -23,7 +23,14 @@ const defaults = { chunkFilename: isProduction ? '[id].chunk.js?[hash]' : '[id].chunk.js', }, resolve: { - alias: {'common': path.resolve('common'), 'fetch': 'isomorphic-fetch'}, + 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'], }, @@ -42,12 +49,19 @@ const defaults = { 'css?modules&localIdentName=[name]_[local]-[hash:base64:4]', 'postcss', ], - }, {test: require.resolve('jquery'), loader: 'expose?$!expose?jQuery'}], + }, { + 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'}), + new webpack.ProvidePlugin({ + 'Promise': 'bluebird', + 'IScroll': 'iscroll/build/iscroll-probe' + }), new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), ], postcss(webpack) { @@ -57,9 +71,7 @@ const defaults = { require('postcss-hexrgba'), require('postcss-position'), require('postcss-responsive-type'), - require('postcss-cssnext')({ - browsers: '> 1%, last 2 versions, not ie <= 8', - }), + require('postcss-cssnext')({browsers: '> 1%, last 2 versions'}), require('postcss-assets')({ cachebuster: true, basePath: 'public/', @@ -82,7 +94,7 @@ export default { ], vendor: isProduction ? defaults.entry.vendor : [ ...defaults.entry.vendor, 'redux-logger', - ] + ], }, module: isProduction ? defaults.module : { loaders: defaults.module.loaders, diff --git a/package.json b/package.json index c3e4e2b..a32169d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "clean": "rimraf output", "slate": "rimraf node_modules && npm install", - "debug": "cross-env NODE_ENV=debug devtool server --console --watch", + "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,54 +44,56 @@ "lint" ], "dependencies": { - "bluebird": "^3.3.5", + "bluebird": "^3.4.0", + "device.js": "github:matthewhudson/device.js", + "iscroll": "^5.2.0", "isomorphic-fetch": "^2.2.1", - "jquery": "^2.2.3", + "jquery": "^2.2.4", "koa": "^2.0.0", "koa-compress": "^2.0.0", "koa-router": "^7.0.1", "koa-static": "^3.0.0", - "lodash": "^4.11.1", - "mysql2": "^1.0.0-rc.1", - "react": "^15.0.1", - "react-dom": "^15.0.1", + "lodash": "^4.13.1", + "react": "^15.1.0", + "react-dom": "^15.1.0", + "react-helmet": "^3.1.0", "react-redux": "^4.4.5", - "react-router": "^2.3.0", + "react-router": "^2.4.1", "redux": "^3.5.2", "redux-actions": "^0.9.1", "redux-promise-middleware": "^3.0.0", - "sequelize": "^3.21.0", - "velocity-animate": "^1.2.3", - "velocity-react": "^1.1.5" + "scrollmagic": "^2.0.5", + "velocity-animate": "^1.2.3" }, "devDependencies": { - "ava": "^0.14.0", - "babel-cli": "^6.7.7", - "babel-core": "^6.7.4", + "ava": "^0.15.1", + "babel-cli": "^6.9.0", + "babel-core": "^6.9.1", "babel-eslint": "^6.0.4", "babel-loader": "^6.2.4", - "babel-plugin-add-module-exports": "^0.1.4", + "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-module-alias": "^1.3.0", - "babel-polyfill": "^6.7.4", - "babel-preset-es2015": "^6.6.0", + "babel-plugin-transform-runtime": "^6.7.5", + "babel-polyfill": "^6.9.1", + "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.5.0", "babel-preset-react-optimize": "^1.0.1", "babel-preset-stage-0": "^6.5.0", - "babel-register": "^6.7.2", - "chokidar": "^1.4.3", + "babel-register": "^6.9.0", + "chokidar": "^1.5.1", "coveralls": "^2.11.9", - "cross-env": "^1.0.7", + "cross-env": "^1.0.8", "css-loader": "^0.23.1", - "css-modules-require-hook": "^4.0.0", - "cssnano": "^3.5.2", - "devtool": "^1.9.1", - "enzyme": "^2.2.0", - "eslint": "^2.8.0", + "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.0.1", + "eslint-plugin-react": "^5.1.1", "expose-loader": "^0.7.1", "file-loader": "^0.8.5", - "jsdom": "^8.4.0", + "jsdom": "^9.2.1", "koa-logger": "^2.0.0", "laggard": "^0.1.0", "localtunnel": "^1.8.1", @@ -99,21 +101,21 @@ "nyc": "^6.4.0", "postcss-advanced-variables": "^1.2.2", "postcss-assets": "^4.1.0", - "postcss-browser-reporter": "^0.4.0", + "postcss-browser-reporter": "^0.5.0", "postcss-clearfix": "^1.0.0", "postcss-cssnext": "^2.5.2", "postcss-hexrgba": "^0.2.0", "postcss-import": "^8.1.0", - "postcss-loader": "^0.8.2", + "postcss-loader": "^0.9.1", "postcss-position": "^0.4.0", "postcss-responsive-type": "^0.3.3", - "pre-commit": "^1.1.2", - "react-addons-test-utils": "^15.0.1", + "pre-commit": "^1.1.3", + "react-addons-test-utils": "^15.1.0", "redux-logger": "^2.6.1", "rimraf": "^2.5.2", "style-loader": "^0.13.1", "url-loader": "^0.5.7", - "webpack": "^1.13.0", + "webpack": "^1.13.1", "webpack-dev-middleware": "^1.6.1", "webpack-hot-middleware": "^2.10.0" } diff --git a/server/modules/server-side-render/match-async-prefetch.js b/server/modules/server-side-render/match-async-prefetch.js index c06f9a4..66cd64f 100644 --- a/server/modules/server-side-render/match-async-prefetch.js +++ b/server/modules/server-side-render/match-async-prefetch.js @@ -6,6 +6,7 @@ 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'; export default function matchAsyncPrefetch(context, options) { @@ -41,6 +42,7 @@ export default function matchAsyncPrefetch(context, options) { ); + options.head = Helmet.rewind(); context.status = 200; context.body = ssrTemplate(options); })); diff --git a/server/modules/server-side-render/template.js b/server/modules/server-side-render/template.js index b6c861f..2abc6cc 100644 --- a/server/modules/server-side-render/template.js +++ b/server/modules/server-side-render/template.js @@ -2,23 +2,16 @@ * Render whatever passed in to plain HTML text as response body. * * @param {Object} [options] - * @param {String} [options.charset='UTF-8'] - * @param {String} [options.meta] - Any sorts of meta tags provided to render - * in ``. - * @param {String} [options.content=''] - Usually the text from any server-side - * rendering function such as `ReactDOMServer.renderToString()`. * @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} */ export default function ssrTemplate(options) { const context = Object.assign({}, { - charset: `UTF-8`, - meta: ` - - - `, - compact: options.env === 'production' + compact: options.env === 'production', + body: '' }, options); return context.compact @@ -30,12 +23,11 @@ const renderHTML = context => ` - - ${context.name} - ${context.meta} + ${context.head.title} + ${context.head.meta.toString()} -
${context.content}
+
${context.body}