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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃檵 Regular Browser Reload on File Save #289

Closed
mattdesl opened this Issue Dec 15, 2017 · 43 comments

Comments

Projects
None yet
@mattdesl
Copy link
Contributor

mattdesl commented Dec 15, 2017

馃檵

Hi there! I'd like to start exploring Parcel as a workflow for creative coding (WebGL, Canvas2D, etc). I'm trying to migrate some programs I've written for non-HMR workflow to this new HMR workflow, and I'm hitting huge performance issues since the code (which is doing things like creating a WebGL/Canvas context) was not designed to be re-run multiple times.

馃帥 Configuration

Take a simple program like this:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

document.body.appendChild(canvas);

馃 Expected Behavior

I'd like to develop with the same way that a JS file runs/loads in the browser (i.e. once, not many times). If possible, I'd like a way to replace JavaScript HMR with a simple window.location.reload() functionality. However, other features (like CSS) should still use HMR / inject without hard reload.

馃槸 Current Behavior

Currently the above code, when saved several times, will create several canvas elements in the body.

馃拋 Possible Solution

A way of turning on/off regular hot reload. I am assuming this may already exist, but I couldn't find it, so perhaps it's more an issue of documentation?

@DeMoorJasper DeMoorJasper changed the title Regular Browser Reload on File Save 馃檵 Regular Browser Reload on File Save Dec 15, 2017

@brandon93s

This comment has been minimized.

Copy link
Member

brandon93s commented Dec 15, 2017

Give --no-hmr a try when you call parcel. It sounds like it may not be exactly what you're looking for, but it could be an improvement.

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Dec 15, 2017

Thanks! But that just turns off reloading altogether... 馃槅

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Dec 16, 2017

For now, I have a branch with the option --reload which will trigger a hard-reload on any non-CSS assets.

https://github.com/mattdesl/parcel/tree/feature/add-js-reload

@davidnagli

This comment has been minimized.

Copy link
Member

davidnagli commented Dec 17, 2017

Honestly I don鈥檛 think this has any practical use cases outside of the one you described.

Is there anywhere else where somebody would actually want this option? If so, feel free to submit a PR with your add-js-reload branch.

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Dec 17, 2017

Honestly I don鈥檛 think this has any practical use cases outside of the one you described.

You mean, creating a new HTML element and adding it to the body? It's really one of the most basic things you can do with JS...

I am genuinely curious how I could change my application to better support HMR.

Here is another example: creating a loop with requestAnimationFrame:

window.requestAnimationFrame(render);
console.log('Starting loop...');

function render (time) {
  window.requestAnimationFrame(render);
  console.log('Rendering', time);
}

If you save the file twice you will end up with two requestAnimationFrame loops running simultaneously.

@davidnagli

This comment has been minimized.

Copy link
Member

davidnagli commented Dec 17, 2017

Simple, just move the animation logic to a separate module, that way you get hot module replacement :)

I didn鈥檛 actually try this for myself, but something like this should work:

index.js

import draw from './draw'

window.requestAnimationFrame(render);
console.log('Starting loop...');

function render (time) {
  window.requestAnimationFrame(render);
  draw(time)
}

draw.js

export default function draw(time){
    console.log('Rendering', time);
    //...
}
@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Dec 17, 2017

@DeMoorJasper The following doesn't work 鈥撀爊othing changes when saving/editing the draw.js file:

import draw from './draw';

document.addEventListener("DOMContentLoaded", function(event) {
  window.requestAnimationFrame(render);
  console.log('Starting loop...');

  function render (time) {
    window.requestAnimationFrame(render);
    draw(time)
    console.log('Rendering', time);
  }
});

@davidnagli That doesn't work either, when I save the draw.js module it triggers a hot reload in my index.js and the loop is re-started (causing duplicate rAFs).

@DeMoorJasper

This comment has been minimized.

Copy link
Member

DeMoorJasper commented Dec 17, 2017

That's why i removed it, i realised it would completely disable HMR @mattdesl

@davidnagli

This comment has been minimized.

Copy link
Member

davidnagli commented Dec 17, 2017

@mattdesl I was able to reproduce your issue, currently working on figuring it out.

@brandon93s

This comment has been minimized.

Copy link
Member

brandon93s commented Dec 17, 2017

For the simple use case, you can do a bit of DOM management to replace instead of append on every change:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

document.body.replaceChild(canvas, document.body.lastElementChild);

Saving this will result in the canvas reloading.

For the animation frame scenario, you can manage the request id:

if (window.currentAnimationFrameId){
    window.cancelAnimationFrame(currentAnimationFrameId)
}

window.currentAnimationFrameId = window.requestAnimationFrame(render);
console.log('Starting loop...');

function render (time) {
  window.currentAnimationFrameId = window.requestAnimationFrame(render);
  console.log('Rendering', time);
}

This isn't "automatic", but with a bit of boilerplate it allows the code to be HMR-compatible.

@jamiebuilds

This comment has been minimized.

Copy link
Member

jamiebuilds commented Dec 18, 2017

I like the idea of a --reload flag, I much prefer it over HMR which I always opt out of.

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Dec 18, 2017

@thejameskyle Interesting, why do you opt out?

Personally I would rather --hmr be opt-in than on by default, since it鈥檚 far more magical than just a simple page reload.

@TennyZhuang

This comment has been minimized.

Copy link

TennyZhuang commented Dec 30, 2017

+1 on --reload

@TennyZhuang

This comment has been minimized.

Copy link

TennyZhuang commented Dec 30, 2017

Hi everyone, if you really need regular reload but not HMR, you can try my fork https://github.com/TennyZhuang/parcel

just npm install git+https://git@github.com/TennyZhuang/parcel

Note that I have not make regular reload as an option now, so you can use it as same as the upstream, and there will always be a browser reload after every change.

I will try to make this as an option and make a merge request to the upstream if needed, @devongovett @brandon93s

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Dec 30, 2017

@TennyZhuang did you see my fork? It includes a 鈥榬eload鈥 option.

I can submit it as a PR. Personally I think it should be the default, but I guess my goals are a little different than those of the Parcel maintainers.

@TennyZhuang

This comment has been minimized.

Copy link

TennyZhuang commented Dec 30, 2017

@DeMoorJasper

This comment has been minimized.

Copy link
Member

DeMoorJasper commented Dec 30, 2017

@mattdesl feel free to submit a PR with it, would probably be much appreciated

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Dec 30, 2017

HMR is an important feature in parcel and faster than regular reload, I think HMR as default behavior is reasonable, but i really need regular reload in my three.js development.

It's not really faster 鈥撀爄n my own tests I've found parcel reloading to be pretty much comparable to budo, which uses hard page reloads. With small to medium bundle sizes, budo seems to reload faster, and with huge bundles (several MBs), parcel seems faster.

Currently HMR is not even working in Parcel as pointed out earlier in this thread. I don't really understand how anybody is using this in production right now. 馃し

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Dec 30, 2017

P.S. Submitted a PR. 馃槃

#443

@devongovett

This comment has been minimized.

Copy link
Member

devongovett commented Dec 31, 2017

Commenting here what I wrote on the PR so others can see. Seems to me like you could just hook into the existing HMR system to do a full page reload if you really wanted to.

if (module.hot) {
  module.hot.accept(function () {
    window.location.reload();
  });
}

However, I'm wondering why this is really needed? HMR is powerful because it allows you to maintain the state of your application across code changes, so you don't need to e.g. re-open a modal and click through 17 steps each time you make a change to the code.

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Jan 1, 2018

@devongovett Yeah, that was the first thing I tried, although it feels like a hack. When I save a file, it triggers a hot module reload, so my code gets re-executed before the hot module replacement reloads the page.

This means any blocking code will slow down the reload cycle, and I end up with more memory usage during development. (Often the start of the application will create a WebGL context, generate geometries, and push features onto the GPU.)

However, I'm wondering why this is really needed? HMR is powerful because it allows you to maintain the state of your application across code changes, so you don't need to e.g. re-open a modal and click through 17 steps each time you make a change to the code.

I agree it can be very powerful, but only if the code is setup to work with it, and only in specific applications (e.g. React, Vue, etc). I've never been able to take advantage of application-wide HMR in a real WebGL project because of GL state, performance constraints, and things like that.

Anyways... as pointed out in this thread, hot module replacement is not working in Parcel, which is probably part of the reason some people are asking for a --reload option. Right now, any module change in your application will trigger a root-level reload, which means there is currently no clean way to avoid problems like window event listeners doubling up, simultaneous requestAnimationFrame loops, etc.

@devongovett

This comment has been minimized.

Copy link
Member

devongovett commented Jan 3, 2018

You could do something like this if you don't want the module to be re-executed:

if (module.hot) {
  module.hot.dispose(function () {
    window.location.reload();
  });
}

This will trigger the reload on module dispose rather than after the module has been re-executed.

You could also use that hook to store your state for later, and on accept restore it. HMR does take some work to get right, which is why things like react-hot-loader exist. Parcel is pretty much agnostic to that: it gives you hooks for when a module changes, it's up to you to decide what to do with that.

