mobx-state-tree icon indicating copy to clipboard operation
mobx-state-tree copied to clipboard

Dynamic Import / Lazy Loading of Stores

Open clgeoio opened this issue 4 years ago • 6 comments

Feature request

Is your feature request related to a problem? Please describe. When using MST, I have run in to issues with tools like bundlesize as having a common root store imports references to other stores, which can bring big packages along for the ride.

E.g. I want to add some code which uses compresses images and uploads them to my server. The method lives in the PhotoStore and the package that I am using to do this is 50kb. With my RootStore as such, now any where else in code that I reference the root store brings the 50kb package too (e.g. the user account password reset page)

import { UserStore } from './user';
import { PhotoStore } from './photos';

const Root = types
  .model('Root', {
    user: types.optional(UserStore, {}),
    photos: types.optional(PhotoStore, {}),
  })

const createRootStore = (
  initialState: IRootSnapshotIn,
  dependencies: IDependencies,
) => Root.create(initialState, dependencies);

Describe the solution you'd like I would like to be able to lazy import a store so my root store can be smaller in size and users will not get shipped code they do not use. Ideally this will work similar to other code splitting strategies in webpack like dynamic imports

e.g.

const Root = types
  .model('Root', {
    user: types.lazy(() => import('./user'), { resolveComponent: (module) => module.UserStore }, {}),
    photos:  types.lazy(() => import('./photos'), { resolveComponent: (module) => module.PhotoStore }, {})
  })

const createRootStore = (
  initialState: IRootSnapshotIn,
  dependencies: IDependencies,
) => Root.create(initialState, dependencies);

....

const root = createRootStore(...);
// root.photos == undefined or "late"
root.photos.load()

// root.photos == PhotoStore

Describe alternatives you've considered If a route is entirely independent from existing stores, it can live in its own tree and not load the main RootStore at all.

Avoid the use of references, and instead use plain strings and do the lookups manually. There wouldn't need to be a RootStore at all anymore.

Additional context A PR was closed a while back which seemed to offer this functionality

Are you willing to (attempt) a PR?

  • [x] Yes
  • [] No

clgeoio avatar Nov 17 '20 06:11 clgeoio

Hey @jamonholmgren, as the new Supreme Commander I am just checking if there is any particular channel I should be making requests like this?

clgeoio avatar Dec 07 '20 03:12 clgeoio

Hi @jamonholmgren, I'm wondering if you could chime in on this topic? This would really help build large web apps using MST without impacting loading times negatively 🙂

fwouts avatar Feb 17 '21 21:02 fwouts

Hello. I like this proposal. To achieve similar effect I rely on volatiles and specific structure of my root store/substores. Which is a bit weird and I'm looking for a better&standard approach.

import {
	types,
	getRoot,
	getIdentifier,
	resolveIdentifier,
} from 'mobx-state-tree';

export const ROOT_STORE = Symbol('rootStore');

export function storeReference(subType) {
	return types.safeReference(subType, {
		get(id, parent) {
			let root = getRoot(parent);

			if (!/^\$/.test(id)) {
				return resolveIdentifier(subType, root, id);
			}

			let globalStore = root[ROOT_STORE];

			if (!globalStore) {
				throw new Error(
					'substore should have ROOT_STORE volatile field',
				);
			}

			let [, storeId, localId] = /^\$([^$]+)\$(.+)$/.exec(id);

			let store = globalStore.subStores[storeId];

			return resolveIdentifier(subType, store, localId);
		},
		set(value) {
			let root = getRoot(value);
			let storeId = root.storeId;

			if (!storeId) {
				throw new Error('substore should have storeId field');
			}

			return `$${storeId}$${getIdentifier(value)}`;
		},
	});
}

////////////////////////////////////////////////////////////////////////////////

const Global = types.model('Global', {}).volatile(() => {
	return {
		subStores: {},
	};
});

let global = Global.create({});

const Val = types.model('Val', {
	id: types.identifier,
	answer: 42,
});

const StoreA = types
	.model('A', {
		value: Val,
	})
	.volatile(() => {
		return {
			[ROOT_STORE]: null,
		};
	})
	.views(() => {
		return {
			get storeId() {
				return 'storeA';
			},
		};
	});

const StoreB = types
	.model('B', {
		ref: storeReference(Val),
	})
	.volatile(() => {
		return {
			[ROOT_STORE]: null,
		};
	})
	.views(() => {
		return {
			get storeId() {
				return 'storeB';
			},
		};
	})
	.actions((self) => {
		return {
			setRef(value) {
				self.ref = value;
			},
		};
	});

let storeA = StoreA.create({ value: { id: 'test' } });
let storeB = StoreB.create({});

storeA[ROOT_STORE] = global;
storeB[ROOT_STORE] = global;

global.subStores.storeA = storeA;
global.subStores.storeB = storeB;

storeB.setRef(storeA.value);

console.log(storeA.value === storeB.ref); // true

Rulexec avatar May 19 '21 05:05 Rulexec

I have created a PR that adds the functionality expressed in the original PR, for those who have commented please take a look and give some feedback/contribute 🙏

https://github.com/mobxjs/mobx-state-tree/pull/1722

clgeoio avatar Jun 03 '21 02:06 clgeoio

Hey folks, sorry about my silence on this issue. I'm looking at the PR and will talk it over with the core team. It looks like there's a fair amount of support for adding this functionality, and since it doesn't seem to break any backwards compatibility, I'm interested in getting it moved forward.

jamonholmgren avatar Jun 16 '21 20:06 jamonholmgren

Any updates to share about this PR? 🙂

fwouts avatar May 11 '22 02:05 fwouts

Hey folks - sorry to keep kicking the can here, just wanted to pop in to say that I'm adding a label here for better categorization while Jamon and I take some time to clean things up in the repo overall.

coolsoftwaretyler avatar Jun 28 '23 04:06 coolsoftwaretyler

We merged #1722 so I'm gonna close this out! Sorry it took me a bit to go back through the issues and let y'all know!

coolsoftwaretyler avatar Nov 29 '23 07:11 coolsoftwaretyler