From 95ebe9f7081a88752c89aaab13ac972477291222 Mon Sep 17 00:00:00 2001 From: Jere Menichelli Date: Tue, 24 Apr 2018 14:39:00 +0300 Subject: [PATCH 1/7] fix: content population on server, hydrate on client --- src/components/Page/Page.jsx | 36 ++++++++++++++++++++++-------------- src/index.jsx | 4 +++- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/components/Page/Page.jsx b/src/components/Page/Page.jsx index bbbc13d7288a..657d7c4c55ca 100644 --- a/src/components/Page/Page.jsx +++ b/src/components/Page/Page.jsx @@ -11,8 +11,28 @@ import Gitter from '../Gitter/Gitter'; import './Page.scss'; class Page extends React.Component { - state = { - content: this.props.content instanceof Promise ? 'Loading...' : this.props.content + constructor(props) { + super(props); + + const { content } = props; + + this.state = { + content: content instanceof Promise ? 'Loading...' : content.default || content + }; + } + + componentDidMount() { + const { content } = this.props; + + if (content instanceof Promise) { + content + .then(module => this.setState({ + content: module.default || module + })) + .catch(error => this.setState({ + content: 'Error loading content.' + })); + } } render() { @@ -66,18 +86,6 @@ class Page extends React.Component { ); } - - componentDidMount() { - if ( this.props.content instanceof Promise ) { - this.props.content - .then(module => this.setState({ - content: module.default || module - })) - .catch(error => this.setState({ - content: 'Error loading content.' - })); - } - } } export default Page; diff --git a/src/index.jsx b/src/index.jsx index fc205186f3a7..e835e82955cf 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -6,9 +6,11 @@ import Site from './components/Site/Site'; // TODO: Re-integrate // Consider `react-g-analytics` package +const render = process.NODE_ENV === 'production' ? ReactDOM.hydrate : ReactDOM.render; + // Client Side Rendering if ( window.document !== undefined ) { - ReactDOM.render(( + render(( Date: Wed, 2 May 2018 00:18:02 -0400 Subject: [PATCH 2/7] chore(rebuild): preload entry page to avoid flash during retrieval This change delays the dynamic part of app from mounting until the first page's content has loaded. This prevents the static content from being stripped from the DOM until dynamic bundle is there to replace it. Once we migrate to `remark-react`, the elements won't even be re-mounted as React's diffing algorithm will be used. All other pages are still loaded using dynamic `import()`s. --- src/index.jsx | 43 +++++++++++++++++++++++--------- src/utilities/content-utils.js | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 src/utilities/content-utils.js diff --git a/src/index.jsx b/src/index.jsx index e835e82955cf..abdb860d91cd 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,8 +1,17 @@ +// Import External Dependencies import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Route } from 'react-router-dom'; + +// Import Components import Site from './components/Site/Site'; +// Import Utilities +import { FindInContent } from './utilities/content-utils'; + +// Import Content Tree +import Content from './_content.json'; + // TODO: Re-integrate // Consider `react-g-analytics` package @@ -10,15 +19,27 @@ const render = process.NODE_ENV === 'production' ? ReactDOM.hydrate : ReactDOM.r // Client Side Rendering if ( window.document !== undefined ) { - render(( - - ( - import(`./content/${path}`) } /> - )} /> - - ), document.getElementById('root')); + let { pathname } = window.location; + let trimmed = pathname.replace(/(.+)\/$/, '$1'); + let entryPage = FindInContent(Content, item => item.url === trimmed); + let entryPath = entryPage.path.replace('src/content/', ''); + + import(`./content/${entryPath}`).then(entryModule => { + render(( + + ( + { + if ( path === entryPath ) { + return entryModule.default || entryModule; + + } else return import(`./content/${path}`); + }} /> + )} /> + + ), document.getElementById('root')); + }); } diff --git a/src/utilities/content-utils.js b/src/utilities/content-utils.js new file mode 100644 index 000000000000..bf44c2b30eee --- /dev/null +++ b/src/utilities/content-utils.js @@ -0,0 +1,45 @@ +/** + * Walk the given tree of content + * + * @param {object} tree - ... + * @param {function} callback - ... + */ +export const WalkContent = (tree, callback) => { + callback(tree); + + if ( tree.children ) { + tree.children.forEach(child => { + WalkContent(child, callback); + }); + } +}; + +/** + * Deep flatten the given `tree`s child nodes + * + * @param {object} tree - ... + * @return {array} - ... + */ +export const FlattenContent = tree => { + if ( tree.children ) { + return tree.children.reduce((flat, item) => { + return flat.concat( + Array.isArray(item.children) ? FlattenContent(item) : item + ); + }, []); + + } else return []; +}; + +/** + * Find an item within the given `tree` + * + * @param {object} tree - ... + * @param {function} test - ... + * @return {object} - ... + */ +export const FindInContent = (tree, test) => { + let list = FlattenContent(tree); + + return list.find(test); +}; From 569862bc5368ed2d38c63dd2b7c63c5ea2aca949 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sat, 5 May 2018 10:36:36 -0400 Subject: [PATCH 3/7] refactor: move sorting/filtering to top-level using new plugin options --- package.json | 2 +- src/components/Site/Site.jsx | 21 +-------------------- webpack.common.js | 12 +++++++++++- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 56ba793821cd..be2ecf9d3efa 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "clean-webpack-plugin": "^0.1.17", "copy-webpack-plugin": "^4.3.0", "css-loader": "^0.28.5", - "directory-tree-webpack-plugin": "^0.2.0", + "directory-tree-webpack-plugin": "^0.3.1", "duplexer": "^0.1.1", "eslint": "4.5.0", "eslint-loader": "^1.9.0", diff --git a/src/components/Site/Site.jsx b/src/components/Site/Site.jsx index ea37bec2fe37..9e4d53cec625 100644 --- a/src/components/Site/Site.jsx +++ b/src/components/Site/Site.jsx @@ -20,27 +20,8 @@ import '../../styles/index'; import '../../styles/icon.font.js'; import './Site.scss'; -// Load & Clean Up JSON Representation of `src/content` -// TODO: Consider moving all or parts of this cleaning to a `DirectoryTreePlugin` option(s) +// Load Content Tree import Content from '../../_content.json'; -Content.children = Content.children - .filter(item => item.name !== 'images') - .map(item => { - if ( item.type === 'directory' ) { - return { - ...item, - children: item.children.sort((a, b) => { - let group1 = (a.group || '').toLowerCase(); - let group2 = (b.group || '').toLowerCase(); - - if (group1 < group2) return -1; - if (group1 > group2) return 1; - return a.sort - b.sort; - }) - }; - - } else return item; - }); class Site extends React.Component { state = { diff --git a/webpack.common.js b/webpack.common.js index 10e06cc6bd2a..a1bf3263581e 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -109,7 +109,17 @@ module.exports = (env = {}) => ({ dir: 'src/content', path: 'src/_content.json', extensions: /\.md/, - enhance: treePluginEnhacer + enhance: treePluginEnhacer, + filter: item => item.name !== 'images', + sort: (a, b) => { + let group1 = (a.group || '').toLowerCase(); + let group2 = (b.group || '').toLowerCase(); + + if (group1 < group2) return -1; + if (group1 > group2) return 1; + if (a.sort && b.sort) return a.sort - b.sort; + else return 0; + } }) ], stats: { From 155a966a0d03366192c0440a82cc5b9a2f043984 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sat, 5 May 2018 10:37:05 -0400 Subject: [PATCH 4/7] chore: remove obsolete todo --- src/server.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/server.jsx b/src/server.jsx index 582cc85ba114..035baa9e8b1d 100644 --- a/src/server.jsx +++ b/src/server.jsx @@ -16,11 +16,7 @@ const bundles = [ '/index.bundle.js' ]; -// Export method for `StaticSiteGeneratorPlugin` -// CONSIDER: How high can we mount `Site` into the DOM hierarchy? If -// we could start at ``, much of this could be moved to the `Site` -// component itself (allowing easier utilization of page data for title, -// description, etc). +// Export method for `SSGPlugin` export default locals => { let { assets } = locals.webpackStats.compilation; From 22200b7d7d605fc3ab3d739bdc1f0e7c6443dbdc Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sat, 5 May 2018 10:38:32 -0400 Subject: [PATCH 5/7] refactor(site): finish content-utils thus simplifying the `Site` component --- src/components/Site/Site.jsx | 44 +++++----------------------------- src/utilities/content-utils.js | 38 +++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/src/components/Site/Site.jsx b/src/components/Site/Site.jsx index 9e4d53cec625..290e5bf6509f 100644 --- a/src/components/Site/Site.jsx +++ b/src/components/Site/Site.jsx @@ -3,6 +3,9 @@ import React from 'react'; import { Switch, Route } from 'react-router-dom'; import { hot as Hot } from 'react-hot-loader'; +// Import Utilities +import { ExtractPages, ExtractSections } from '../../utilities/content-utils'; + // Import Components import NotificationBar from '../NotificationBar/NotificationBar'; import Navigation from '../Navigation/Navigation'; @@ -31,8 +34,9 @@ class Site extends React.Component { render() { let { location } = this.props; let { mobileSidebarOpen } = this.state; - let sections = this._sections; + let sections = ExtractSections(Content); let section = sections.find(({ url }) => location.pathname.startsWith(url)); + let pages = ExtractPages(Content); return (
@@ -70,7 +74,7 @@ class Site extends React.Component { render={ props => ( - { this._pages.map(page => ( + { pages.map(page => ( { - return array.reduce((flat, item) => { - return flat.concat( - Array.isArray(item.children) ? this._flatten(item.children) : item - ); - }, []); - } - /** * Strip any non-applicable properties * @@ -154,28 +144,6 @@ class Site extends React.Component { children: children ? this._strip(children) : [] })); } - - /** - * Get top-level sections - * - * @return {array} - ... - */ - get _sections() { - return Content.children.filter(item => ( - item.type === 'directory' - )); - } - - /** - * Get all markdown pages - * - * @return {array} - ... - */ - get _pages() { - return this._flatten(Content.children).filter(item => { - return item.extension === '.md'; - }); - } } export default Hot(module)(Site); diff --git a/src/utilities/content-utils.js b/src/utilities/content-utils.js index bf44c2b30eee..f411010875cc 100644 --- a/src/utilities/content-utils.js +++ b/src/utilities/content-utils.js @@ -1,8 +1,8 @@ /** * Walk the given tree of content * - * @param {object} tree - ... - * @param {function} callback - ... + * @param {object} tree - Any node in the content tree + * @param {function} callback - Run on every descendant as well as the given `tree` */ export const WalkContent = (tree, callback) => { callback(tree); @@ -17,8 +17,8 @@ export const WalkContent = (tree, callback) => { /** * Deep flatten the given `tree`s child nodes * - * @param {object} tree - ... - * @return {array} - ... + * @param {object} tree - Any node in the content tree + * @return {array} - A flattened list of leaf node descendants */ export const FlattenContent = tree => { if ( tree.children ) { @@ -34,12 +34,36 @@ export const FlattenContent = tree => { /** * Find an item within the given `tree` * - * @param {object} tree - ... - * @param {function} test - ... - * @return {object} - ... + * @param {object} tree - Any node in the content tree + * @param {function} test - A callback to find any leaf node in the given `tree` + * @return {object} - The first leaf node that passes the `test` */ export const FindInContent = (tree, test) => { let list = FlattenContent(tree); return list.find(test); }; + +/** + * Get top-level sections + * + * @param {object} tree - Any node in the content tree + * @return {array} - Immediate children of the given `tree` that are directories + */ +export const ExtractSections = tree => { + return tree.children.filter(item => ( + item.type === 'directory' + )); +}; + +/** + * Get all markdown pages + * + * @param {object} tree - Any node in the content tree + * @return {array} - All markdown descendants of the given `tree` + */ +export const ExtractPages = tree => { + return FlattenContent(tree).filter(item => { + return item.extension === '.md'; + }); +}; From c3ed7358f24d78636dfd3be548149dc818b710ba Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sat, 5 May 2018 10:38:56 -0400 Subject: [PATCH 6/7] refactor(splash): rename variable for clarity --- src/components/Splash/Splash.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Splash/Splash.jsx b/src/components/Splash/Splash.jsx index fcdeefbba718..a3720b571c0c 100644 --- a/src/components/Splash/Splash.jsx +++ b/src/components/Splash/Splash.jsx @@ -7,8 +7,8 @@ import SplashViz from '../SplashViz/SplashViz'; import Markdown from '../Markdown/Markdown'; import Support from '../Support/Support'; -// Import Content -import Content from '../../content/index.md'; +// Import Demo Content +import SplashContent from '../../content/index.md'; // Load Styling import './Splash.scss'; @@ -21,7 +21,7 @@ const Splash = () => (
From 73808896dea13dce5c217f99eb49a99b59462311 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sun, 6 May 2018 19:17:50 -0400 Subject: [PATCH 7/7] chore(rebuild): address minor feedback --- src/utilities/content-utils.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/utilities/content-utils.js b/src/utilities/content-utils.js index f411010875cc..2bae423d4d22 100644 --- a/src/utilities/content-utils.js +++ b/src/utilities/content-utils.js @@ -51,9 +51,7 @@ export const FindInContent = (tree, test) => { * @return {array} - Immediate children of the given `tree` that are directories */ export const ExtractSections = tree => { - return tree.children.filter(item => ( - item.type === 'directory' - )); + return tree.children.filter(item => item.type === 'directory'); }; /** @@ -63,7 +61,5 @@ export const ExtractSections = tree => { * @return {array} - All markdown descendants of the given `tree` */ export const ExtractPages = tree => { - return FlattenContent(tree).filter(item => { - return item.extension === '.md'; - }); + return FlattenContent(tree).filter(item => item.extension === '.md'); };