Right now, any module change in your application will trigger a root-level reload

That shouldn't be the case. The event starts at the module which changed, and bubbles up to the root. If you accept an update, the event stops bubbling up.

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Jan 3, 2018

That shouldn't be the case. The event starts at the module which changed, and bubbles up to the root. If you accept an update, the event stops bubbling up.

Ok 鈥 this was never clear to me. It might be good to explain the system in the docs somewhere, rather than assume everybody is using React/Vue/etc.

So, how does one go about "setting up" HMR without copying the internals of react-hot-loader or similar modules? I really just want a basic app that reloads on file save (hence my PR), and I'd rather not add a deal of boilerplate to each file or have to carefully step around my code not to accidentally "Cmd + S" a certain file in case it duplicates window state.

The code here pretty much sums up the application I would like to build:
#289 (comment)

Or is it basically React-or-nothing?

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Jan 3, 2018

P.S.

To illustrate the issue with reload and thread blocking, try this example:

index.js

import * as THREE from 'three';

console.log('Creating...');
const sphere = new THREE.SphereGeometry(1, 512, 512);
console.log('Done');

if (module.hot) {
  module.hot.accept(() => {
    // or use this instead of dispose()
    // window.location.reload();
  });

  module.hot.dispose(() => {
    window.location.reload();
  });
}

Whether using dispose or accept, the JS blocks and waits for the geometry to be generated. So you end up waiting twice as long: once before the reload is triggered, and once after the page actually reloads. :\

@pohy

This comment has been minimized.

Copy link

pohy commented Jan 26, 2018

@brandon93s Thanks, your solution works perfectly for me. Hence, I now have no need for the --reload option.

@jaredramirez

This comment has been minimized.

Copy link

jaredramirez commented Jan 27, 2018

+1 on wanting an --reload option (or something). When using elm-lang in fullscreen mode, an entirely new instance of the elm app is appended to the DOM on any .elm files saves.

Using

if (module.hot) {
  module.hot.dispose(() => {
    window.location.reload();
  });
}

works, but there still is an second where the new content flashes before the page reload occurs. Plus still having .css module replaced would be ideal

@devongovett

This comment has been minimized.

Copy link
Member

devongovett commented Apr 8, 2018

What if we automatically called window.location.reload() for you if the HMR event bubbled all the way to the top without any module calling module.hot.accept()? Then, by default, you'd get window reloading, but if you added HMR code to handle events yourself, it would not reload. No option needed! Thoughts?

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Apr 13, 2018

@devongovett This makes sense to me, I think that's what webpack dev server does in hot mode.

@jamiebuilds

This comment has been minimized.

Copy link
Member

jamiebuilds commented Apr 13, 2018

What about with asset types that implement hot module reloading themselves? Like it seems reasonable to make all CSS files support hot module reloading by default, but if you wanted to disable that behaviour for some reason, how could you?

@buckle2000

This comment has been minimized.

Copy link
Contributor

buckle2000 commented May 30, 2018

I found a hacky solution to reload immediately:

// put this at the top of your code
if (module.hot) {
    module.hot.dispose(() => {
        window.location.reload();
        throw 'whatever'
    })
}

Or...

if (module.hot) {
    module.hot.dispose(() => {
        window.location.reload();
    })
} else { do_something(); }

sampsyo added a commit to cucapra/linguine that referenced this issue Jul 24, 2018

Fix problem with Parcel reload
Parcel uses "hot module replacement" (HMR), which has quite intuitive
problems with `requestAnimationFrame`-based animation:
parcel-bundler/parcel#289

We now use a pretty ridiculous hack to cancel previous animation loops
when setting up new ones for our examples.
@Teoxoy

This comment has been minimized.

Copy link

Teoxoy commented Sep 15, 2018

I would like to have this functionality as well.

What if we automatically called window.location.reload() for you if the HMR event bubbled all the way to the top without any module calling module.hot.accept()? Then, by default, you'd get window reloading, but if you added HMR code to handle events yourself, it would not reload. No option needed! Thoughts?

Would this get implemented?

@nnmrts

This comment has been minimized.

Copy link

nnmrts commented Oct 24, 2018

I would love to have a --reload feature. Maybe I'm a bad developer and can't get my app to work perfectly with HMR, but never ever did a bundler send my fine working app in an infinite loop and crashed my browser tab when I save a file before. This is kinda frustrating.

@mattdesl

This comment has been minimized.

Copy link
Contributor Author

