-
Notifications
You must be signed in to change notification settings - Fork 11
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
State Hydration Proposals/Discussion #2
Comments
@peteruithoven Thanks for taking the time to comment! Also, thanks for the link, I had no idea people had been discussing this. import module from '@module'
//this idea could be extended in a few ways by
//exporting the unload function here instead of globally
module.unload = function() {
// do something. Notice we no longer need access to the oldModule as entry... Since it is in scope.
}
//we could pass in other information (not claiming this is a good idea, but) similar to node
//https://nodejs.org/api/modules.html#modules_module_filename
module.filename == 'someFile'
module.children // dependents But even if module (or some other name) is just an empty object, this would solve the use case beautifully, and I don't think this would be hard to implement via a custom loader either. Depending on whether or not the loader knows which file the import originated from. I suspect not. But either way. This would be an incredibly valuable extension. Finally, in terms of GC, i think the things being persisted to this store (@module) would be precisely the things the developer means to persist (and would do via another method if not here). |
As in, the previous module would be How would someone access his old state from I would personally keep the content of that persistent private object up to the developer. I would certainly not automatically store a reference to the whole module, since that might encourage exporting the state, just for hot reloading, which I feel is bad practice. Being able to retrieve extra information about the module sounds interesting, but I wouldn't mix that with persistent storage. Maybe two possible imports could be provided; Not that I'm against but, why would you put the reload hook in |
I'm not suggesting we store a reference to the whole module. Only that it is a private store available for write. Perhaps module would be a bad name, since it leads to this confusion.
This could work
The reason the unload hook might work better in the '@hot' module would be that then its not being exported to the whole world. With regards to getting previous state, this would amount to accessing data stored in @hot. So: import hot from '@hot'
const incrementNumber = () => {
hot.numberToPersist++;
}
//here we store some data we want persisted
if (hot.reloading) {
console.log("reloading with number ", hot.numberToPersist)
} else {
//here we store some data we want persisted
hot.reloading = true
hot.numberToPersist = 42
hot.unload = function() {
incrementNumber()
// As far as I can see we don't need access to the 'old module'.
// Since we have the hot store. Which can be used to do the same thing really.
// And like this we don't export unload to the whole world.
// Just a thought...
}
console.log("first load with number ", hot.numberToPersist)
}
console.log("initializing with number ", hot.numberToPersist) Sorry, I know this example of usage is messy. But I wanted to show usage from a number of cases. |
Like! |
Awesome! Maybe we can get some comment from our sensei @guybedford |
ping @guybedford |
Ok, I appreciate the argument that the Then the only way to get this would be if the old module itself was available through an import, or an import API. We can't have the old module available as an import though, because it is undefined when starting up, so the remaining option is an API to get the previous instance. The problem with an implicit I guess I do think an API would be the best alternative. The issue with a state store, is that then you do just have a generic state store - the problem space completely changes from storing the previous instance state, to all the Redux-ish problems of ensuring a good state store API. It becomes an easy slope where the API suddenly becomes a source of truth of application state when all it was supposed to be was a hot reloading interface. So having the ability to get the previous module instance with publicly exported state, or at least a public "getState" function (which is probably the best practice anyway), would be the better way to go about this. Also by having a readable state only, it avoids further abuse. It would be nice if this could just work via: var previousInstance = System.get(__moduleName);
let previousState = previousInstance.getState();
previousInstance.unload(); But we do need to delete the previous instance from the registry before kicking off the loading process unfortunately. So perhaps just something like: import { getPreviousInstance } from '@hot';
let previousInstance = getPreviousInstance(__moduleName);
let previousState = previousInstance.getState();
previousInstance.unload(); It would be possible for hot-reloader-internals.js var instance;
function getPreviousInstance () {
if (!instance && instance !== false)
throw new ReferenceError('getPreviousInstance can only be called during immediate module execution');
return instance;
};
function hotReload(moduleName) {
var m = System.get(moduleName);
System.delete(moduleName);
System.load(moduleName).then(function() {
// this triggers execution - load is non-executing
instance = m || false;
System.get(moduleName);
instance = undefined;
});
} I can't drive any of this work though, so take what you want from all that - no permission is needed :) |
@guybedford Thanks for taking taking the time to answer. I see your point about doubling the size of the module registry, and I agree, the import semantics we were speaking about do feel hacky. I think the best option here would be to expose a @peteruithoven what are your thoughts on the following API. import { getPrevious } from '@hot'
const previous = getPrevious(__moduleName)
// State hydration could be handled like this
const state = previous ? previous.state : {}
// people could define their own unload rules via
if (previous)
previous.unload()
const unload = () => { console.log("I just unloaded something") }
export { unload, state } Allowing the user to handle their own unload logic is nice, but I still think we might need to allow them to export a named function (maybe Even if this isn't the case for JS. I think loaders should be able to describe an unload process as described here https://github.com/alexisvincent/systemjs-hmr#loader-plugin-unload-hook. With regards to implementing the above, I'm thinking that This is rather cool as it simplifies how HMR is currently performed and is really simple to implement. |
@peteruithoven thoughts? |
Are there no hooks that allow checking something before checking the module registery? Just like there are hooks that allow transpilation or loading files from other places for example.
I don't really see the problem of "a generic state store", and what Redux-ish problems are (I'm a Redux user btw). Especially since it's so similar to how my simple systemjs-hot-reloader-store utility works.
Are we sure there is no way to have @hot figure this out automatically? |
@peteruithoven I just checked the module loader spec and it turns out that the loader hook accepts as an argument the name of the importer module. Meaning that this whole thing can probably be implemented as a loader plugin. The only complication would be that the loader would need access to a global registry, but if there are no issues with loading order this should be pretty simple. This allows us to do a number of things. We can import module directly here, since the '@hot' loader knows which module this is. import { module } from '@hot'
const __state = module ? module.__state : {}
if (module)
previous.__unload()
const __unload = () => { console.log("I just unloaded something") }
export { __unload, __state } we could also import { module, state } from '@hot'
if(module) {
// function exported from module
module.__unload()
// function set on state
state.__unload()
console.log('reloading')
} else {
console.log('initial load')
}
// unload function exported on state
state.__unload = () => {
console.log("unload function from state")
}
// unload function exported on module
const __unload = () => {
console.log("unload function from module")
}
export { __unload } I'm slightly more wary of the second one (even though this was my first choice before), since people might misuse it, as Guy said:
On the other hand, exporting state you want to persist isn't "nice". Another issue I can see when relying on the first step would be the fringe case where people export a bunch of functions from a module (including __state/__unload/whatever) and then another module wants to import * as something from './something.js'
something.forEach(f => f()) While this is a fringe case, it at the very least prevents folks from implementing a pattern like this from that module. I really doubt this is an issue, but just throwing it out there. @guybedford comment? |
The loader plugin, how would it retrieve the module name? If it has the module name, why does it need the global registry? |
Sorry, I meant to say the normalize hook. So the code I'm suggesting would look very roughly as follows System.set('_hot', loader.newModule({
repository: {},
meta: {}
})
const oldNormalize = System.normalize;
/*
* name: the unnormalized module name
* parentName: the canonical module name for the requesting module
* parentAddress: the address of the requesting module
*/
function normalize(name, parentName, parentAddress) {
System = this;
// Or something to this effect
if (name == '@hot')
return oldNormalize(parentAddress + parentName) + "@hot"
else
return oldNormalize(name)
}
function instantiate(load) {
// an empty return indicates standard ES6 linking and execution
return;
moduleRepo = System.get('_hot').repository[load.name]
// a return value creates a "dynamic" module linking
return {
deps: [],
execute: function() {
return loader.newModule({
module: moduleRepo.module,
state: moduleRepo.state
});
}
};
} |
Ok, so I've written and published a complete rewrite of SystemJS HMR (significantly simpler). systemjs-hmr 'next' supports the following api(s) import { module } from '@hot'
export const state = module ? module.state : {}
// module == false on first load
if(module)
module._unload()
export const _unload = () => console.log('unload something') Note that unload is not an API I expose, but rather a simple export. You can also import { _state } from '@hot'
// state == {} on first load
_state.something = 'something' Where state is a persistent object (you will get the same object back on every reload). However this is just here for experimentation. It is not likely to stay unless someone can convince me to keep it. To use the new systemjs-hmr, simply import 'systemjs-hmr/dist/next' I have submitted a PR to systemjs-hot-reloader. And once that is approved you will be able to import 'systemjs-hot-reloader/next/default-listener.js'
or
import HotReloader from 'systemjs-hot-reloader/next/hot-reloader.js' |
README has been updated to explain how this works. |
@guybedford, would really appreciate your input here |
@alexisvincent the approach can certainly adapt to new changes in SystemJS, just note that every major may break the API you are relying on. I'm happy to assist with the upgrade paths as we hit them if you have any questions. |
Great thanks! |
As I can't comment on he readme I'm using this issue.
Great write up of Javascript Plugin Reload Hook solutions.
I wrote the original __reload hook (alexisvincent/systemjs-hot-reloader#23) but I agree that this complicates things, as it runs after the regular initiation is run, you'll need hooks to adapt what's already initiated (like Redux's replaceReducer).
You might find the following discussion interesting: alexisvincent/systemjs-hot-reloader#34
For example Proposal #3 would give you access to the old module, but a good point of mikz is that this requires you to make your state public (export it).
I like my last idea: alexisvincent/systemjs-hot-reloader#34 (comment), which I turned into a simple utility: simple systemjs-hot-reloader-store utility. This enables you to use the old state during the regular initiation of the module and doesn't require exporting the state. It also doesn't require any special hooks.
It's big downside however is that you manually need to pick a unique name.
Maybe there is a way to enable every module to import a
@hotState
, which is aunique object per module. This can then used to store state in and on reload, when it's there, it can be used for rehydration. This fixes the unique name issue and the module author can decide if he want's to store anything and what is stored. (I stole the name from: alexisvincent/systemjs-hot-reloader#34 (comment))
The text was updated successfully, but these errors were encountered: