site: SSR for official website (#3903)

* site: ssr for Layout

* site: SSR for site

* fix: ssr for Anchor

* chore: add ssr test

* chore: update deploy script

* site: udpate detail

* site: revert animation for site

* site: fix loading animation
This commit is contained in:
Benjy Cui 2016-11-21 14:39:15 +08:00 committed by GitHub
parent 34f353deb0
commit 4ee2b9d930
15 changed files with 100 additions and 70 deletions

View File

@ -87,6 +87,10 @@ class AnchorHelper {
getCurrentAnchor(bounds = 5) { getCurrentAnchor(bounds = 5) {
let activeAnchor = ''; let activeAnchor = '';
if (typeof document === 'undefined') {
return activeAnchor;
}
this.links.forEach(section => { this.links.forEach(section => {
const target = document.getElementById(section.substring(1)); const target = document.getElementById(section.substring(1));
if (target) { if (target) {

View File

@ -79,6 +79,10 @@
"@types/react": "~0.14.41", "@types/react": "~0.14.41",
"@types/react-dom": "~0.14.18", "@types/react-dom": "~0.14.18",
"antd-tools": "0.14.2", "antd-tools": "0.14.2",
"babel-cli": "^6.18.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"babel-eslint": "^7.1.0", "babel-eslint": "^7.1.0",
"babel-jest": "^17.0.0", "babel-jest": "^17.0.0",
"babel-plugin-import": "^1.0.0", "babel-plugin-import": "^1.0.0",
@ -131,8 +135,15 @@
"typescript-babel-jest": "^0.1.5", "typescript-babel-jest": "^0.1.5",
"values.js": "^1.0.3" "values.js": "^1.0.3"
}, },
"babel": {
"presets": [
"es2015",
"react",
"stage-0"
]
},
"scripts": { "scripts": {
"test": "npm run lint && npm run dist && npm run jest", "test": "npm run lint && npm run dist && npm run jest && npm run site",
"lint": "npm run tslint && npm run eslint && npm run demolint && npm run lesshint", "lint": "npm run tslint && npm run eslint && npm run demolint && npm run lesshint",
"tslint": "antd-tools run ts-lint && npm run compile && rm -rf lib", "tslint": "antd-tools run ts-lint && npm run compile && rm -rf lib",
"eslint": "eslint test site scripts ./.eslintrc.js ./webpack.config.js --ext '.js,.jsx,.tsx' --ignore-pattern '!.eslintrc.js'", "eslint": "eslint test site scripts ./.eslintrc.js ./webpack.config.js --ext '.js,.jsx,.tsx' --ignore-pattern '!.eslintrc.js'",
@ -147,8 +158,10 @@
"compile": "antd-tools run compile && node ./tests/dekko/lib.test.js", "compile": "antd-tools run compile && node ./tests/dekko/lib.test.js",
"start": "bisheng start -c ./site/bisheng.config.js --no-livereload", "start": "bisheng start -c ./site/bisheng.config.js --no-livereload",
"site": "bisheng build -c ./site/bisheng.config.js", "babel-site": "babel ./site/theme/template --out-dir ./site/theme/template",
"deploy": "npm run clean && bisheng gh-pages -c ./site/bisheng.config.js", "clean-site": "rm site/theme/template/**/*.js site/theme/template/*.js",
"site": "npm run babel-site && bisheng build --ssr -c ./site/bisheng.config.js && npm run clean-site",
"deploy": "npm run clean && npm run site && bisheng gh-pages --push-only",
"pub": "antd-tools run update-self && antd-tools run pub", "pub": "antd-tools run update-self && antd-tools run pub",
"prepublish": "antd-tools run guard", "prepublish": "antd-tools run guard",

View File

@ -1,4 +1,4 @@
import appLocaleData from 'react-intl/locale-data/en'; const appLocaleData = require('react-intl/locale-data/en');
module.exports = { module.exports = {
locale: 'en-US', locale: 'en-US',

View File

@ -101,9 +101,12 @@ div.main-container {
animation: loadTween 2s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite; animation: loadTween 2s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
} }
} }
#react-content:empty + .ant-site-loading { #react-content[hidden] {
display: none;
+ .ant-site-loading {
opacity: 1; opacity: 1;
} }
}
#loading-text { #loading-text {
font-family: lato,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif; font-family: lato,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif;

View File

@ -1 +1,2 @@
import 'react-github-button/assets/style.css';
import './index.less'; import './index.less';

View File

@ -24,7 +24,9 @@
</script> </script>
</head> </head>
<body> <body>
<div id="react-content"></div> <div id="react-content" hidden>
{{ content | safe }}
</div>
<div class="ant-site-loading" id="ant-site-loading"> <div class="ant-site-loading" id="ant-site-loading">
<img src='https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg'/> <img src='https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg'/>
<div id="loading-text">One Design Language</div> <div id="loading-text">One Design Language</div>
@ -32,6 +34,7 @@
<script> <script>
(function () { (function () {
var content = document.getElementById('loading-text'); var content = document.getElementById('loading-text');
if (content) {
content.innerHTML = content.innerHTML.split('').map(function (letter, i) { content.innerHTML = content.innerHTML.split('').map(function (letter, i) {
var className; var className;
if (i > 0 && i < content.innerText.length - 1) { if (i > 0 && i < content.innerText.length - 1) {
@ -42,6 +45,7 @@
} }
return '<span class="' + className + '">' + letter + '</span>'; return '<span class="' + className + '">' + letter + '</span>';
}).join(''); }).join('');
}
})(); })();
// Enable Google Analytics // Enable Google Analytics

View File

@ -9,7 +9,7 @@ import config from '../../';
const SubMenu = Menu.SubMenu; const SubMenu = Menu.SubMenu;
function getActiveMenuItem(props) { function getActiveMenuItem(props) {
return props.params.children || props.location.pathname; return props.params.children || props.location.pathname.replace(/^\//, '');
} }
function fileNameToPath(filename) { function fileNameToPath(filename) {
@ -120,8 +120,8 @@ export default class MainContent extends React.Component {
getModuleData(props) { getModuleData(props) {
const pathname = props.location.pathname; const pathname = props.location.pathname;
const moduleName = /^components/.test(pathname) ? const moduleName = /^\/?components/.test(pathname) ?
'components' : pathname.split('/').slice(0, 2).join('/'); 'components' : pathname.split('/').filter(item => item).slice(0, 2).join('/');
const moduleData = moduleName === 'components' || moduleName === 'changelog' || moduleName === 'docs/react' ? const moduleData = moduleName === 'components' || moduleName === 'changelog' || moduleName === 'docs/react' ?
[...props.picked.components, ...props.picked['docs/react'], ...props.picked.changelog] : [...props.picked.components, ...props.picked['docs/react'], ...props.picked.changelog] :
props.picked[moduleName]; props.picked[moduleName];

View File

@ -5,7 +5,7 @@ import * as utils from '../utils';
const locale = utils.isZhCN() ? 'zh-CN' : 'en-US'; const locale = utils.isZhCN() ? 'zh-CN' : 'en-US';
export function collect(nextProps, callback) { export function collect(nextProps, callback) {
const pageData = nextProps.location.pathname === 'changelog' ? const pageData = nextProps.location.pathname.endsWith('changelog') ?
nextProps.data.CHANGELOG : nextProps.pageData; nextProps.data.CHANGELOG : nextProps.pageData;
if (!pageData) { if (!pageData) {
callback(404, nextProps); callback(404, nextProps);

View File

@ -3,7 +3,6 @@ import { Link } from 'react-router';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ScrollElement from 'rc-scroll-anim/lib/ScrollElement'; import ScrollElement from 'rc-scroll-anim/lib/ScrollElement';
import GitHubButton from 'react-github-button'; import GitHubButton from 'react-github-button';
import 'react-github-button/assets/style.css';
import { Icon } from 'antd'; import { Icon } from 'antd';
import QueueAnim from 'rc-queue-anim'; import QueueAnim from 'rc-queue-anim';

View File

@ -6,8 +6,8 @@ import ScrollOverPack from 'rc-scroll-anim/lib/ScrollOverPack';
import { Icon, Button } from 'antd'; import { Icon, Button } from 'antd';
import QueueAnim from 'rc-queue-anim'; import QueueAnim from 'rc-queue-anim';
const clientHeight = document.documentElement.clientHeight;
function onScrollEvent(e) { function onScrollEvent(e) {
const clientHeight = document.documentElement.clientHeight;
const header = document.getElementById('header'); const header = document.getElementById('header');
if (e.pageY >= clientHeight) { if (e.pageY >= clientHeight) {
if (header.className.indexOf('home-nav-bottom') < 0) { if (header.className.indexOf('home-nav-bottom') < 0) {

View File

@ -30,8 +30,10 @@ class Footer extends React.Component {
// //
// 1. // 1.
// 2. // 2.
if (localStorage.getItem('antd@2.0.0-notification-sent') !== 'true' && if (
Date.now() < new Date('2016/10/14').getTime()) { localStorage.getItem('antd@2.0.0-notification-sent') !== 'true' &&
Date.now() < new Date('2016/10/14').getTime()
) {
this.infoNewVersion(); this.infoNewVersion();
} }
} }
@ -65,9 +67,8 @@ class Footer extends React.Component {
} }
render() { render() {
const options = Object.keys(docVersions).map(version => ( const options = Object.keys(docVersions)
<Option value={docVersions[version]} key={version}>{version}</Option> .map(version => <Option value={docVersions[version]} key={version}>{version}</Option>);
));
return ( return (
<footer id="footer"> <footer id="footer">
<ul> <ul>

View File

@ -1,7 +1,6 @@
import React from 'react'; import React, { PropTypes } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import enquire from 'enquire.js';
import classNames from 'classnames'; import classNames from 'classnames';
import { Select, Menu, Row, Col, Icon, Button, Popover } from 'antd'; import { Select, Menu, Row, Col, Icon, Button, Popover } from 'antd';
@ -9,20 +8,18 @@ const Option = Select.Option;
export default class Header extends React.Component { export default class Header extends React.Component {
static contextTypes = { static contextTypes = {
router: React.PropTypes.object.isRequired, router: PropTypes.object.isRequired,
intl: React.PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
} }
constructor(props) { state = {
super(props);
this.state = {
menuMode: 'horizontal', menuMode: 'horizontal',
}; };
}
componentDidMount() { componentDidMount() {
enquire.register('only screen and (min-width: 320px) and (max-width: 940px)', { /* eslint-disable global-require */
require('enquire.js')
.register('only screen and (min-width: 320px) and (max-width: 940px)', {
match: () => { match: () => {
this.setState({ menuMode: 'inline' }); this.setState({ menuMode: 'inline' });
}, },
@ -30,6 +27,7 @@ export default class Header extends React.Component {
this.setState({ menuMode: 'horizontal' }); this.setState({ menuMode: 'horizontal' });
}, },
}); });
/* eslint-enable global-require */
} }
handleSearch = (value) => { handleSearch = (value) => {
@ -51,7 +49,7 @@ export default class Header extends React.Component {
render() { render() {
const { location, picked, isFirstScreen } = this.props; const { location, picked, isFirstScreen } = this.props;
const components = picked.components; const components = picked.components;
const module = location.pathname.replace(/\/$/, '').split('/').slice(0, -1).join('/'); const module = location.pathname.replace(/(^\/|\/$)/g, '').split('/').slice(0, -1).join('/');
let activeMenuItem = module || 'home'; let activeMenuItem = module || 'home';
if (activeMenuItem === 'components' || location.pathname === 'changelog') { if (activeMenuItem === 'components' || location.pathname === 'changelog') {
activeMenuItem = 'docs/react'; activeMenuItem = 'docs/react';

View File

@ -8,12 +8,17 @@ import Footer from './Footer';
import enLocale from '../../en-US'; import enLocale from '../../en-US';
import cnLocale from '../../zh-CN'; import cnLocale from '../../zh-CN';
import * as utils from '../utils'; import * as utils from '../utils';
import '../../static/style';
if (typeof window !== 'undefined') {
/* eslint-disable global-require */
require('../../static/style');
// Expose to iframe // Expose to iframe
window.react = React; window.react = React;
window['react-dom'] = ReactDOM; window['react-dom'] = ReactDOM;
window.antd = require('antd'); window.antd = require('antd');
/* eslint-enable global-require */
}
const appLocale = utils.isZhCN() ? cnLocale : enLocale; const appLocale = utils.isZhCN() ? cnLocale : enLocale;
addLocaleData(appLocale.data); addLocaleData(appLocale.data);
@ -23,12 +28,9 @@ export default class Layout extends React.Component {
router: React.PropTypes.object.isRequired, router: React.PropTypes.object.isRequired,
} }
constructor(props) { state = {
super(props);
this.state = {
isFirstScreen: true, isFirstScreen: true,
}; };
}
componentDidMount() { componentDidMount() {
if (typeof ga !== 'undefined') { if (typeof ga !== 'undefined') {
@ -43,6 +45,7 @@ export default class Layout extends React.Component {
loadingNode.parentNode.removeChild(loadingNode); loadingNode.parentNode.removeChild(loadingNode);
}, 450); }, 450);
} }
document.getElementById('react-content').removeAttribute('hidden');
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -20,6 +20,10 @@ export function getMenuItems(moduleData, locale) {
} }
export function isZhCN() { export function isZhCN() {
if (typeof location === 'undefined') {
// Use English in SSR.
return false;
}
if (location.search.indexOf('locale=zh-CN') > -1) { if (location.search.indexOf('locale=zh-CN') > -1) {
return true; return true;
} }

View File

@ -1,4 +1,4 @@
import appLocaleData from 'react-intl/locale-data/zh'; const appLocaleData = require('react-intl/locale-data/zh');
module.exports = { module.exports = {
locale: 'zh-CN', locale: 'zh-CN',