mattdesl commented Oct 24, 2018

For anyone who is looking for a zero-config tool for canvas/WebGL prototyping, I ended up building my own tool, canvas-sketch, that approaches (hot) reloading differently, in a way that is more suitable for requestAnimationFrame and so on.

@steve-king

This comment has been minimized.

Copy link

steve-king commented Oct 26, 2018

I'm running Parcel in a Django CMS setup. The HMR is fantastic for all my JS and CSS changes, however ParcelJS doesn't know anything about my Django HTML templates at this stage, nor is it possible or practical for Parcel to be used to bundle them in any way.

As such I don't get a reload when I update my templates. It would be great if I could just give Parcel the path to my templates directory and tell it to perform a hard reload whenever a change is detected there, but keep HMR support for the other stuff.

I know I should just implement browsersync for this sort of thing but it would seem a shame to add that as Parcel already handles almost everything I need/want for so little effort.

@steve-king

This comment has been minimized.

Copy link

steve-king commented Oct 26, 2018

Is it/could it be possible to initiate a hard reload over the websocket from a custom plugin?

Being able to execute custom code on the client side could be pretty powerful (I have no idea if this already exists though)

@AlexLomm

This comment has been minimized.

Copy link

AlexLomm commented Dec 3, 2018

Bump for the --reload feature. The js' hmr behavior is too brittle to even be the default reloading option, let alone the only one.

It can potentially lead to myriads of nasty bugs. Especially, in the hands of less-experienced developers. I'm not sure whether the hmr is worth such a price.

Thanks for the amazing work, btw.

@slikts

This comment has been minimized.

Copy link

slikts commented Dec 23, 2018

I second this; I often have to manually restart the dev server when developing since it just stops working. That's on top of it getting stuck in an endless loop as per #1317.

@devongovett devongovett added the HMR label Jan 5, 2019

@paulosborne

This comment has been minimized.

Copy link

paulosborne commented Jan 10, 2019

+1 for --reload option

@nnmrts

This comment has been minimized.

Copy link

nnmrts commented Jan 17, 2019

Guys, let's not get this to a 3130 level. @paulosborne, you would +1 it? Just hit the thumbs up button on the first post.

Thanks.

@mischnic mischnic referenced this issue Jan 17, 2019

Open

Add autorun for NodeJS builds #2547

0 of 3 tasks complete

lambdalisue added a commit to lambdalisue/typescript-browser-sandbox that referenced this issue Feb 21, 2019

Use FuseBox instead of Parcel
Because Parcel

- Could not serve static files
  parcel-bundler/parcel#1080
- Could not use native reload on HMR
  parcel-bundler/parcel#289

lambdalisue added a commit to lambdalisue/typescript-browser-sandbox that referenced this issue Feb 21, 2019

Use FuseBox instead of Parcel
Because Parcel

- Could not serve static files
  parcel-bundler/parcel#1080
- Could not use native reload on HMR
  parcel-bundler/parcel#289
@zoutepopcorn

This comment has been minimized.

Copy link

zoutepopcorn commented Feb 21, 2019

I am using a workaround.

Because I'm getting an error when parcel is updating: 'parcelRequire' of undefined

// page reload on parcel hmr
window.onerror = function(e) {
    if(e == `Uncaught TypeError: Cannot read property 'parcelRequire' of undefined`) {
        console.log('yess')
        location.reload();
    }
};
@mischnic

This comment has been minimized.

Copy link
Member

mischnic commented Feb 23, 2019

So, should both of the suggested solutions be implemented?

  • --reload flag for cases where HMR doesn't work at all/causes an infinite loop, an explicit opt-out
  • Automatic reload when hmr accept bubbled up and wasn't accepted (would be somewhat similar to webpack's default, seems a better default anyway): #289 (comment)
@richyliu

This comment has been minimized.

Copy link

richyliu commented Feb 28, 2019

If you want to reload after file changes, you can run Parcel in watch mode, which compiles changes to another directory (default ./dist/) but does not run a server.

parcel watch index.html --no-hmr

Then run a live-server, which listens to file changes and reloads (assuming your output is to the ./dist/ directory)

live-server ./dist/

Here's what my npm start script looks like:

parcel watch ./src/index.html --no-hmr & live-server --port=8000 ./dist/

@devongovett

This comment has been minimized.

Copy link
Member

devongovett commented Mar 5, 2019

In #2676 which just landed in master, the default was changed to reload the page unless you call module.hot.accept() in your app. We found that HMR seems to fail more often than not, so made it an opt-in feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can鈥檛 perform that action at this time.