react-async-component icon indicating copy to clipboard operation
react-async-component copied to clipboard

Preload dependencies during idle time?

Open oliviertassinari opened this issue 8 years ago • 3 comments

Hey, cool to see you have abstracted away the lazy loading handling for large React tree. I found the approach much better than what react-router is suggesting to do! I'm wondering, do you have any plan preloading dependencies on the client side during the idle time?

I have been implemented a naive solution, I'm pretty sure we could improve that. It's preloading everything. If we were collecting some data. For instance, the order of lazing loading, we could even build a prediction model. I have heard that Facebook is using a machine learning algorithm to smartly load dependencies ahead of time.

oliviertassinari avatar Jan 23 '17 21:01 oliviertassinari

Hey @oliviertassinari! Thanks!

Yeah, totally a great idea and I think @bradennapier has some alternative ideas around this problem.

It would be great to come up with a simple and flexible solution for this. I'll review your solution when I get a chance and keep this in the back of my mind. Happy to collaborate with you to get something awesome together.

ctrlplusb avatar Jan 24 '17 09:01 ctrlplusb

Olivier, I may be misunderstanding your intent - but the fact the async loading is done using import which already returns promises, we shouldn't need to use setTimeout to accomplish nested asynchronous loading.

This method also allows using the defer method to handle pushing to the client for rendering which @ctrlplusb has given us. From my testing it appears to do a good job so far, but I haven't done extensive tests on it as its working for our dev thus far.

I have setup a method of handling routes asynchronously as well as their dependencies.

export default [
  {
    id: 'Home',
    exactly: true,
    props: {
      title: 'Home'
    },
    pattern: '/',
    component: () => import('./screens/Home')
  },
  {
    id: 'Welcome',
    props: {
      title: 'Welcome'
    },
    pattern: '/login/1',
    component: () => import('./screens/Welcome')
  },
  {
    id: 'SecurityCenter',
    props: {
      title: 'Security Center'
    },
    pattern: '/security-center',
    secure: true,
    component: () => import('./screens/SecurityCenter')
  },
  {
    id: 'UserProjects',
    props: {
      title: 'Project Select'
    },
    pattern: '/project',
    component: () => import('./screens/UserProjects')
  },
  {
    id: 'LoginPage',
    props: {
      title: 'Login'
    },
    pattern: '/login',
    component: () => import('./screens/Login')
  }
]

Then when we move deeper into the routes we simply pass new routes that will be also imported asynchronously

import React from 'react'
import Resolver from 'app/resolver'

const routes = [
  {
    id: 'ProjectSelect',
    pattern: '/',
    exactly: true,
    component: () => import('./screens/ProjectSelect')
  },
  {
    id: 'ProjectDashboard',
    pattern: '/dashboard/:projectSlug',
    component: () => import('./screens/ProjectDashboard')
  }
]

export default props => (<Resolver {...props} routes={routes} />)

Then we have this as a Resolver ( I added a redux push as well for redirecting when accessing secure pages using a layer:

const RoutedResolver = ({ routes, ...props }) => (
  <div>
    {
      routes.map( route => {
        if ( ! route.id ) { throw new Error('Route Does not have an ID: ', route) }
        const matchProps = {
          ...route,
          key: route.id,
          exactly: route.exactly == true,
          pattern: route.pattern === '/'
            ? props.pathname
            : props.pathname + route.pattern
        }
        return <Match {...matchProps} render={ renderProps => {
            const appProps = {
              ...matchProps,
              ...renderProps,
            }
            return <Resolver {...props} {...appProps} />
          }} 
        />
      })
    }
  </div>
)

const Loading = () => (<div>LOADING</div>)

const Resolver = ({ 
  component, routes, 
  isAuthenticated = false,
  defer  = false, 
  secure = false,
  ...props 
}, { store }) => {
  if ( secure && ! isAuthenticated ) {
    const { location, ...rest } = props
    store.dispatch({
      type: 'ROUTER_SECURE_REQUESTED',
      location: props.location,
      props:    rest
    })
    return null
  }
  const Component = routes
    ? RoutedResolver
    : component
      ? BuildAsyncComponent(component, secure || defer)
      : Loading
  if ( routes ) { props.routes = routes }
  //console.log('Resolver: ', props.id, props)
  return <Component {...props} />
}

Resolver.contextTypes = {
  store: PropTypes.object.isRequired
}

export const BuildAsyncComponent = (component, defer) => 
  createAsyncComponent({
    resolve: component,
    defer
  })

export default Resolver

bradennapier avatar Jan 24 '17 09:01 bradennapier

Related: https://github.com/ctrlplusb/react-universally/issues/406

ctrlplusb avatar Mar 23 '18 14:03 ctrlplusb