Open serapath opened 9 years ago
Please read this article on Amok and this article on Amokify for more context
I'll assume browserify-hmr
is basically webpack-hmr, skimmed it and it looks identical. Hot module replacements have issues, it executes the changed code via eval
which is really troublesome, the new code is not available in the debugger for starters. Compared to amok, which does not eval
nor execute anything. The entire script is just updated via the runtime, shows up in the debugger as updated, and works great with developer tools.
Except for the eval vs no eval thing, to make functions live with hot module replacements you would have to wrap all your functions in your entire application. This can obviously be done with a transform, but as far as I know no such transform exists to date.
Lets say such a transform was available, which I would assume @AgentME is already working on since he's laying the groundwork with his ud
library. There's a problem with wrapping every single function at every nesting level, and that is that it is very expensive. You add a lot of indirection which will kill perf.
Take the following example, you can edit this with Amok as is without any code generation or special tricks having to take place, it just tells the debugger to update the source code.
(function() {
setInterval(function() {
(function() {
var greeting = 'hello';
setTimeout(function() {
var subject = 'world';
setTimeout(function() {
console.log(greeting, world);
}, 1000);
}, 1000);
}());
}, 1000);
}());
I'll leave it as an exercise to wrap that with guards.
Now, Amokify does generate code and it wraps top level expressions with with execution guards like those found in that ud
library (might use that btw @AgentME instead of rolling my own, cheers!).
But, it only wraps top level module expressions because they are the only ones that can cause the module to change. Functions are still being kept live by amok after all so the perf impact is essentially none for code that is actually executing.
They seem to have similar goals for live updating code, but take fundamentally different techniques. Amok uses v8 debugger features to update code, and Browserify-HMR and Webpack HMR rely on pure javascript by just re-executing changed modules and relying on the re-executed modules to use the HMR API to allow themselves to handle the specifics of updating the running page.
Using the v8 debugger features appears to allow Amok to update function definitions even within closures and avoid re-executing expressions, which makes it more likely to be useful with code not written specifically with it in mind. However, its dependency on v8's debugger connection has cons too: You can't use it with non-v8 browsers, and you can only use it when you can set up a debugger connection. This makes setting up mobile debugging a bit more complicated (assuming your mobile browser supports it; React Native may be a no-go), and precludes you from using it to hot-patch code for users in production (Yeah, this is a little ambitious of an idea on normal sites, but it could easily be a killer feature for a jsfiddle-like site). You're also limited by what code updates v8 is able to understand. How it will handle very creative changes that rename values or refactor expressions could be very unpredictable unless you're closely familiar with v8's code. (Though this usually isn't a big deal in development if an update is taken wrongly occasionally when you can refresh to get back to a good state.) The HMR way is more explicit about how updates are handled, which does require more cooperation from the code.
@caspervonb Glad to see someone sees what I'm going at with ud. I've been picturing a transform (probably a babel plugin actually so it plays nice with Babel and ES6, gets its AST handling, and isn't Browserify or Webpack specific) which either automatically wraps top-level declarations with ud calls or only wraps top-level declarations that opt in through an ES7 or comment decorator. I'm still not sure on the specifics (should top-level variable declarations by default be wrapped with ud.defonce
so they're only set on the first run? Reading about Amok is pushing me toward that way if I do make it automatic. Should there be a way for a specific declaration to opt out(in) of that? If the code itself uses calls to ud directly in top-level declarations, will the transform play nicely with that? It's important that the transform doesn't interfere with code that has its own idea of how to handle updates or make that more complicated.), so I'm using ud directly in a few applications for now to help figure out what common patterns could be automated.
I don't know if ud's code will directly be very useful for you as-is. The most interesting parts of it are interfacing with the HMR API to implement defonce, and the function/class wrapper to allow the illusion of replacing a function's code (which comes for free properly in the v8 way). The conventions or terminology in ud's API might be useful in thinking about ways that livecoding should work.
Using the v8 debugger features appears to allow Amok to update function definitions even within closures and avoid re-executing expressions, which makes it more likely to be useful with code not written specifically with it in mind.
Basically, the rule of thumb is as long as it compiles it works. Now I'm not advocating that old school jQuery soup is the way to go but Amok would work with it.
You can't use it with non-v8 browsers
True, currently only Chrome and Chromium is supported.
I am likely to support the chakra runtime soon as the runtime apparently supports live function patching. Microsoft also rolls their own bridge for the chrome protocol too so theres very little Amok needs to do in order to support it. We basically just have to spawn their bridge (Written in C/C++) in an package basically.
In the same tune node is also likely to come soon with them supporting the protocol Amok uses directly (chrome debugging protocol).
However, having to depend on a debugger feature that is not universal at the time is troublesome for development, as I can't really just decide to support a runtime because I want to, it needs to have the required functionality.
You can only use it when you can set up a debugger connection.
True, but you also get remote developer tools in the process.
This makes setting up mobile debugging a bit more complicated (assuming your mobile browser supports it; React Native may be a no-go), and precludes you from using it to hot-patch code for users in production
True, currently I'm not too sure how this will work out with JavaScriptCore at all. I've read some commits and patches that points to that this might be a solvable problem, but have not been able to pursue it. Personally its something I want to get, so we can have at-least one runtime per platform (desktop, mobile and web). Until I have a device to test and develop with, it will remain unresolved.
Precludes you from using it to hot-patch code for users in production
Yep Amok is a debugger client, and it's a command line executable so any sort of production mode is nonsensical.
You're also limited by what code updates v8 is able to understand. How it will handle very creative changes that rename values or refactor expressions could be very unpredictable unless you're closely familiar with v8's code.
I'd say this is an untrue statement, If you're writing something like Amok or Amokify, yeah can't be clueless about how it works.
The actual setScriptSource
feature is damn hard to break, I've yet to come up with an example that breaks it.
The HMR way is more explicit about how updates are handled, which does require more cooperation from the code.
You can use HMR to apply side effects after amok has patched things too. When doing R&D with Amokify I considered this, but eventually concluded that I did not need HMR for what I wanted Amokify to do (https://github.com/caspervonb/amok/issues/136) and going with an eval free approach in order to not break other devtools and debuggers view of what a source file contains.
Glad to see someone sees what I'm going at with ud. I've been picturing a transform (probably a babel plugin actually so it plays nice with Babel and ES6, gets its AST handling, and isn't Browserify or Webpack specific) which either automatically wraps top-level declarations with ud calls or only wraps top-level declarations that opt in through an ES7 or comment decorator. I'm still not sure on the specifics (should top-level variable declarations by default be wrapped with ud.defonce so they're only set on the first run? Reading about Amok is pushing me toward that way if I do make it automatic. Should there be a way for a specific declaration to opt out(in) of that? If the code itself uses calls to ud directly in top-level declarations, will the transform play nicely with that? It's important that the transform doesn't interfere with code that has its own idea of how to handle updates or make that more complicated.), so I'm using ud directly in a few applications for now to help figure out what common patterns could be automated.
Since Amokify has the starting point of all functions are already updated its an easy case for me, I only have to make transformations that decide if expressions in the module scope should execute or not since they are the only ones that can cause any side effects.
I don't know if ud's code will directly be very useful for you as-is. The most interesting parts of it are interfacing with the HMR API to implement defonce, and the function/class wrapper to allow the illusion of replacing a function's code (which comes for free properly in the v8 way). The conventions or terminology in ud's API might be useful in thinking about ways that livecoding should work.
Had only skimmed it at the time, It gets close but it does to much :grin:
Anyhow, @AgentME always cool to see more people working in this space. Roll back a year and no one was interested in this :grimacing:
https://github.com/AgentME/browserify-hmr https://github.com/AgentME/ud