Skip to content

Commit

Permalink
[docs] Only server-side render the popular languages
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari committed Aug 31, 2019
1 parent 98f2c7c commit 125686e
Show file tree
Hide file tree
Showing 32 changed files with 307 additions and 340 deletions.
5 changes: 5 additions & 0 deletions docs/cdn/_redirects
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/fr/* /:splat 200
/de/* /:splat 200
/ja/* /:splat 200
/aa/* /:splat 200

# v1
https://v1-5-0.material-ui.com/* https://v1.material-ui.com/:splat 301!

Expand Down
16 changes: 8 additions & 8 deletions docs/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const withTypescript = require('@zeit/next-typescript');
const pkg = require('../package.json');
const { findPages } = require('./src/modules/utils/find');
const { LANGUAGES } = require('./src/modules/constants');
const { LANGUAGES_SSR } = require('./src/modules/constants');

const workspaceRoot = path.join(__dirname, '../');

Expand Down Expand Up @@ -140,13 +140,13 @@ module.exports = withTypescript({
}

// We want to speed-up the build of pull requests.
if (process.env.PULL_REQUEST === 'true') {
traverse(pages, 'en');
} else {
LANGUAGES.forEach(userLanguage => {
traverse(pages, userLanguage);
});
}
// if (process.env.PULL_REQUEST === 'true') {
// traverse(pages, 'en');
// } else {
LANGUAGES_SSR.forEach(userLanguage => {
traverse(pages, userLanguage);
});
// }

return map;
},
Expand Down
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build-sw": "node ./scripts/buildServiceWorker.js",
"dev": "rimraf ./node_modules/.cache/babel-loader && cross-env NODE_ENV=development node src/server.js",
"deploy": "git push material-ui-docs master:latest",
"export": "rimraf docs/export && next export -o export && yarn build-sw && cpy cdn/. export",
"export": "rimraf docs/export && next export --threads=3 --concurrency=5 -o export && yarn build-sw && cpy cdn/. export",
"icons": "rimraf static/icons/* && node ./scripts/buildIcons.js",
"start": "next start",
"typescript": "tslint -p tsconfig.json \"src/pages/**/*.{ts,tsx}\"",
Expand Down
299 changes: 252 additions & 47 deletions docs/pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,202 @@
/* eslint-disable no-underscore-dangle */
import 'docs/src/modules/components/bootstrap';
// --- Post bootstrap -----
import React from 'react';
import App, { Container } from 'next/app';
import find from 'lodash/find';
import { Provider as ReduxProvider } from 'react-redux';
import { Provider as ReduxProvider, useDispatch, useSelector } from 'react-redux';
import { loadCSS } from 'fg-loadcss/src/loadCSS';
import NextHead from 'next/head';
import PropTypes from 'prop-types';
import acceptLanguage from 'accept-language';
import { create } from 'jss';
import rtl from 'jss-rtl';
import { Router as Router2, useRouter } from 'next/router';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import pages from 'docs/src/pages';
import AppWrapper from 'docs/src/modules/components/AppWrapper';
import initRedux from 'docs/src/modules/redux/initRedux';
import { loadCSS } from 'fg-loadcss/src/loadCSS';
import PageContext from 'docs/src/modules/components/PageContext';
import GoogleAnalytics from 'docs/src/modules/components/GoogleAnalytics';
import loadScript from 'docs/src/modules/utils/loadScript';
import NextHead from 'next/head';
import { ThemeProvider } from 'docs/src/modules/components/ThemeContext';
import { pathnameToLanguage, getCookie } from 'docs/src/modules/utils/helpers';
import { ACTION_TYPES, CODE_VARIANTS } from 'docs/src/modules/constants';

// Configure JSS
const jss = create({
plugins: [...jssPreset().plugins, rtl()],
insertionPoint: process.browser ? document.querySelector('#insertion-point-jss') : null,
});

function useFirstRender() {
const firstRenderRef = React.useRef(true);
React.useEffect(() => {
firstRenderRef.current = false;
}, []);

return firstRenderRef.current;
}

acceptLanguage.languages(['en', 'zh']);

function loadCrowdin() {
window._jipt = [];
window._jipt.push(['project', 'material-ui-docs']);
loadScript('https://cdn.crowdin.com/jipt/jipt.js', document.querySelector('head'));
}

