Meteor 1.5: How to actually dynamically import modules?

Someday if we get JS tree shaking for dead code in the build tool, it will only work for ES6 import style code. That is IMO a big reason to start using ES6 classes.

1 Like

I’ve been working on a post about it… not sure if my article is any better than the meteor blog and this thread, but at least it’s a new take and perhaps covers a slightly different angle:

Hope this helps, and let me know if I need to correct anything. I plan on making a few more posts from different angles.

8 Likes

see my article linked above…

Excellent article - thank you :slight_smile:

I have a question about this section:

If we import Limb we get Limb - Leaf in 1 bundle.

If we later dynamically import Twig from some other page, we get Twig - Leaf in a (different) 2nd bundle.

This means that we have just duplicated code, bundled it twice.

How does that stack up with @sashko’s blog article, which states:

How it works

So what does Meteor do differently that enables some of these features? Well, here’s a summary:

The most important aspect is that the client is aware of the shape of the module tree, the names of those modules, and their content hashes ahead of time for the entire app. This means that when a dynamic import happens, the client-side app can determine exactly which modules and hashes are missing, and ask only for those from the server in one request. Then, all of the modules are cached individually on the client so that next time they can be loaded from persistent storage.

This means that, in most cases, you can load every dynamic module exactly once. Even if you push an entirely new version of your app, if that’s using the same version of a library as before that will still be loaded from local storage.

I can’t link directly to that section, unfortunately.

No - he’s discussing the pitfalls of using Webpack 2. Read on …

By contrast, in the Meteor 1.5 implementation of dynamic import(…), the client has perfect information about which modules were in the initial bundle, which modules are in the browser’s persistent cache, and which modules still need to be fetched. There is never any overlap between requests made by a single client, nor will there be any unneeded modules in the response from the server. You might call this strategy exact code splitting, to differentiate it from bundling.

1 Like

Oohhh :sweat_smile: that is awesome then

2 Likes

I wonder if hot module replacement is next :blush:

I believe it’s already here, only changed dynamically loaded modules will be pushed to the client.

From this blog post:

This means that, in most cases, you can load every dynamic module exactly once. Even if you push an entirely new version of your app, if that’s using the same version of a library as before that will still be loaded from local storage.

1 Like

This gives me a parsing error in Atom. Any ideas how to fix that?

I don’t want to derail this thread as it’s great, bust my two cents… ES6/7 is in fact JavaScript. It’s not just some fancy new language. Once you start writing the new function syntax and imports, you will wonder why you haven’t moved to it yet, and will never go back. Good article https://medium.com/javascript-scene/how-to-learn-es6-47d9a1ac2620?source=linkShare-f5240fd0035-1496577988

And you don’t need to write ES6 classes. It’s pretty backwards compatible with “old” js.

5 Likes

totally agree,

also I recommend to not use classes at all, unless you have a clear reason why you need classes.

1 Like

Sorry - my experience of Atom is limited to a disappointing evaluation. According to issue#136 it should work. Lots of people here do use it - maybe they can help?

@benjamn is react-loadable the “recommended” way of dynamically loading and rendering a React component then? If so, it’d be nice to have an example that uses not only a “user” component, but also an NPM one…?

1 Like

My test for this was incorrect, I was logging bundles which were processed… instead I needed to monitor webframes and watch the actual imports.

@sashko & @benjamn were correct (no surprise) and each dynamically imported component is only imported 1 time, even if imported via different hierarchies.

I have corrected my article to reflect this.

4 Likes

I’ve been really disappointed with all the hype and lack of real world examples for this feature. I honestly can not figure out how to do this with even the most basic example. I am using React, React Router, and i have a sign-in page i’d like to load async inside of my App component.

const Login = await import (’./Login’)

<Route path="/login" exact component={Login} />

this doesn’t work. as stated above it needs to be in an async function? or have a .then() afterwards where you “do something with it”? I can’t seem to find a single example where this is properly explained.

It seems that everybody is aware that a lot of people are using React, but yet all the examples of this use Blaze Templates. It’s super confusing. I was really excited to use and understand this feature, but i just do not understand where to go for a complete working example of doing this with react. could somebody help explain?

3 Likes

Check this video by Łukasz Jagodziński:

Thanks to @aadams for linking to it in his post

2 Likes

Or, to spare people from having to suffer through yet another video tutorial (sorry, but I really don’t like them):

  1. Install the npm-module react-loadable
  2. Create a React component named Loading.tsx which is displayed while the component loads
import React from 'react';

export default function Loading({ isLoading, pastDelay, error }) {
  if (isLoading && pastDelay) {
    return <p>Loading...</p>;
  } else if (error && !isLoading) {
    return <p>Error!</p>;
  } else {
    return null;
  }
}

_3. Create a wrapper function in a file named MyLoadable.js

import Loadable from 'react-loadable';
import Loading from './Loading';

export default function MyLoadable(opts) {
    return Loadable({
        LoadingComponent: Loading,
        delay: 200,
        ...opts
    });
}

and then import your components you want to load later like this:

import MyLoadable from './MyLoadable';
const MyDeferredComponent = MyLoadable({
    loader: () => import('./MyDeferredComponent')
});

Please also note that VSCode will mark the latter code as an error and TypeScript most likely won’t like it at all.

3 Likes

To add to above. If you are using flowrouter and komposer you can do the following with react components that are loaded via data container.

So to code split at router level you…

FlowRouter.route('/todo/new', {
  action() {
    import("/imports/client/components/todo/new/")
      .then(({KomposerContainer}) => {
        mount(Layout, {content: <KomposerContainer/>});
      });
  }
});

Works absolutely fine and you don’t even need react loadable package either.

I spent one evening moving all client components into the import folder, and moving files to CDN where I could. Was able to cut down app bundled size from 400kb+ to a lean 266kb. Load times have improved dramatically as well as my server only has to serve a smaller initial file before the app loads up.

1 Like

Or if you are using ViewModel you can do:

Example({
  render() {
    <div>
      <BigComponent b="defer: true" />
    </div>
  }
})

to dynamically load the BigComponent. Nothing more to do.

You don’t even need Komposer, flow router or react loadable :stuck_out_tongue_closed_eyes:

The online platform spectrum.chat has recently gone open source. They use react-loadable and react-router: