ember-vite-hmr 2.2.4 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -221,17 +221,73 @@ export default function hotReplaceAst(babel) {
221
221
  if (import.meta.hot) {
222
222
  import.meta.hot.data._proxy = this;
223
223
  }
224
+ // Bound methods are cached per-delegate (not per-proxy) so identity
225
+ // stays stable across repeated reads of the same delegate (e.g.
226
+ // {{on "click" this.svc.handler}} comparing args by reference), while
227
+ // naturally invalidating once a delegate is swapped out on HMR accept.
228
+ const boundMethods = new WeakMap();
224
229
  return new Proxy(this, {
225
- get(target, prop) {
230
+ get(target, prop, receiver) {
226
231
  if (prop === '_delegate') {
227
232
  return target._delegate;
228
233
  }
229
- return target._delegate[prop];
234
+ // A subclass of this service (see the Reflect.get fallback
235
+ // below) may override a method the base delegate class also
236
+ // defines. Since 'prop in delegate' walks the delegate's
237
+ // *entire* prototype chain, it would match the base
238
+ // implementation before the subclass's own override ever
239
+ // gets a chance -- so look for an own property between
240
+ // target's dynamic prototype and this proxy's own prototype
241
+ // first, and prefer it over the delegate.
242
+ let proto = Object.getPrototypeOf(target);
243
+ while (proto && proto !== ${proxyClassName}.prototype) {
244
+ if (Object.prototype.hasOwnProperty.call(proto, prop)) {
245
+ return Reflect.get(target, prop, receiver);
246
+ }
247
+ proto = Object.getPrototypeOf(proto);
248
+ }
249
+ const delegate = target._delegate;
250
+ // Methods invoked via normal member-call syntax (service.method())
251
+ // bind 'this' to whatever object the property access happened on
252
+ // -- the proxy, here -- not to the delegate. That breaks native
253
+ // private fields, whose storage lives only on the concrete
254
+ // delegate instance, so delegate-owned methods are explicitly
255
+ // bound to the delegate.
256
+ if (prop in delegate) {
257
+ const value = delegate[prop];
258
+ if (typeof value === 'function') {
259
+ let cache = boundMethods.get(delegate);
260
+ if (!cache) {
261
+ cache = new Map();
262
+ boundMethods.set(delegate, cache);
263
+ }
264
+ let bound = cache.get(prop);
265
+ if (!bound) {
266
+ bound = value.bind(delegate);
267
+ cache.set(prop, bound);
268
+ }
269
+ return bound;
270
+ }
271
+ return value;
272
+ }
273
+ // Not on the delegate: fall through to the proxy's own real
274
+ // prototype chain, which covers methods declared on a subclass
275
+ // of this service (the subclass never went through this babel
276
+ // transform itself, since it doesn't extend Service directly).
277
+ return Reflect.get(target, prop, receiver);
230
278
  },
231
279
  set(target, prop, value) {
232
280
  target._delegate[prop] = value;
233
281
  return true;
234
282
  },
283
+ defineProperty(target, prop, descriptor) {
284
+ // A subclass's own class fields are installed via
285
+ // [[DefineOwnProperty]] against whatever 'this' super() returned
286
+ // (this proxy), bypassing the 'set' trap entirely. Route them to
287
+ // the delegate too, so subclass state lives alongside the rest
288
+ // of the service's state instead of stranded on the proxy target.
289
+ return Reflect.defineProperty(target._delegate, prop, descriptor);
290
+ },
235
291
  });
236
292
  `)();
237
293
  const willDestroyBody = babelTemplate.statements(`
@@ -309,8 +365,19 @@ export default function hotReplaceAst(babel) {
309
365
  continue;
310
366
  }
311
367
 
312
- // Skip Function properties - they should not be synced
313
- if (typeof previousValue === 'function') {
368
+ // Skip function properties that the new implementation also
369
+ // declares (a fresh redefinition should win over the old
370
+ // one). But a function-valued property the new
371
+ // implementation *doesn't* declare at all isn't a stale
372
+ // redefinition -- it's state a subclass installed on the
373
+ // delegate directly (subclasses of this service never go
374
+ // through this transform themselves, since their
375
+ // superclass isn't literally named Service; their own
376
+ // class fields land on the delegate via the proxy's
377
+ // defineProperty trap instead). Dropping it here would
378
+ // silently break e.g. an arrow-function class field on a
379
+ // subclass across a base-class HMR swap.
380
+ if (typeof previousValue === 'function' && hasOwnDefault) {
314
381
  continue;
315
382
  }
316
383
 
package/dist/lib/hmr.js CHANGED
@@ -141,6 +141,17 @@ shadowManager.create = function (owner, definition, vmArgs) {
141
141
  capturedArgsStack.pop();
142
142
  }
143
143
  };
144
+ // Glimmer's debug render tree normally adds one 'component' node per
145
+ // invoked component instance (see VM_GET_COMPONENT_SELF_OP). Returning no
146
+ // nodes here suppresses that node for HotComponent itself, so only the
147
+ // real, curried target component (invoked in HotComponent's own template)
148
+ // shows up in ember-inspector's component tree - this is the same hook
149
+ // Ember core uses internally to keep its own {{outlet}}/{{mount}} wrapper
150
+ // machinery out of (or relabeled in) that tree (see OutletComponentManager
151
+ // and MountManager in ember-source).
152
+ shadowManager.getDebugCustomRenderTree = function () {
153
+ return [];
154
+ };
144
155
  setInternalComponentManager(shadowManager, HotComponent);
145
156
  `;
146
157
  const cachedYields = {};
@@ -188,7 +199,8 @@ export function hmr(enableViteHmrForModes = ['development']) {
188
199
  return {
189
200
  name: 'hmr-plugin',
190
201
  enforce: 'post',
191
- config(_config, env) {
202
+ config(config, env) {
203
+ var _a;
192
204
  // The per-component hot wrapper (see getHotComponent) imports the first
193
205
  // four of these, but it is generated/served on demand, so it is never
194
206
  // part of the static module graph Vite's dependency scanner crawls on
@@ -207,6 +219,18 @@ export function hmr(enableViteHmrForModes = ['development']) {
207
219
  if (!enableViteHmrForModes.includes(env.mode)) {
208
220
  return;
209
221
  }
222
+ // With `optimizeDeps.noDiscovery: true`, Vite never scans the app's own
223
+ // source, so nothing else pulls these same glimmer subpaths into the
224
+ // optimizer. Forcing them into `include` then creates a second,
225
+ // separately pre-bundled copy of the glimmer VM alongside the
226
+ // unoptimized one the rest of the (unscanned) app actually uses,
227
+ // leading to "The global context for Glimmer VM was not set" (#554).
228
+ // There's nothing useful this hook can pre-declare in that mode, so
229
+ // skip it entirely rather than fight the user's own dep-optimization
230
+ // config.
231
+ if ((_a = config.optimizeDeps) === null || _a === void 0 ? void 0 : _a.noDiscovery) {
232
+ return;
233
+ }
210
234
  return {
211
235
  optimizeDeps: {
212
236
  include: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-vite-hmr",
3
- "version": "2.2.4",
3
+ "version": "2.3.0",
4
4
  "private": false,
5
5
  "description": "provides hmr support for ember apps",
6
6
  "keywords": [
@@ -14,7 +14,8 @@
14
14
  "author": "Patrick Pircher",
15
15
  "exports": {
16
16
  ".": {
17
- "default": "./dist/lib/hmr.js"
17
+ "node": "./dist/lib/hmr.js",
18
+ "default": null
18
19
  },
19
20
  "./*": {
20
21
  "types": "./declarations/*.d.ts",
@@ -22,7 +23,6 @@
22
23
  },
23
24
  "./addon-main.js": "./addon-main.cjs"
24
25
  },
25
- "main": "dist/lib/hmr.js",
26
26
  "typesVersions": {
27
27
  "*": {
28
28
  "*": [
@@ -38,7 +38,7 @@
38
38
  "@babel/core": "^7.29.0",
39
39
  "@babel/plugin-transform-typescript": "^7.28.6",
40
40
  "@babel/traverse": "^7.29.0",
41
- "@babel/types": "^8.0.4",
41
+ "@babel/types": "^7.28.5",
42
42
  "@embroider/addon-shim": "^1.10.3",
43
43
  "@glimmer/syntax": "^0.95.0",
44
44
  "@types/supports-color": "^10.0.0",
@@ -52,7 +52,7 @@
52
52
  "@babel/plugin-proposal-class-properties": "^7.16.7",
53
53
  "@babel/plugin-proposal-decorators": "^7.28.0",
54
54
  "@babel/plugin-transform-class-static-block": "^7.28.6",
55
- "@babel/runtime": "^8.0.0",
55
+ "@babel/runtime": "^7.28.4",
56
56
  "@embroider/addon-dev": "^8.3.0",
57
57
  "@embroider/compat": "^4.1.20",
58
58
  "@embroider/core": "^4.6.2",