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

Hot module replacement? #295

Closed
bengourley opened this issue Oct 26, 2016 · 13 comments
Closed

Hot module replacement? #295

bengourley opened this issue Oct 26, 2016 · 13 comments

Comments

@bengourley
Copy link
Contributor

So I'm going to start with the the premise that Hot Module Replacement™ should be as easy and pluggable as https://github.com/substack/react-starter-hmr, with minimal change to the client application.

screen shot 2016-10-26 at 11 43 51

This is a rough-guide on what is required to get it set up:

  1. Install the dependencies: npm i --save-dev browserify-hmr ud
  2. Add the plugin to the browserify/watchify bundler watchify client.js -p browserify-hmr ...
  3. Add the ud.defn(...) annotation to the part of the application to show which bits of the program are suitable for hot-reload (per screenshot above)
  4. Watch view updates appear insta-matically in the page without losing state

My trouble is that with step 4. I couldn't find a way with choo to force a re-render of the page's current state like in the React example. I got close in two different ways - forcing a reboot of the entire app (so reverting to initial state) or views updating the next time they were rendered (not insta-matically).

If anyone knows the secret to getting this set up, please tell! Otherwise if a change is required in choo to facilitate this, let's figure it out? I think it's going to be something like exposing a way to call render/re-render, unless I'm missing something!

@timwis
Copy link
Member

timwis commented Oct 26, 2016

Excited for this. This is probably a hacky idea, but the page should be re-rendered any time a reducer is fired, regardless of whether the state was updated. So you could inject a reducer that simply returns the current state, thereby not changing it but re-rendering the page anyway. But I'm sure there's a more elegant way if we change something in choo.

@bengourley
Copy link
Contributor Author

