Haul: Code splitting & OTA

Created on 31 Mar 2017  路  25Comments  路  Source: callstack/haul

How to separate js bundle into framework bundle and user code bundle?
and then 锛學hen we publish a single file to user's cell phone ,We Just need combine a single file to the base bundle.

enhancement

Most helpful comment

Okay so after taking some time to read the dist.unbundle.bundle generated runtime code that RN/Metro created with the --unbundle feature, and discovered. It's _not code splitting_ in the way that we understand it whatsoever.

Rather this works like just splitting out (for no particular rhyme or reason), the module graph into x bundles that access the same globally defined runtime on self.

Turns out webpack.optimize.AggressiveSplittingPlugin() is the exact thing we need to create these splits.

Here is an example of me using a contrived sample:

image

This stiches the runtime with other loaded chunks using self["webpackChunk"](chunkId) which means they really aren't async at all or deferred but rather just separated into a separate file. My guess is that RN handles the file serving when the bundle is needed?

Is there a way to lazy load modules by using webpack dynamic import() but not result splitted bundles?

This is exactly what webpack does with the webworker target!

All 25 comments

CC: @axemclion

so currently haul does not support code splitting? my app had large bundle size (4MB) and it effects app load time. I thought that because haul is based on webpack and webpack supports code splitting that it will be possible to load only what's necessary when app loads and the rest afterwards

Webpack needs the DOM to insert script tags when doing code splitting. Since we don't have DOM, it's not possible.

Also, as an app user I'm already used to download large apps, probably lazy loading code will not improve the experience at all.

@satya164 Why not use something similar to react-native unbundle. React Native already supports code splitting, we may jsut have to write a custom plugin to insert the hash file to indicate that this is an unbundle operation.

Yeah, I think eventually we want to do that. I was just saying that we probably cannot use webpack's code splitting feature as is right now.

@satya164 Not sure if it might help, but this #182 changes target to webworker and polyfills importScripts so you can download anything you want and it will be eval'ed.

Is there a way to lazy load modules by using webpack dynamic import() but not result splitted bundles?

Wait, even if webpack requires the DOM, that is just for the target: web, if you use target: node, webpack will use just require statements! Has anyone played with this yet?

We use web workers target as closest to what plays nice with RN. While migrating to webpack 4 however, we feel like creating our own custom target to get rid of some nasty hacking

Besides the importScripts and eval, is there a reason the generic node target wasn't sufficient.

@TheLarkInn not that I know of. The only reason is the script loading mechanism. You can check the comments here https://github.com/callstack/haul/pull/182#discussion_r125022108

We haven't tried node yet though.

Here are the considirations I have when it comes to using node target:

  • RN doesn't have any require, module, __filename, __dirname variables defined, so there cannot be even singlerequire call (unless we provide a async polyfill for it)
  • How would node target download hot-update{.json,.js}?
  • JS engine on Android is ancient and supports only ES5, so no new syntax/APIs in bootstrap code, unless we inject polyfills before it

And most importantly it still has to work properly in webworker, since with attached debugger the JS bundle will be executed in worker context.

Okay so after taking some time to read the dist.unbundle.bundle generated runtime code that RN/Metro created with the --unbundle feature, and discovered. It's _not code splitting_ in the way that we understand it whatsoever.

Rather this works like just splitting out (for no particular rhyme or reason), the module graph into x bundles that access the same globally defined runtime on self.

Turns out webpack.optimize.AggressiveSplittingPlugin() is the exact thing we need to create these splits.

Here is an example of me using a contrived sample:

image

This stiches the runtime with other loaded chunks using self["webpackChunk"](chunkId) which means they really aren't async at all or deferred but rather just separated into a separate file. My guess is that RN handles the file serving when the bundle is needed?

Is there a way to lazy load modules by using webpack dynamic import() but not result splitted bundles?

This is exactly what webpack does with the webworker target!

So if you all know how to take this output, and stiched together set of bundles, "unbundled", and do whatevers needed for RN to consume it the right way, then we would be golden.

@TheLarkInn Great findings.
Regarding RN, AFAIK, it requires a single bundle, meaning we cannot do multiple fetches for bundle and then join it together.
Maybe we could generate separate chunks, and join them together at bundle request? In that case, we could separate vendors from user code, am I thinking correct?

These are separate files but just loaded sequentially? According to RN docs it says its bin encoded blobs or something?

If the unbundle feature exists, which generates multiple files, then there must be some way to make the RN to load all of those files. So the questions would be:

  1. What files does unbundle generate (filenames and extensions specifically)?
  2. Is there any _entry_ bundle, which requests other unbundled bundles? If so, how is it done?

@zamotany Did that unbundle feature was introduced recently? That might be delta bundles feature

@zamotany they basically wrote a webpack require runtime and a custom chunk wrapper why I'm not sure still but it looks just like webpack had generated about x small common chunks that are sync loaded.

@Krizzu no, it was there for months but undocumented. Delta bundles are another thing, which we should investigate because the concept is pretty neat.

@TheLarkInn Ok, so the bundles are requested from JS side. Curious how they load it, since XMLHttpRequest is async in RN.

Hola, any update on this?

Hola, any update on this?

@zamotany Hey, sorry for taking so long to get back on this.

Okay, from what I understand, this is the equivalent of adding 8 scripts to your head and they all load. (With the unbundled one first). Basically each file executes sequentially and adds their modules on self (as seen above). Building it in the webworker target allows for this to happen.

Due to the nature of React Native, code splitting can be achieved using:

  • either dynamic imports import('something') with RAM bundles (ram-bundle command, https://facebook.github.io/react-native/docs/performance#enable-the-ram-format)
  • or with multi-bundle feature aka Apennine Architecture (multiple bundles + DLLs) once it's merged into the React Native
Was this page helpful?
0 / 5 - 0 ratings