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

Add basic HMR integration to Blaze #313

Merged
merged 3 commits into from
Mar 20, 2021
Merged

Conversation

zodern
Copy link
Member

@zodern zodern commented Jan 14, 2021

You can try this integration in apps using Meteor 2 by running:

meteor add hot-module-replacement blaze-html-templates@1.2.0-beta.2

To also use HMR for javascript files that use the Template api, run

meteor add blaze-hot

Spacebar Templates

When HMR is enabled, spacebar templates are automatically updated with HMR. To apply changes, all of the root views are destroyed and recreated (no template instance state is preserved). If this causes problems for some apps, we can provide a way to disable it.

When the blaze-hot package is added, it instead only updates the blaze views for the modified templates. This is much faster, and preserves state for parent and sibling templates. However, it might not be compatible with all frameworks built on Blaze and with code that accesses the DOM of children templates.

JS files that use the Template API's to add helpers, event maps, and callbacks

The blaze-hot package is able to automatically update files that use the Template API's. There are two types of files that are automatically updated with HMR:

  1. The file imports meteor/templating or meteor/blaze and it does not have any exports
  2. Or the file is only imported by files that meets the requirements in the previous item.

The blaze-hot package keeps track of helpers, callbacks, event maps, etc. that are added when a module is first ran. When the module is updated, it removes any helpers, callbacks, event maps, etc. the old version of the module added. Any that are added after the module is initially ran, such as in Meteor.startup or in a callback are not tracked and automatically removed.

When possible it tries to only update the Blaze views for the modified templates, without affecting parent or sibling views so their state can be preserved. In some cases it has to update parent views instead or update all root views, especially when modified templates use or are used as custom block tags.

When a file is updated, Meteor calls any dispose handlers for the old version, and then runs the new version. Depending on what else the file does (creating collections, Method stubs, Tracker autoruns outside of a Blaze template, etc.), it might not support having two versions of the file running in the app. In that case, you can either:

  1. Move the parts that can't be re-run into a separate file
  2. Write a HMR dispose function to clean up the old version (for example, to stop autoruns)
if (module.hot) {
  module.hot.dipose(() => {
    // clean up here
  });
}
  1. add this to the file to prevent it from being re-ran with HMR. When this file would need to be re-run, it uses hot code push instead of HMR.
if (module.hot) {
  module.hot.decline();
}

@namenotrequired
Copy link

namenotrequired commented Jan 19, 2021

I'm excited about this!

When I created a new app using this from the beginning, it worked wonderfully.

When I updated an existing app, client changes wouldn't propagate. Instead I'd get the below messages in the console

EDIT: thanks Zodern, I guess that was it, I restarted and cannot reproduce the issue.

@zodern
Copy link
Member Author

zodern commented Jan 20, 2021

@namenotrequired did you restart Meteor after adding the hot-module-replacement package? It looks like Meteor didn't start the HMR server.

@softwarerero
Copy link

I updated an older app to 2.0-rc.3 and get errors on Firefox 83. I also deactivated the browser-policy package to be sure nothing blocks.

Firefox can’t establish a connection to the server at ws://localhost:3080/sockjs/378/o6c32yzj/websocket. sockjs-0.3.4.js:1283:14
Firefox can’t establish a connection to the server at ws://localhost:3080/__meteor__hmr__/websocket. hot-module-replacement.js:243:11
error
bubbles: false
cancelBubble: false
cancelable: false
composed: false
currentTarget: null
defaultPrevented: false
eventPhase: 0
explicitOriginalTarget: WebSocket { url: "ws://localhost:3080/__meteor__hmr__/websocket", readyState: 3, bufferedAmount: 0, … }
isTrusted: true
originalTarget: WebSocket { url: "ws://localhost:3080/__meteor__hmr__/websocket", readyState: 3, bufferedAmount: 0, … }
returnValue: true
srcElement: WebSocket { url: "ws://localhost:3080/__meteor__hmr__/websocket", readyState: 3, bufferedAmount: 0, … }
target: WebSocket { url: "ws://localhost:3080/__meteor__hmr__/websocket", readyState: 3, bufferedAmount: 0, … }
timeStamp: 1300
type: "error"
<get isTrusted()>: function isTrusted()
<prototype>: EventPrototype { composedPath: composedPath(), stopPropagation: stopPropagation(), stopImmediatePropagation: stopImmediatePropagation(), … }
HMR: websocket closed hot-module-replacement.js:247:13

With Chromium 85 I get:

modules.js?hash=08a0915c6fb42438520594c652463183521f87c2:29763 Uncaught Error: Expected template rendered with Blaze.render
    at Object.Blaze.remove (view.js:674)
    at templating-runtime.js?hash=c9151dde82872fbfc0f1ae1dc28267b3ec7fb84d:128
    at wrapped (modules.js?hash=08a0915c6fb42438520594c652463183521f87c2:29759)