Something magic (read: something I haven't figured out yet 😁 ) causes the React example to re-render, even though it seems to just be updating the class/prototype of the <App /> component.

@timwis I'm going to give your suggestion of a noop-y reducer a try and see if that sorts us out!

@bengourley
Copy link
Contributor Author

bengourley commented Oct 26, 2016

Ok, brace yo'selves… here's how HMR is achievable in choo right now:

  • npm install --save-dev browserify-hmr ud
  • add -p browserify-hmr to your browserify options
  • every "route" (i.e every view that you provide to the router) must be wrapped in the ud.defn() call like so:
module.exports = ud.defn(module, (state, prev, send) => {
  return html`
    <div>Blah</div>
  `
})

(here's the nastiest bit!)

  • define a noop reducer and a subscription that repeatedly calls it:
  ...
  reducers: {
    noop: (action, state) => ({})
  },
  subscriptions: [
    (send, done) => {
      setInterval(() => send('noop', done), 1000)
    }
  ]
  ...

So I'm going to try to distill all of this down to what I think are the two functional requirements needed to get a decent dev set up…

  1. allow the user to hook into choo's render and wrap it, which will become the single place ud.defn(...) needs to be added to the codebase
  2. provide a way to trigger a render independent of state changes

The final piece of the puzzle is knowing when to call 2.. I'm still baffled by how the React one works. Does React use a render/digest loop? As far as I can see, browserify-hmr nor ud provide "events", but I'm sure we could hook into them somehow. The former logs a message to the console when it hot-swaps a module in.

@bengourley
Copy link
Contributor Author

Noting down @yoshuawuyts hot reload proof of concept here https://github.com/yoshuawuyts/hot-app-replacement/tree/choo

I think this is interesting but as far as I can see it

  1. saves app state
  2. reloads the entire bundle
  3. loads app state
  4. re-boots the entire app

?

I think it would be cool if we could leverage these community projects and also benefit from actual hot-swaps of updated modules, rather than sending the entire bundle. I can see the benefit of the entire reload, if any views have any state that's not stored in a model obviously this would help.

@yoshuawuyts
Copy link
Member

I'd favor the approach in hot-app-replacement because:

  1. it's simpler to maintain - browserify-hmr has proven to be buggy, and
    I've found it complicated to debug
  2. it's simpler to reason about - people can jump in and write their own
    implementations / contribute
  3. with choo apps it's an anti-pattern to have state live outside choo;
    should probably write a bit more in depth
    about this, but some widgets aside (hey maps) all state should merrily be
    handled by choo for transparancy
  4. watchify performs incremental rebuilds; the cost of then sending the
    full bundle over the loopback interface is negligable
  5. using the approach in hot-app-replacement also works for CSS, removing
    the need for some different CSS solution

Hah yeah so that's about it - obviously you're free to do what you want,
but this is my take on it (:

On Wed, Oct 26, 2016 at 6:59 PM Ben Gourley [email protected]
wrote:

Noting down @yoshuawuyts https://github.com/yoshuawuyts hot reload
proof of concept here
https://github.com/yoshuawuyts/hot-app-replacement/tree/choo

I think this is interesting but as far as I can see it

  1. saves app state
  2. reloads the entire bundle
  3. loads app state
  4. re-boots the entire app

?

I think it would be cool if we could leverage these community projects and
also benefit from actual hot-swaps of updated modules, rather than sending
the entire bundle. I can see the benefit of the entire reload, if any views
have any state that's not stored in a model obviously this would help.


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
#295 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACWlevJdm_Fim_PYDb7PCOyMZA_EJYznks5q34bUgaJpZM4KhBw_
.

@bengourley
Copy link
Contributor Author

@yoshuawuyts ok you have me convinced! Can we put together a list of todos in order to have this packaged up and ready to go?

I guess the only one reliant on you is the merge/publish of new barracks version? I'm happy to work on some of the other bits if we define what they are.

@yoshuawuyts
Copy link
Member

@bengourley

  • create a lil plugin that can be loaded into choo (choo-hmr perhaps?)
  • find a good Server Sent Events (SSE) implementation to send the bundles through; websockets usually requires compiling native code which is not great
  • patch bankai to use the server implementation

Something like this?

@bengourley
Copy link
Contributor Author

Sounds good @yoshuawuyts :D I might be able to get started on choo-mhr this week. It's going to be super useful to me on a project I'm working on.

@bengourley
Copy link
Contributor Author

bengourley commented Nov 3, 2016

[Edit: I hit cmd enter too soon and posted this before it was finished 😬 ]

So I've come up with a solution! Anyone want to try this out?

npm i --save choo-resume hot-rld

Attach the choo plugin. This saves the state on the window, and wraps the initial state to load from the window if it exists.

const app = require('choo')()
const resume = require('choo-resume')
app.start('#app-root')

Run the standalone SSE server. This watches the -b path on disk and notifies the client to hot-swap the <script> tag with source equal to -s.

./node_modules/.bin/hot-rld -b static/js/bundle.js -s /static/js/bundle.js

In your server rendered html output the client that connects to the SSE server.

const client = require('hot-rld/client')
const html = `<script>${client()}</script>`

Concerns

Since this reloads the entire app bundle, any side effects of the app, including timers, event listeners and other subscription-like behaviour will happen multiple times.

Crude example, causes a duplicate interval to be set up every reload:

subscriptions: [
  (send, done) => setInverval(() => console.log('boop'), 5000)
]

I can't think of a good way of solving this, other than sticking the state in local storage and doing a full page reload. But then it's not really a hot-reload, more like a snapshot/resume tool (aside, that's not very hip and trendy, but it's still a useful and workable solution, perhaps we should run with that?). This is why my gut feel previously was to go the hot-module-swap because then you can just swap out the immutable parts of choo and be guaranteed to not have side effects.

Thoughts❔❔❔

@yoshuawuyts
Copy link
Member

@bengourley oohhhh really digging this; might just work on top of this from choojs/bankai#86 - use the same client, but use bankai as the server. 😁

Is there a way we could make this also work for CSS ? perhaps have different push channels that set the script and css individually? If we document the wire protocol then we could have compatible client and servers and like be super happy with all of it ✨

@bengourley
Copy link
Contributor Author

bengourley commented Nov 3, 2016

@yoshuawuyts I decoupled the cli/standalone server from the sse route handler, so if you pull in require('hot-rld/server')(bundlePath, sourcePath) you get a fn with signature (req, res) => {} which you can mount wherever you like on a pre-existing server. You then just need to provide the address argument to the client script generator client('http://myhost:1234/path-to-where-sse-was-mounted')

As far as CSS is concerned, yeah sure we could namespace the sse events into channels. FYI I don't know if you dug deep into the code, but I'm only sending tiny payloads (i.e. the path of the file that needs replacing, not the source of the bundle) and the browser is re-requesting the entire bundle from wherever it was served initially.

@bengourley
Copy link
Contributor Author

I've published v1 of both https://github.com/bengourley/choo-resume and https://github.com/bengourley/hot-rld.

@yoshuawuyts hot-rld now supports css too.

@yoshuawuyts
Copy link
Member

Module reloading is part of bankai now too, couple with https://github.com/yoshuawuyts/choo-reload for SSE based reloading ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants