Experimental Code Splitting 🔪📦

Thanks @klaussner and @benjamn for this

My initial hunch is no. A server has to have everything ready for all users. As node is a single threaded process, it could reduce experience for other users (not just the current one). Besides, Meteor does not take that much memory so we can say that it’s free server-side. Also, every time you update the server, you risk having to re-upload all the libs again.

3 Likes

Wow, that sounds great! :blush:
Regarding import(...) on the server: I think it would be useful to support this for code that is shared between client and server, e.g., for Meteor methods:

Meteor.methods({
  async rarelyUsed() {
    const bigModule = await import('./big-module.js');
    // …
  }
});

I would expect this to work everywhere. On the client, big-module.js would be loaded from a separate bundle and on the server, await import(...) would be similar to require.

2 Likes

this is awesome! thanks for the PoC

1 Like

Absolutely amazing work @klaussner. With all the work from @benjamn’s, the TC39 discussions and so on, it looks like you guys are nearer to solve the puzzle than ever before…

In case you didn’t hear about, somebody from Airbnb started something in November:
https://github.com/airbnb/babel-plugin-dynamic-import-node (at least server-side, pretty rudimentary stuff though)

But maybe I can give another perspective to code splitting:

I think we can all agree on that loading big modules asynchronously (in a browser env) is one of the biggest weaknesses of Meteor’s build system compared to Webpack at the moment, right?
But for me, far more important than loading ./big-module.js would be loading complete packages like ‘meteor/big-local-atmosphere-package’ or ‘big-npm-package’.

Doesn’t Package.js already provide us with all the needed information for code splitting like which module to rely strong/weak on and which deps just to use? If you look at rocket.chat or other big meteor projects, what is currently developed with the “umbrella” package approach is basically a folder-based recipe on how to split the app into logical modules perfect for code splitting, or am I wrong?

Personally I think loading something from packages/ in an async way would be extremely valuable than just loading a file in the project dir. I know, MDG wants to move away from Atmosphere in the long term, but when it comes to splitting a webapp into big modules I think the whole npm link / eslint / babel compile horror is just not worth the time so people start to structure their app inside the imports/ directory just because they fear that local atmosphere packages get deprecated in the future :slight_smile:

Oh, and that’s what I also found googling around / some literature:



@benjamn, are we waiting for tc39 proposal to firm up before we implement? I am super excited about this. Not only for our apps, but for Meteor as a whole. The build system is a key advantage.

Also, since we are in the process of adding it as a new feature, I would love if we could properly support service workers. What I mean: if a bundle has not changed, why change its name / hash and force a reload? Keep as-is so the client loads it from the cache. Only change names of bundles that indeed have a change.

This would be a game-changer for Meteor and the ecosystem. As it makes apps loading very smart, only loading what physically has changed.

3 Likes

I’m working on support for dynamic import(...) as we speak!

If all goes according to plan, the next 1.4.3 beta will include a working prototype. That’s part of why there hasn’t been a 1.4.3 beta in a little while.

Because import(...) is asynchronous, it definitely seems amenable to service worker-based caching. However, there’s no necessary reason fetching modules has to happen via HTTP (I’m currently planning to do it over a web socket), so the service workers API might not be the only option for async module caching. There are multiple places we could intercept module fetching for caching purposes, even if we didn’t have service workers.

Looking forward to showing you what I’ve built!

41 Likes

hey benjamin, that’s SO COOL! I’m just wondering if there’s a way to define how things get built with Meteor? I had to get off Meteor recently and go to Webpack unfortunately because creating a Chrome extension with Meteor was difficult. Code splitting in Meteor really excites me, would love to get back to Meteor in the future with this progress :slight_smile:

1 Like

I think it can be hard to find the client-side build output of a Meteor app since it’s hidden away inside various directories, did you try loading that JS file into an extension?

I think using Meteor to build client-only JS bundles should be possible but probably harder than it could be since it’s not the main focus.

Ah, so again, should it be in a “minor” version?:wink:

1 Like

You are my hero, and a great inspiration to follow !

1 Like

You made our day. Thank you!

1 Like

Amazing!!! … this darn forum only let’s us click on ‘like’ once. You would have been overwhelmed with ‘love’ :slight_smile:

2 Likes

Can’t wait to test the new beta! this is looking awesome :smiley: :smiley:

1 Like

But then what about caching by a reverse proxy or serving your modules via a CDN?

2 Likes

You’re getting at some interesting tradeoffs that I’m happy to discuss.

In short, the server-side logic for resolving dependencies of requested modules is more complex than a traditional CDN or proxy can understand. At best, the client could ask the module server for a set of module URLs to fetch, then fetch those modules via HTTP (with traditional caching). However, that would involve extra round trips, which is something I’m hoping to avoid.

The good news:

  • Previously fetched modules will be cached in IndexedDB (or local storage) on the client, so the module server can avoid sending back unchanged module code.
  • The module server will be stateless (clients tell the server what dynamic modules they already have), so you could (in principle) run multiple instances of the module server and load balance between them. In fact, scaling to additional containers on Galaxy will spread the load automatically.
  • The module server sends back exactly the modules the client needs, rather than sending back pre-built bundles. Bundles are easier to cache on a CDN, but you would need a combinatorial explosion of bundles to eliminate any duplication of modules between bundles.
  • In the future, HTTP/2 Server Push might be a good way to implement dynamic module delivery, but it’s not required by the current implementation, and it seems unnecessarily complicated for what I’m trying to do.
21 Likes

Hope this is implemented in meteor as it’s default behavior, right know meteor loads a massive js even if you don’t navigate to all the pages so a lot of js gets unused

Hey @benjamn, your work on support for dynamic import(...) is open source?
I’ll love to have a look on what you’re doing and perhaps help in some way.
Thank you.

1 Like

It is now! https://github.com/meteor/meteor/pull/8327#issuecomment-278432593

3 Likes

Wow! That’s awesome Benjamn! I’ll try it right now.

@klaussner, I would like to crate dynamic remove doc from collection.

// Methods
export const dynamicRemove = new ValidatedMethod({
    name: 'dynamicRemove',
    mixins: [CallPromiseMixin],
    validate: null,
    async run({path, selector}) {
        const Collection = await import(path);
        return Collection.remove(selector);
    }
});

// Call
dynamicRemove.callPromise({path: '../api/posts/posts', selector}).then((result) => {
            ........... Success
        }).catch((err) => {
            console.log(err.reason);
        });

Get error

Exception while invoking method 'dynamicRemove' TypeError: Collection.remove is not a function
I20170330-08:21:35.310(7)?     at [object Object]._callee$ (imports/modules/dynamicRemove.js:24:27)
I20170330-08:21:35.310(7)?     at tryCatch (/Users/theara/Desktop/meteor-app/react-material/node_modules/regenerator-runtime/runtime.js:64:40)
I20170330-08:21:35.311(7)?     at GeneratorFunctionPrototype.invoke [as _invoke] (/Users/theara/Desktop/meteor-app/react-material/node_modules/regenerator-runtime/runtime.js:299:22)
I20170330-08:21:35.313(7)?     at GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (/Users/theara/Desktop/meteor-app/react-material/node_modules/regenerator-runtime/runtime.js:116:21)
I20170330-08:21:35.313(7)?     at tryCatch (/Users/theara/Desktop/meteor-app/react-material/node_modules/regenerator-runtime/runtime.js:64:40)
I20170330-08:21:35.314(7)?     at invoke (/Users/theara/Desktop/meteor-app/react-material/node_modules/regenerator-runtime/runtime.js:154:20)
I20170330-08:21:35.315(7)?     at /Users/theara/Desktop/meteor-app/react-material/node_modules/regenerator-runtime/runtime.js:164:13
I20170330-08:21:35.315(7)?     at /Users/theara/.meteor/packages/promise/.0.8.8.1mes7d8++os+web.browser+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:32:39