diff options
author | Sorin Davidoi <sorin.davidoi@gmail.com> | 2017-07-08 00:06:02 +0200 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2017-07-08 00:06:02 +0200 |
commit | 348d6f5e7551e632e7dea41e61c40f79aac59be9 (patch) | |
tree | 54cc599e3509457c25603653d5490bd96efe39c6 /app/javascript/mastodon/features/ui/util | |
parent | 00df69bc89f1b5ffdf290bde8359b3854e2b1395 (diff) |
Lazy load components (#3879)
* feat: Lazy-load routes * feat: Lazy-load modals * feat: Lazy-load columns * refactor: Simplify Bundle API * feat: Optimize bundles * feat: Prevent flashing the waiting state * feat: Preload commonly used bundles * feat: Lazy load Compose reducers * feat: Lazy load Notifications reducer * refactor: Move all dynamic imports into one file * fix: Minor bugs * fix: Manually hydrate the lazy-loaded reducers * refactor: Move all dynamic imports to async-components * fix: Loading modal style * refactor: Avoid converting the raw state for each lazy hydration * refactor: Remove unused component * refactor: Maintain modal name * fix: Add as=script to preload link * chore: Fix lint error * fix(components/bundle): Check if timestamp is set when computing elapsed * fix: Load compose reducers for the onboarding modal
Diffstat (limited to 'app/javascript/mastodon/features/ui/util')
-rw-r--r-- | app/javascript/mastodon/features/ui/util/async-components.js | 143 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/util/react_router_helpers.js | 65 |
2 files changed, 208 insertions, 0 deletions
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js new file mode 100644 index 000000000..c9f81136d --- /dev/null +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -0,0 +1,143 @@ +import { store } from '../../../containers/mastodon'; +import { injectAsyncReducer } from '../../../store/configureStore'; + +// NOTE: When lazy-loading reducers, make sure to add them +// to application.html.haml (if the component is preloaded there) + +export function EmojiPicker () { + return import(/* webpackChunkName: "emojione_picker" */'emojione-picker'); +} + +export function Compose () { + return Promise.all([ + import(/* webpackChunkName: "features/compose" */'../../compose'), + import(/* webpackChunkName: "reducers/compose" */'../../../reducers/compose'), + import(/* webpackChunkName: "reducers/media_attachments" */'../../../reducers/media_attachments'), + import(/* webpackChunkName: "reducers/search" */'../../../reducers/search'), + ]).then(([component, composeReducer, mediaAttachmentsReducer, searchReducer]) => { + injectAsyncReducer(store, 'compose', composeReducer.default); + injectAsyncReducer(store, 'media_attachments', mediaAttachmentsReducer.default); + injectAsyncReducer(store, 'search', searchReducer.default); + + return component; + }); +} + +export function Notifications () { + return Promise.all([ + import(/* webpackChunkName: "features/notifications" */'../../notifications'), + import(/* webpackChunkName: "reducers/notifications" */'../../../reducers/notifications'), + ]).then(([component, notificationsReducer]) => { + injectAsyncReducer(store, 'notifications', notificationsReducer.default); + + return component; + }); +} + +export function HomeTimeline () { + return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline'); +} + +export function PublicTimeline () { + return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline'); +} + +export function CommunityTimeline () { + return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline'); +} + +export function HashtagTimeline () { + return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline'); +} + +export function Status () { + return import(/* webpackChunkName: "features/status" */'../../status'); +} + +export function GettingStarted () { + return import(/* webpackChunkName: "features/getting_started" */'../../getting_started'); +} + +export function AccountTimeline () { + return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline'); +} + +export function AccountGallery () { + return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery'); +} + +export function Followers () { + return import(/* webpackChunkName: "features/followers" */'../../followers'); +} + +export function Following () { + return import(/* webpackChunkName: "features/following" */'../../following'); +} + +export function Reblogs () { + return import(/* webpackChunkName: "features/reblogs" */'../../reblogs'); +} + +export function Favourites () { + return import(/* webpackChunkName: "features/favourites" */'../../favourites'); +} + +export function FollowRequests () { + return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests'); +} + +export function GenericNotFound () { + return import(/* webpackChunkName: "features/generic_not_found" */'../../generic_not_found'); +} + +export function FavouritedStatuses () { + return import(/* webpackChunkName: "features/favourited_statuses" */'../../favourited_statuses'); +} + +export function Blocks () { + return import(/* webpackChunkName: "features/blocks" */'../../blocks'); +} + +export function Mutes () { + return import(/* webpackChunkName: "features/mutes" */'../../mutes'); +} + +export function MediaModal () { + return import(/* webpackChunkName: "modals/media_modal" */'../components/media_modal'); +} + +export function OnboardingModal () { + return Promise.all([ + import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal'), + import(/* webpackChunkName: "reducers/compose" */'../../../reducers/compose'), + import(/* webpackChunkName: "reducers/media_attachments" */'../../../reducers/media_attachments'), + ]).then(([component, composeReducer, mediaAttachmentsReducer]) => { + injectAsyncReducer(store, 'compose', composeReducer.default); + injectAsyncReducer(store, 'media_attachments', mediaAttachmentsReducer.default); + return component; + }); +} + +export function VideoModal () { + return import(/* webpackChunkName: "modals/video_modal" */'../components/video_modal'); +} + +export function BoostModal () { + return import(/* webpackChunkName: "modals/boost_modal" */'../components/boost_modal'); +} + +export function ConfirmationModal () { + return import(/* webpackChunkName: "modals/confirmation_modal" */'../components/confirmation_modal'); +} + +export function ReportModal () { + return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal'); +} + +export function MediaGallery () { + return import(/* webpackChunkName: "status/MediaGallery" */'../../../components/media_gallery'); +} + +export function VideoPlayer () { + return import(/* webpackChunkName: "status/VideoPlayer" */'../../../components/video_player'); +} diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js new file mode 100644 index 000000000..e33a6df6f --- /dev/null +++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js @@ -0,0 +1,65 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Switch from 'react-router-dom/Switch'; +import Route from 'react-router-dom/Route'; + +import ColumnLoading from '../components/column_loading'; +import BundleColumnError from '../components/bundle_column_error'; +import BundleContainer from '../containers/bundle_container'; + +// Small wrapper to pass multiColumn to the route components +export const WrappedSwitch = ({ multiColumn, children }) => ( + <Switch> + {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))} + </Switch> +); + +WrappedSwitch.propTypes = { + multiColumn: PropTypes.bool, + children: PropTypes.node, +}; + +// Small Wraper to extract the params from the route and pass +// them to the rendered component, together with the content to +// be rendered inside (the children) +export class WrappedRoute extends React.Component { + + static propTypes = { + component: PropTypes.func.isRequired, + content: PropTypes.node, + multiColumn: PropTypes.bool, + } + + renderComponent = ({ match }) => { + this.match = match; // Needed for this.renderBundle + + const { component } = this.props; + + return ( + <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}> + {this.renderBundle} + </BundleContainer> + ); + } + + renderLoading = () => { + return <ColumnLoading />; + } + + renderError = (props) => { + return <BundleColumnError {...props} />; + } + + renderBundle = (Component) => { + const { match: { params }, props: { content, multiColumn } } = this; + + return <Component params={params} multiColumn={multiColumn}>{content}</Component>; + } + + render () { + const { component: Component, content, ...rest } = this.props; + + return <Route {...rest} render={this.renderComponent} />; + } + +} |