farmOS / field-kit

A modular, offline-first companion app to farmOS.
https://farmOS.org
GNU General Public License v3.0
60 stars 39 forks source link

Load Field Modules after Vue app is instantiated. #355

Closed jgaehring closed 4 years ago

jgaehring commented 4 years ago

Working on https://github.com/farmOS/farmOS-client/issues/341#issuecomment-657622417 got me thinking again about when we check the server for what modules are available, and how we load them, particularly how we right now depend on the Vue plugin API (Vue.use(myPlugin) must be called before the app is instantiated). I addressed this initially in https://github.com/farmOS/farmOS-client/issues/309#issuecomment-589247191:

Thinking more about this, we may indeed wish to load modules from within the Vue app itself, to give us more flexibility, but for one thing, I don't think it would be that major of a change to the module architecture. More importantly, just because we're not installing the modules as proper Vue plugins via Vue.use(), that doesn't mean we can't use the same interface. Basically, we're just providing an object with an install method, which takes the Vue object as its first argument, and an optional object as the second argument, where we're supplying the store and the router. So we can just provide our own implementation of Vue.use() within the script loader, if we want to fire it off after the app has been initialized.

For reference, here is where the Vue.use() method is implemented: https://github.com/vuejs/vue/blob/dev/src/core/global-api/use.js. It would be super easy to re-implement ourselves.

The most important use case for this is when the user logs in successfully, whether this is for the first time, before any modules have been loaded, or if it's been a while since using the app and the refresh token's expired, in which case it's likely that there may have been substantial changes to the modules.

The feasibility of moving away from the Vue plugin architecture will hinge primarily on two parts of the code: first, the loadFieldModule function, that loads the actual script:

https://github.com/farmOS/farmOS-client/blob/63dab4bbf970d8b89b01a83a13c20a73cc7f7139/src/core/app.js#L31-L46

and second, the createFieldModule function, which calls the script, registers components, add routes, and commits module config to the store:

https://github.com/farmOS/farmOS-client/blob/63dab4bbf970d8b89b01a83a13c20a73cc7f7139/src/utils/createFieldModule.js#L42-L70

Critically, as far as I know, all the procedures in these two parts of the code can be called after the Vue app has been instantiated, possibly from within a Vuex action, or in the App.vue file from a life cycle hook (or from a Vuex action that gets called from App.vue—that might actually be best).

So far as createFieldModule is concerned, I think we can just discard the install method and the whole wrapper object, and just run the procedures contained within it directly. Then loadFieldModule will not have to call Vue.use() at all, once it's called createFieldModule(). The one tricky thing might be component registration, which requires the static method Vue.component(), but I think we can pass the Vue class to createFieldModule or just import it where it's defined. We'll need to be a little careful about circular dependencies, but I think that will be manageable.

One last, possibly separate issue is whether to have a way of removing modules once they've been added. This would entail removing their references in the Vuex store and localStorage, removing their script tag from the DOM, and making sure the service worker knows to clear them from the cache.