function LanguageNegotiation() {
const dispatch = useDispatch();
const router = useRouter();
const userLanguage = useSelector(state => state.options.userLanguage);

React.useEffect(() => {
if (userLanguage === 'aa') {
loadCrowdin();
}
}, [userLanguage]);

React.useEffect(() => {
const { userLanguage: userLanguageUrl, canonical } = pathnameToLanguage(
Router2._rewriteUrlForNextExport(router.asPath),
);
const preferedLanguage =
getCookie('userLanguage') !== 'noDefault' && userLanguage === 'en'
? acceptLanguage.get(navigator.language)
: userLanguage;

if (preferedLanguage !== userLanguage) {
window.location = preferedLanguage === 'en' ? canonical : `/${preferedLanguage}${canonical}`;
} else if (userLanguageUrl !== userLanguage) {
dispatch({ type: ACTION_TYPES.OPTIONS_CHANGE, payload: { userLanguage: userLanguageUrl } });
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps

return null;
}

/**
* Priority: on first render: navigated value, persisted value; otherwise initial value, 'JS'
* @param {string} initialCodeVariant
* @param {(nextCodeVariant: string) => void} codeVariantChanged
* @returns {string} - The persisted variant if the initial value is undefined
*/
function usePersistCodeVariant(initialCodeVariant = CODE_VARIANTS.JS, codeVariantChanged) {
const isFirstRender = useFirstRender();

const navigatedCodeVariant = React.useMemo(() => {
const navigatedCodeVariantMatch =
typeof window !== 'undefined' ? window.location.hash.match(/\.(js|tsx)$/) : null;

if (navigatedCodeVariantMatch === null) {
return undefined;
}

return navigatedCodeVariantMatch[1] === 'tsx' ? CODE_VARIANTS.TS : CODE_VARIANTS.JS;
}, []);

const persistedCodeVariant = React.useMemo(() => {
if (typeof window === 'undefined') {
return undefined;
}
return getCookie('codeVariant');
}, []);

/**
* we initialize from navigation or cookies. on subsequent renders the store is the
* truth
*/
const codeVariant =
isFirstRender === true
? navigatedCodeVariant || persistedCodeVariant || initialCodeVariant
: initialCodeVariant;

React.useEffect(() => {
if (codeVariant !== initialCodeVariant) {
codeVariantChanged(codeVariant);
}
});

React.useEffect(() => {
document.cookie = `codeVariant=${codeVariant};path=/;max-age=31536000`;
}, [codeVariant]);

return codeVariant;
}

function PersistState() {
const dispatch = useDispatch();
const options = useSelector(state => state.options);

const codeVariant = usePersistCodeVariant(options.codeVariant, nextCodeVariant =>
dispatch({ type: ACTION_TYPES.OPTIONS_CHANGE, payload: { codeVariant: nextCodeVariant } }),
);

React.useEffect(() => {
window.ga('set', 'dimension1', codeVariant);
}, [codeVariant]);

React.useEffect(() => {
window.ga('set', 'dimension2', options.userLanguage);
}, [options.userLanguage]);

return null;
}

// Inspired by
// https://developers.google.com/web/tools/workbox/guides/advanced-recipes#offer_a_page_reload_for_users
function forcePageReload(registration) {
// console.log('already controlled?', Boolean(navigator.serviceWorker.controller));

if (!navigator.serviceWorker.controller) {
// The window client isn't currently controlled so it's a new service
// worker that will activate immediately.
return;
}

// console.log('registration waiting?', Boolean(registration.waiting));
if (registration.waiting) {
// SW is waiting to activate. Can occur if multiple clients open and
// one of the clients is refreshed.
registration.waiting.postMessage('skipWaiting');
return;
}

function listenInstalledStateChange() {
registration.installing.addEventListener('statechange', event => {
// console.log('statechange', event.target.state);
if (event.target.state === 'installed' && registration.waiting) {
// A new service worker is available, inform the user
registration.waiting.postMessage('skipWaiting');
} else if (event.target.state === 'activated') {
// Force the control of the page by the activated service worker.
window.location.reload();
}
});
}

if (registration.installing) {
listenInstalledStateChange();
return;
}

// We are currently controlled so a new SW may be found...
// Add a listener in case a new SW is found,
registration.addEventListener('updatefound', listenInstalledStateChange);
}

async function registerServiceWorker() {
if (
'serviceWorker' in navigator &&
process.env.NODE_ENV === 'production' &&
window.location.host.indexOf('material-ui.com') <= 0
) {
// register() automatically attempts to refresh the sw.js.
const registration = await navigator.serviceWorker.register('/sw.js');
// Force the page reload for users.
forcePageReload(registration);
}
}

// Add the strict mode back once the number of warnings is manageable.
// We might miss important warnings by keeping the strict mode 🌊🌊🌊.
Expand Down Expand Up @@ -77,57 +262,79 @@ function findActivePage(currentPages, pathname) {
return activePage;
}

class MyApp extends App {
constructor(props) {
super();
this.redux = initRedux(props.pageProps.reduxServerState);
}
function AppWrapper(props) {
const { children, pageProps } = props;

const router = useRouter();
const [redux] = React.useState(() => initRedux(pageProps.reduxServerState));

componentDidMount() {
React.useEffect(() => {
loadDependencies();
}
registerServiceWorker();

render() {
const { Component, pageProps, router } = this.props;

let pathname = router.pathname;
// Add support for leading / in development mode.
if (pathname !== '/') {
// The leading / is only added to support static hosting (resolve /index.html).
// We remove it to normalize the pathname.
// See `_rewriteUrlForNextExport` on Next.js side.
pathname = pathname.replace(/\/$/, '');
}
// console.log(pages, { ...router, pathname })
const activePage = findActivePage(pages, pathname);

let fonts = ['https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'];
if (pathname.match(/onepirate/)) {
fonts = [
'https://fonts.googleapis.com/css?family=Roboto+Condensed:700|Work+Sans:300,400&display=swap',
];
} else if (pathname.match(/blog/)) {
fonts.push('https://fonts.googleapis.com/css?family=Roboto+Slab:300&display=swap');
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
}, []);

return (
<ReactMode>
let pathname = router.pathname;
// Add support for leading / in development mode.
if (pathname !== '/') {
// The leading / is only added to support static hosting (resolve /index.html).
// We remove it to normalize the pathname.
// See `_rewriteUrlForNextExport` on Next.js side.
pathname = pathname.replace(/\/$/, '');
}
// console.log(pages, { ...router, pathname })
const activePage = findActivePage(pages, pathname);

let fonts = ['https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'];
if (pathname.match(/onepirate/)) {
fonts = [
'https://fonts.googleapis.com/css?family=Roboto+Condensed:700|Work+Sans:300,400&display=swap',
];
} else if (pathname.match(/blog/)) {
fonts.push('https://fonts.googleapis.com/css?family=Roboto+Slab:300&display=swap');
}

return (
<ReactMode>
<Container>
<NextHead>
{fonts.map(font => (
<link rel="stylesheet" href={font} key={font} />
))}
</NextHead>
<Container>
<ReduxProvider store={this.redux}>
<PageContext.Provider value={{ activePage, pages }}>
<AppWrapper>
<Component {...pageProps} />
</AppWrapper>
</PageContext.Provider>
</ReduxProvider>
<GoogleAnalytics key={router.route} />
</Container>
</ReactMode>
<ReduxProvider store={redux}>
<PageContext.Provider value={{ activePage, pages }}>
<StylesProvider jss={jss}>
<ThemeProvider>{children}</ThemeProvider>
</StylesProvider>
</PageContext.Provider>
<PersistState />
<LanguageNegotiation />
</ReduxProvider>
</Container>
<GoogleAnalytics key={router.route} />
</ReactMode>
);
}

AppWrapper.propTypes = {
children: PropTypes.node.isRequired,
pageProps: PropTypes.object.isRequired,
};

export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props;

return (
<AppWrapper pageProps={pageProps}>
<Component {...pageProps} />
</AppWrapper>
);
}
}
Expand All @@ -152,5 +359,3 @@ MyApp.getInitialProps = ({ ctx }) => {
pageProps,
};
};

export default MyApp;
Loading

0 comments on commit 125686e

Please sign in to comment.