universal-router icon indicating copy to clipboard operation
universal-router copied to clipboard

Does HMR actually work with universal-router?

Open DominikLevitsky opened this issue 9 years ago • 6 comments


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

DominikLevitsky avatar Aug 29 '16 12:08 DominikLevitsky

Yeah it works

nightwolfz avatar Oct 20 '16 06:10 nightwolfz

@nightwolfz

I have to say it depends.

Currently it's not easy to support HMR with static module import.

In the react-starter-kit , it requires dynamic import to make HMR work:

//routes/index.js
const routes = {
  path: '/',
  children: [
    {
      path: '/',
      load: () => import(/* webpackChunkName: 'home' */ './home'), //dynamic import
    }
  ]
}

//router.js
export default new Router(routes, {
  resolveRoute(context, params) {
    if (typeof context.route.load === 'function') {
      return context.route
        .load()
        .then(action => action.default(context, params)); // call load function
    }
    if (typeof context.route.action === 'function') {
      return context.route.action(context, params);
    }
    return null;
  },
});

//client.js
if (module.hot) {
  module.hot.accept('./router', () => { 
    onLocationChange(currentLocation);
  });
}

When you change code in home.js, the resolveRoute will be called through onLocationChange, and it calls load to import the changed module.

However, in cases with out dynamic import:

//routes/index.js
import home from './home'
const routes = {
  path: '/',
  children: [
    {
      path: '/',
      load: () => home,
    }
  ]
}

// client.js
import routes from 'routes';
let router = new UniversalRouter(routes);

if (module.hot) {
  module.hot.accept('./routes', () => {
    const nextRotues = require('./routes').default;
    // How can we replace routes in router ?
  });
}

The load method will return the old home module.

We can get new routes object with changed code in HMR callback, but how can we replace it in router instance?

Currently I'm replace it manually:

module.hot.accept('./routes', () => {
    // eslint-disable-next-line global-require
    const routes = require('./routes').default;
    router.root = Array.isArray(routes) ? { path: '', children: routes, parent: null } : routes;
    router.root.parent = null;
    renderApp(history.location);
  });

But I believe there should be better solution:

  1. expose an static resolve API like #86
  2. add setRoutes method on prototype of class UniversalRouter

Both of them looks great for me, and personally I would prefer the solution 1

kpaxqin avatar Sep 20 '17 14:09 kpaxqin

@kpaxqin why don't you want to create a new instance of router after hot update?

// entry.js
import UniversalRouter from 'universal-router';
import routes from './routes';

let router = new UniversalRouter(routes);

// ...

if (module.hot) {
  module.hot.accept('./routes', () => {
    const nextRoutes = require('./routes').default;
    router = new UniversalRouter(nextRoutes);
  });
}

or

// router.js
import UniversalRouter from 'universal-router';
import routes from './routes';

export default new UniversalRouter(routes);
// entry.js
import router from './router';

// ...

if (module.hot) {
  module.hot.accept('./router');
}

frenzzy avatar Sep 20 '17 14:09 frenzzy

@frenzzy My bad! I just made a mistake thinking router instance as a listener and avoid to create duplicated ones.

Actually it just resolve url and returns component, so its safe to re-create router instance.

Thanks for your reply.

kpaxqin avatar Sep 20 '17 15:09 kpaxqin

Anyone knows how to intercept the dynamic route value as well as route query params? I can't find the info regarding how to obtain those values when it is using load property.

adamchenwei avatar Oct 28 '17 14:10 adamchenwei

@adamchenwei if you are talking about react-starter-kit resolve route approach, then you can may use context object as usual:

const route = {
  path: '/page',
  action(context) { // action method
    context.pathname // or context.<any>
  },
};

vs

const route = {
  path: '/page',
  load: () => import('./route-action.js'),
};

// route-action.js
function action(context) { // the same action method as above
  context.pathname // or context.<any>
}
export default action;

You may pass any data to routes via router.resolve({ pathname, ...context })

router.resolve({
  pathname: window.location.pathname,
  dynamicValue: 'here',
});

frenzzy avatar Oct 30 '17 15:10 frenzzy