Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Import / Lazy Loading of Stores #1607

Closed
1 task done
clgeoio opened this issue Nov 17, 2020 · 8 comments
Closed
1 task done

Dynamic Import / Lazy Loading of Stores #1607

clgeoio opened this issue Nov 17, 2020 · 8 comments
Labels
has PR A Pull Request to fix the issue is available

Comments

@clgeoio
Copy link
Contributor

clgeoio commented Nov 17, 2020

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?

  • Yes
  • [] No
@clgeoio
Copy link
Contributor Author

clgeoio commented Dec 7, 2020

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

@fwouts
Copy link

fwouts commented Feb 17, 2021

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 🙂

@Rulexec
Copy link

Rulexec commented May 19, 2021

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

@clgeoio
Copy link
Contributor Author

clgeoio commented Jun 3, 2021

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 🙏

#1722

@jamonholmgren
Copy link
Collaborator

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.

@fwouts
Copy link

fwouts commented May 11, 2022

Any updates to share about this PR? 🙂

@coolsoftwaretyler
Copy link
Collaborator

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 coolsoftwaretyler added the has PR A Pull Request to fix the issue is available label Jun 28, 2023
@coolsoftwaretyler
Copy link
Collaborator

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has PR A Pull Request to fix the issue is available
Projects
None yet
Development

No branches or pull requests

5 participants