Viewmodel-react + dynamic imports

viewmodel-react had dynamic/lazy/deferred loading for a long time (using Webpack) but it now works with Meteor since 1.5 introduced dynamic imports. To use it just apply the defer binding (with a boolean value) to a component and you’re done. Best of all, it works out of the box.

For example if you have landing page with a section below the fold, you can load it dynamically and thus speed up the loading time of the above the fold content:

LandingComponent({
  render() {
    <div>
      <AboveTheFoldContent />
      <BelowTheFoldContent b="defer: true" />
    </div>
  }
})

Want to conditionally show some content but defer loading it until it’s actually needed?
In this example we’re loading (and showing) the big component only after the user clicks the button.

App({
  showContent: false,
  render() {
    <div>
      <button b="click: showContent">Show Content</button>
      <BigComponent b="defer: showContent" />
    </div>
  }
})

Your router doesn’t support dynamic loading?

No problem, just create an empty component (to be loaded/called by your router) and have that call the real component with the defer attribute.

Check out the documentation and here’s an example of how it works. The example doesn’t use Meteor but the viewmodel-react API is the same for any React project.

6 Likes

I’ve tried this and I get an error in the client console

Uncaught TypeError: require.ensure is not a function
    at App.render (App.js:7)

This is using your viewmodel-react-starter-meteor project, updated to Meteor 1.5. Also updated some npm modules, package.json:

{
  "name": "test4",
  "private": true,
  "scripts": {
    "start": "meteor run"
  },
  "dependencies": {
    "babel-runtime": "^6.23.0",
    "meteor-node-stubs": "~0.2.4",
    "react": "^15.5.4",
    "react-dom": "^15.5.4",
    "viewmodel-react": "^2.1.1",
    "viewmodel-react-plugin": "^3.0.0"
  }
}

Am I doing something wrong? Would you like me to open a github issue with repro?

Another question for once I get this working: my app has all the UI components in the lib/ folder so they are available on the client and server for SSR. Would that mean that they are all included in the initial bundle anyway, defeating the purpose of deferred loading? Would I have to move them to imports/ instead?

Can’t wait to get this working. I’ve finally converted my entire app to vm-react from blaze to get full SSR on any route.

That starter was ancient. I’m pretty sure the old hot-module-reload doesn’t work with the latest React.

I updated viewmodel-react-starter so it can work with the latest greatest. It has an example of lazy loading and testing components. Get it and let me know.

As for your project, you’ll have to move them to the imports folder.

Can you create a repo with your setup? One that has 2 routes (home and about) just to show how you’re achieving SSR.

doh! I thought you were talking about the other starter. I just updated viewmodel-react-starter-meteor with the latest versions. I also updated it to show how to test components and how defer works with Meteor.

Great, I’ve got your starter project working. Any ideas what I might need to update in my own app to get it working, without the require.ensure is not a function error?

I’ve created a repo here, based on your starter but with react router (v3, not v4) and react-router-ssr (which is abandoned, but still works). It also uses react-helmet to show per-route meta titles, and also data-hydration on the Todo’s route :wink:

For the SSR I basically copied this post. For data hydration my solution is explained here.

Here’s some more questions based on my repo, but they-re not all Viewmodel specific so I’m not expecting you to answer all of them:

  1. Do you have any thoughts of how to do deferred loading of all route components which aren’t on the initial route. Basically so the first page load is as small as possible then all other components load asynchronously? Using react-router, every route page needs to be imported so it can be referenced in the route definitions, therefore each route would end up in the initial bundle. Otherwise I would have to write a deferred loading wrapper for every page.

  2. Is there a more elegant way of doing {Meteor.isServer ? <Big /> : <Big b="defer: true"/>} so the initial page load is prerendered with everything it needs, but all off-route components are deferred? The above works, but results in a quick flash while the client waits for the deferred component.

  3. why is the created() function called twice during the SSR? See the server console when loading the /about route in my repo.

  4. the Todos destroyed() function is never called on the server. Does this mean that the server-side subscription is left open, leaking memory, or are sever side subscriptions closed as soon as the render finishes?

(edit: [tab] then [enter] submitted my reply prematurely :slight_smile: )

Scratch that, my app seems to be working now without the require.ensure is not a function error. I’ve got dynamic imports working!

Now I’m trying to find a way to do the dynamic loading closer to the router without creating a wrapper for every route…

  1. You could create a file that knows how to dynamically load any/all components based on information given by the router. This would be similar to what Angular does with modules.
  2. Not that I know, you can’t rely on the deferred property (in this case you hard code true) because to VM defer means “use dynamic import” (aka. an async operation). That means the SSR will not wait for it. btw, I don’t get the flash, it renders straight from the server (i.e. the initial html has the component).
  3. & 4. Dunno about that, created and destroyed just put the given functions on React’s component will mount and unmount.
  1. That is exactly what I have done and it works well.

  2. I’ve discovered that there’s no point doing {Meteor.isServer ? <Big /> : <Big b="defer: true"/>} because the Big component ends up compiled into the app.js anyway. For me, the Big component was prerendered by the server, but then there was a brief flash when the client re-rendered the page.

Thanks for making it so easy to use deferred components! It makes things so much tidier. My current production site uses a couple of nasty hacks to keep text-heavy pages out of the app bundle. On my privacy policy and terms of use pages the text is loaded in an iframe from a static html file. On my FAQ page the faqs are loaded from a server-only method. Those hacks are no longer needed, yay! (although the FAQ hack does allow the FAQs to be in the pre-render).

1 Like