Blaze.remove @ view.js:674
(anonymous) @ templating-runtime.js?hash=c9151dde82872fbfc0f1ae1dc28267b3ec7fb84d:128
wrapped @ modules.js?hash=08a0915c6fb42438520594c652463183521f87c2:29759
setTimeout (async)
(anonymous) @ modules.js?hash=08a0915c6fb42438520594c652463183521f87c2:29813
Template._applyHmrChanges @ templating-runtime.js?hash=c9151dde82872fbfc0f1ae1dc28267b3ec7fb84d:127
(anonymous) @ app.js?hash=551950deedb96cf6f4a03f56c2246f3c1a5ca4e1:7389
(anonymous) @ hot-module-replacement.js?hash=e9f1fc577edabe0d5d25e1ac558108e26a95ca40:406
module.constructor._reset @ hot-module-replacement.js?hash=e9f1fc577edabe0d5d25e1ac558108e26a95ca40:405
(anonymous) @ hot-module-replacement.js?hash=e9f1fc577edabe0d5d25e1ac558108e26a95ca40:504
applyChangeset @ hot-module-replacement.js?hash=e9f1fc577edabe0d5d25e1ac558108e26a95ca40:501
(anonymous) @ hot-module-replacement.js?hash=e9f1fc577edabe0d5d25e1ac558108e26a95ca40:189
handleMessage @ hot-module-replacement.js?hash=e9f1fc577edabe0d5d25e1ac558108e26a95ca40:188
(anonymous) @ hot-module-replacement.js?hash=e9f1fc577edabe0d5d25e1ac558108e26a95ca40:269

Could it be because I use CoffeeScript?

@zodern
Copy link
Member Author

zodern commented Jan 20, 2021

@softwarerero could you please create a reproduction?

@gwhobbs
Copy link

gwhobbs commented Jan 26, 2021

Thank you for adding HMR support to Blaze! I'm very excited to get this working. On Safari, Chrome and Firefox on macOS Big Sur, I'm seeing HMR: updated 1 file in the browser console with each file save, but no changes to how the page is rendered. Any ideas as to what might be causing this?

@zodern
Copy link
Member Author

zodern commented Jan 28, 2021

It doesn't work if Blaze.render is used by the app or a package (one is kadira:blaze-layout).

Eventually I will change the integration to support it. Until then, I am using this code in one app that uses blaze-layout:

if (module.hot) {
  let currentTemplate;
  let currentRegions;

  const oldRender = BlazeLayout.render;

  BlazeLayout.render = function (template, regions) {
    currentTemplate = template;
    currentRegions = regions;

    oldRender.call(this, template, regions);
  };

  const oldMigrateTemplate = Template._migrateTemplate;

  Template._migrateTemplate = function () {
    BlazeLayout.reset();
    oldMigrateTemplate.apply(this, arguments);
    BlazeLayout._render(currentTemplate, currentRegions);
  };
}

@gwhobbs
Copy link

gwhobbs commented Jan 29, 2021

@zodern I am using kadira:blaze-layout, and that worked for me—thanks!

@softwarerero
Copy link

I am also using kadira:blaze-layout and the workaround enables HMR for me.

I still get "Uncaught Error: Expected template rendered with Blaze.render" on the console but that seems to be only a cosmetic issue.

My test code is here: https://github.com/softwarerero/m2bt

@filipenevola
Copy link
Collaborator

Hi, Blaze HMR beta.2 is available.

You can try this integration in apps using Meteor 2+ by running:

meteor add hot-module-replacement blaze-html-templates@1.2.0-beta.2

To also use HMR for javascript files that use the Template api, run

meteor add blaze-hot

@zodern
Copy link
Member Author

zodern commented Mar 15, 2021

With the second beta there are two implementations of applying updates during HMR. By default it uses a simple one that replaces all root views when a template is modified. This is slow and loses all template instance state, but should work reliably. This is similar to how the first beta worked.

The second one is used when the blaze-hot package is added. It only replaces the blaze views for the modified templates and their children instead of everything, which is much faster and allows parent and sibling templates to preserve their state. However, it doesn't work as well when templates access the dom of children templates, and might be incompatible with some frameworks built on top of Blaze.

Some other changes:

  • if a template fails to render during an HMR update, it can recover during the next HMR update
  • checking for templates with duplicate names works correctly with HMR updates
  • removes global helpers that were removed from modified js files

@filipenevola filipenevola changed the base branch from master to release-2.4 March 20, 2021 00:34
@filipenevola
Copy link
Collaborator

We are merging this PR to #323

We are close to finish this new release (2.4)

@filipenevola filipenevola merged commit 08a8e16 into release-2.4 Mar 20, 2021
@filipenevola filipenevola deleted the hot-module-replacement branch March 20, 2021 00:40
@filipenevola filipenevola added this to the 2.4 milestone Mar 24, 2021
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

Successfully merging this pull request may close these issues.

5 participants