-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Framework: Introduce AsyncLoad for asynchronous rendering by code splitting #5356
Changes from all commits
8dcf24d
f81431c
ca56219
15baa7a
018eb3e
8c3c472
4a36949
e8f2ee6
c8a338f
fe02a4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import React, { Component, PropTypes } from 'react'; | ||
import omit from 'lodash/omit'; | ||
|
||
export default class AsyncLoad extends Component { | ||
constructor() { | ||
super( ...arguments ); | ||
|
||
this.state = { | ||
component: null | ||
}; | ||
} | ||
|
||
componentWillMount() { | ||
this.props.require( ( component ) => { | ||
this.setState( { component } ); | ||
} ); | ||
} | ||
|
||
render() { | ||
if ( this.state.component ) { | ||
const props = omit( this.props, Object.keys( this.constructor.propTypes ) ); | ||
return <this.state.component { ...props } />; | ||
} | ||
|
||
if ( this.props.placeholder ) { | ||
return this.props.placeholder; | ||
} | ||
|
||
return <div className="async-load" />; | ||
} | ||
} | ||
|
||
AsyncLoad.propTypes = { | ||
require: PropTypes.func.isRequired, | ||
placeholder: PropTypes.node | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.async-load { | ||
background: lighten( $gray, 22% ); | ||
border-radius: 20px; | ||
content: ''; | ||
display: block; | ||
height: 8px; | ||
width: 80px; | ||
margin: 0 16px; | ||
animation: pulse-light 0.8s ease-in-out infinite; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,4 +76,9 @@ | |
display: block; | ||
padding: 0 16px; | ||
width: 237px; | ||
|
||
.async-load { | ||
margin: 16px 0; | ||
width: auto; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,3 +39,8 @@ | |
color: darken( $gray, 10% ); | ||
} | ||
} | ||
|
||
.editor-action-bar .async-load { | ||
max-width: 30%; | ||
margin-top: 6px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import React from 'react'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import CategoryListData from 'components/data/category-list-data'; | ||
import CategoriesTagsAccordion from 'post-editor/editor-categories-tags/accordion'; | ||
import TagListData from 'components/data/tag-list-data'; | ||
|
||
const EditorDrawerCategoriesAndTags = React.createClass( { | ||
|
||
propTypes: { | ||
site: React.PropTypes.object, | ||
post: React.PropTypes.object | ||
}, | ||
|
||
render() { | ||
if ( ! this.props.site ) { | ||
return; | ||
} | ||
|
||
return ( | ||
<CategoryListData siteId={ this.props.site.ID }> | ||
<TagListData siteId={ this.props.site.ID }> | ||
<CategoriesTagsAccordion | ||
site={ this.props.site } | ||
post={ this.props.post } /> | ||
</TagListData> | ||
</CategoryListData> | ||
); | ||
} | ||
} ); | ||
|
||
export default EditorDrawerCategoriesAndTags; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,17 +11,9 @@ import { connect } from 'react-redux'; | |
import Accordion from 'components/accordion'; | ||
import AccordionSection from 'components/accordion/section'; | ||
import Gridicon from 'components/gridicon'; | ||
import CategoriesTagsAccordion from 'post-editor/editor-categories-tags/accordion'; | ||
import CategoryListData from 'components/data/category-list-data'; | ||
import TagListData from 'components/data/tag-list-data'; | ||
import EditorSharingAccordion from 'post-editor/editor-sharing/accordion'; | ||
import AsyncLoad from 'components/async-load'; | ||
import FormTextarea from 'components/forms/form-textarea'; | ||
import PostFormatsData from 'components/data/post-formats-data'; | ||
import PostFormatsAccordion from 'post-editor/editor-post-formats/accordion'; | ||
import Location from 'post-editor/editor-location'; | ||
import Discussion from 'post-editor/editor-discussion'; | ||
import PageParent from 'post-editor/editor-page-parent'; | ||
import SeoAccordion from 'post-editor/editor-seo-accordion'; | ||
import EditorMoreOptionsSlug from 'post-editor/editor-more-options/slug'; | ||
import InfoPopover from 'components/info-popover'; | ||
import PageTemplatesData from 'components/data/page-templates-data'; | ||
|
@@ -39,7 +31,6 @@ import { getEditorPostId } from 'state/ui/editor/selectors'; | |
import { getEditedPostValue } from 'state/posts/selectors'; | ||
import { getPostType } from 'state/post-types/selectors'; | ||
import config from 'config'; | ||
import EditorDrawerFeaturedImage from './featured-image'; | ||
import EditorDrawerTaxonomies from './taxonomies'; | ||
|
||
/** | ||
|
@@ -112,38 +103,27 @@ const EditorDrawer = React.createClass( { | |
}, | ||
|
||
renderTaxonomies: function() { | ||
var element; | ||
|
||
if ( config.isEnabled( 'manage/custom-post-types' ) && | ||
! includes( [ 'post', 'page' ], this.props.type ) ) { | ||
return ( | ||
<EditorDrawerTaxonomies | ||
postTerms={ this.props.post && this.props.post.terms } | ||
/> | ||
); | ||
} | ||
return ( | ||
<EditorDrawerTaxonomies | ||
postTerms={ this.props.post && this.props.post.terms } | ||
/> | ||
); | ||
}, | ||
|
||
renderCategoriesAndTags: function() { | ||
if ( ! this.currentPostTypeSupports( 'tags' ) ) { | ||
return; | ||
} | ||
|
||
element = ( | ||
<CategoriesTagsAccordion | ||
return ( | ||
<AsyncLoad | ||
require={ function( callback ) { | ||
require( [ 'post-editor/editor-drawer/categories-and-tags' ], callback ); | ||
} } | ||
site={ this.props.site } | ||
post={ this.props.post } /> | ||
post={ this.props.post } | ||
/> | ||
); | ||
|
||
if ( this.props.site ) { | ||
element = ( | ||
<CategoryListData siteId={ this.props.site.ID }> | ||
<TagListData siteId={ this.props.site.ID }> | ||
{ element } | ||
</TagListData> | ||
</CategoryListData> | ||
); | ||
} | ||
|
||
return element; | ||
}, | ||
|
||
renderPostFormats: function() { | ||
|
@@ -153,18 +133,22 @@ const EditorDrawer = React.createClass( { | |
} | ||
|
||
return ( | ||
<PostFormatsData siteId={ this.props.site.ID }> | ||
<PostFormatsAccordion | ||
site={ this.props.site } | ||
post={ this.props.post } | ||
className="editor-drawer__accordion" /> | ||
</PostFormatsData> | ||
<AsyncLoad | ||
require={ function( callback ) { | ||
require( [ 'post-editor/editor-drawer/post-formats' ], callback ); | ||
} } | ||
site={ this.props.site } | ||
post={ this.props.post } | ||
/> | ||
); | ||
}, | ||
|
||
renderSharing: function() { | ||
return ( | ||
<EditorSharingAccordion | ||
<AsyncLoad | ||
require={ function( callback ) { | ||
require( [ 'post-editor/editor-sharing/accordion' ], callback ); | ||
} } | ||
site={ this.props.site } | ||
post={ this.props.post } | ||
isNew={ this.props.isNew } /> | ||
|
@@ -177,14 +161,18 @@ const EditorDrawer = React.createClass( { | |
} | ||
|
||
return ( | ||
<EditorDrawerFeaturedImage | ||
<AsyncLoad | ||
require={ function( callback ) { | ||
require( [ './featured-image' ], callback ); | ||
} } | ||
site={ this.props.site } | ||
post={ this.props.post } /> | ||
post={ this.props.post } | ||
/> | ||
); | ||
}, | ||
|
||
renderExcerpt: function() { | ||
var excerpt; | ||
let excerpt; | ||
|
||
if ( ! this.currentPostTypeSupports( 'excerpt' ) ) { | ||
return; | ||
|
@@ -228,7 +216,12 @@ const EditorDrawer = React.createClass( { | |
return ( | ||
<AccordionSection> | ||
<span className="editor-drawer__label-text">{ this.translate( 'Location' ) }</span> | ||
<Location coordinates={ PostMetadata.geoCoordinates( this.props.post ) } /> | ||
<AsyncLoad | ||
require={ function( callback ) { | ||
require( [ 'post-editor/editor-location' ], callback ); | ||
} } | ||
coordinates={ PostMetadata.geoCoordinates( this.props.post ) } | ||
/> | ||
</AccordionSection> | ||
); | ||
}, | ||
|
@@ -238,9 +231,12 @@ const EditorDrawer = React.createClass( { | |
return; | ||
} | ||
|
||
return( | ||
return ( | ||
<AccordionSection> | ||
<Discussion | ||
<AsyncLoad | ||
require={ function( callback ) { | ||
require( [ 'post-editor/editor-discussion' ], callback ); | ||
} } | ||
site={ this.props.site } | ||
post={ this.props.post } | ||
isNew={ this.props.isNew } | ||
|
@@ -261,7 +257,12 @@ const EditorDrawer = React.createClass( { | |
} | ||
|
||
return ( | ||
<SeoAccordion metaDescription={ PostMetadata.metaDescription( this.props.post ) } /> | ||
<AsyncLoad | ||
require={ function( callback ) { | ||
require( [ 'post-editor/editor-seo-accordion' ], callback ); | ||
} } | ||
metaDescription={ PostMetadata.metaDescription( this.props.post ) } | ||
/> | ||
); | ||
}, | ||
|
||
|
@@ -302,7 +303,7 @@ const EditorDrawer = React.createClass( { | |
<Accordion | ||
title={ this.translate( 'Page Options' ) } | ||
icon={ <Gridicon icon="pages" /> }> | ||
{ this.props.site && this.props.post ? | ||
{ this.props.site && this.props.post && | ||
<div> | ||
<PageParent siteId={ this.props.site.ID } | ||
postId={ this.props.post.ID } | ||
|
@@ -312,7 +313,7 @@ const EditorDrawer = React.createClass( { | |
<PageTemplates post={ this.props.post } /> | ||
</PageTemplatesData> | ||
</div> | ||
: null } | ||
} | ||
<PageOrder menuOrder={ this.props.post ? this.props.post.menu_order : 0 } /> | ||
</Accordion> | ||
); | ||
|
@@ -326,7 +327,10 @@ const EditorDrawer = React.createClass( { | |
{ site && ! this.hasHardCodedPostTypeSupports( type ) && ( | ||
<QueryPostTypes siteId={ site.ID } /> | ||
) } | ||
{ this.renderTaxonomies() } | ||
{ config.isEnabled( 'manage/custom-post-types' ) && ! includes( [ 'post', 'page' ], this.props.type ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tab in here. In fact, I think we should drop the Edit: As in, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense, I haven't moved taxonomies to be async-loaded because it was causing issues. Leaving that up to your or @timmyc's discretion :) |
||
? this.renderTaxonomies() | ||
: this.renderCategoriesAndTags() | ||
} | ||
{ this.renderFeaturedImage() } | ||
{ this.renderPageOptions() } | ||
{ this.renderSharing() } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only concern is readability of the tree if we start having loader wrappers everywhere. Nothing jumps out at me immediately as possible improvements, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's an unfortunate consequence of Webpack only being capable of static analysis for the code splitting, since otherwise would offer something a bit more simplified like:
...and have the component manage the complexities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this work? As you stated, webpack only does static analysis, but this would imply that is can do dynamic analysis... i think. Usually webpack is using require.ensure to inject split points.. plain requires, even hidden in functions, should be treated as hard deps that need to be in the same chunk (or a parent chunk)..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not entirely obvious, but this is the AMD (not CommonJS) syntax for asynchronously loading modules (note the callback). Both are supported by Webpack in defining a split point. The reason for using AMD is that it provides the loaded modules as arguments to the callback function, whereas CommonJS passes only
require
, and theCodeSplitRender
function is otherwise not capable of determining which modules it should proceed to load (unless we duplicated the modules array as a separate prop).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhhhhhhh... subtle.