ember-vite-hmr 2.2.4 → 2.4.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.
package/dist/lib/hmr.js CHANGED
@@ -13,12 +13,18 @@ import { parseSync } from '@babel/core';
13
13
  import traverseModule from '@babel/traverse';
14
14
  const traverse = traverseModule.default || traverseModule;
15
15
  import { readFile } from 'fs/promises';
16
+ import { hmrImportMetadataCache } from './babel-plugin.js';
16
17
  const __filename = fileURLToPath(import.meta.url);
17
18
  const __dirname = path.dirname(__filename);
18
19
  // Above this many distinct named blocks, fall back to forwarding all of them
19
20
  // unconditionally rather than generating an exponential number of branches
20
21
  // (see generateInvocations below for why branching is needed at all).
21
22
  const MAX_COMBINATORIAL_YIELDS = 8;
23
+ // Safety cap on how many `extends` links resolveYieldSource will walk while
24
+ // looking for an inherited template (see its own doc comment) - real
25
+ // component hierarchies never get remotely this deep, this only guards
26
+ // against a pathological/cyclic chain.
27
+ const MAX_EXTENDS_DEPTH = 20;
22
28
  function blockTag(y) {
23
29
  return `<:${y} as |a b c d e f g h i j k l|>{{yield a b c d e f g h i j k l to='${y}'}}</:${y}>`;
24
30
  }
@@ -78,6 +84,7 @@ const hotCallbacks = new Set();
78
84
 
79
85
  if (import.meta.hot) {
80
86
  import.meta.hot.accept('${imp}', (module) => {
87
+ import.meta.hot.data.latestModule = module;
81
88
  for (const callback of hotCallbacks) {
82
89
  callback(module);
83
90
  }
@@ -106,7 +113,11 @@ export default class HotComponent extends Component {
106
113
  named[name] = capturedArgs?.named[name] ?? createComputeRef(() => args[name]);
107
114
  }
108
115
  const CurriedComponent = 0;
109
- this.curried = curry(CurriedComponent, TargetComponent, owner, { positional, named});
116
+ // After an accepted update this module is not re-evaluated, so the static
117
+ // TargetComponent binding still points at the pre-update module. Instances
118
+ // created after the update must curry the latest accepted class instead.
119
+ const Target = import.meta.hot?.data.latestModule?.default ?? TargetComponent;
120
+ this.curried = curry(CurriedComponent, Target, owner, { positional, named});
110
121
  if (import.meta.hot) {
111
122
  const callback = (module) => {
112
123
  this.curried = curry(CurriedComponent, module.default, owner, { positional, named});
@@ -141,6 +152,17 @@ shadowManager.create = function (owner, definition, vmArgs) {
141
152
  capturedArgsStack.pop();
142
153
  }
143
154
  };
155
+ // Glimmer's debug render tree normally adds one 'component' node per
156
+ // invoked component instance (see VM_GET_COMPONENT_SELF_OP). Returning no
157
+ // nodes here suppresses that node for HotComponent itself, so only the
158
+ // real, curried target component (invoked in HotComponent's own template)
159
+ // shows up in ember-inspector's component tree - this is the same hook
160
+ // Ember core uses internally to keep its own {{outlet}}/{{mount}} wrapper
161
+ // machinery out of (or relabeled in) that tree (see OutletComponentManager
162
+ // and MountManager in ember-source).
163
+ shadowManager.getDebugCustomRenderTree = function () {
164
+ return [];
165
+ };
144
166
  setInternalComponentManager(shadowManager, HotComponent);
145
167
  `;
146
168
  const cachedYields = {};
@@ -177,6 +199,188 @@ function normalizePath(inputPath) {
177
199
  // Always convert backslashes to forward slashes
178
200
  return inputPath.replace(/\\/g, '/');
179
201
  }
202
+ // The compiled backing class embeds an import specifier as a browser-facing
203
+ // URL (i.e. prefixed with the configured vite `base`), since that's what
204
+ // gets sent to the client. But `server.transformRequest` is an internal API
205
+ // keyed by root-relative ids, so the base has to be stripped back off before
206
+ // reusing that specifier, or a lookup 404s under a non-root base.
207
+ function stripBase(specifier, base) {
208
+ if (base !== '/' && specifier.startsWith(base)) {
209
+ return `/${specifier.slice(base.length)}`;
210
+ }
211
+ return specifier;
212
+ }
213
+ // A component's compiled module only calls `setComponentTemplate` on itself
214
+ // when it owns a template directly (an inline `<template>`); it's absent
215
+ // both for a classic component whose template lives in a separately
216
+ // resolved colocated `.hbs` (see the `.hbs` import check this feeds into)
217
+ // and for a component with no template of its own at all, which inherits
218
+ // whatever's registered on its nearest ancestor class instead (Glimmer's
219
+ // `getComponentTemplate`/`setComponentTemplate`, vendored in ember-source's
220
+ // `@glimmer/manager`, resolve a component's template by walking
221
+ // `Object.getPrototypeOf` up the prototype chain - the same mechanism
222
+ // `getInternalComponentManager` uses, see the `shadowManager` comment above).
223
+ function findSuperclassImportSource(content) {
224
+ let result;
225
+ try {
226
+ result = parseSync(content, {
227
+ filename: 'hmr-extends-check.js',
228
+ ast: true,
229
+ code: false,
230
+ configFile: false,
231
+ babelrc: false,
232
+ plugins: [
233
+ ['@babel/plugin-syntax-typescript', { isTSX: true }],
234
+ ['@babel/plugin-proposal-decorators', { version: '2022-03' }],
235
+ ],
236
+ });
237
+ }
238
+ catch (_a) {
239
+ return null;
240
+ }
241
+ if (!result) {
242
+ return null;
243
+ }
244
+ // Prefer the default-exported class specifically (that's always the
245
+ // component itself) over "the first class with a superclass in the file",
246
+ // since a file can declare other, unrelated classes above it (a local
247
+ // helper class, etc.) that would otherwise be matched instead.
248
+ let superName = null;
249
+ traverse(result, {
250
+ ExportDefaultDeclaration(path) {
251
+ const declaration = path.node.declaration;
252
+ let classNode = declaration;
253
+ if (declaration.type === 'Identifier' && declaration.name) {
254
+ const binding = path.scope.getBinding(declaration.name);
255
+ if ((binding === null || binding === void 0 ? void 0 : binding.path.isClassDeclaration()) ||
256
+ (binding === null || binding === void 0 ? void 0 : binding.path.isClassExpression())) {
257
+ classNode = binding.path.node;
258
+ }
259
+ }
260
+ const superClass = classNode.superClass;
261
+ if ((superClass === null || superClass === void 0 ? void 0 : superClass.type) === 'Identifier' && superClass.name) {
262
+ superName = superClass.name;
263
+ }
264
+ },
265
+ });
266
+ if (!superName) {
267
+ // Fall back to the first class with a superclass anywhere in the file
268
+ // (e.g. a non-default-exported component, or an export form the check
269
+ // above doesn't specifically handle).
270
+ traverse(result, {
271
+ 'ClassDeclaration|ClassExpression'(path) {
272
+ if (superName) {
273
+ return;
274
+ }
275
+ const superClass = path.node.superClass;
276
+ if ((superClass === null || superClass === void 0 ? void 0 : superClass.type) === 'Identifier' && superClass.name) {
277
+ superName = superClass.name;
278
+ }
279
+ },
280
+ });
281
+ }
282
+ if (!superName) {
283
+ return null;
284
+ }
285
+ let source = null;
286
+ traverse(result, {
287
+ ImportDeclaration(path) {
288
+ if (source) {
289
+ return;
290
+ }
291
+ if (path.node.specifiers.some((s) => s.local.name === superName)) {
292
+ source = path.node.source.value;
293
+ }
294
+ },
295
+ });
296
+ return source;
297
+ }
298
+ // If `content` (already run through `server.transformRequest`) registers a
299
+ // template on itself - either inline, or via a colocated `.hbs` import -
300
+ // returns that template's own source. Returns null when this file has no
301
+ // template of its own to scan, meaning any named blocks it uses have to be
302
+ // found on an ancestor class instead (see resolveYieldSource).
303
+ function resolveOwnTemplateSource(server, base, filename, content) {
304
+ return __awaiter(this, void 0, void 0, function* () {
305
+ var _a;
306
+ // For a classic component with a separately resolved template (a colocated
307
+ // `.hbs` file next to a backing class, or a template-only component), the
308
+ // compiled backing module only *imports* its template (e.g. `import TEMPLATE
309
+ // from "./foo.hbs?import"`); it doesn't inline it. Any {{yield ... to="..."}}
310
+ // usage lives in that template file, so it has to be fetched and scanned
311
+ // separately, otherwise named blocks other than "default" are never detected
312
+ // and get silently dropped by the generated hot-reload wrapper.
313
+ const templateImportMatch = content.match(/\bfrom\s*['"]([^'"]+\.hbs(?:\?[^'"]*)?)['"]/);
314
+ if (templateImportMatch) {
315
+ const templateSpecifier = stripBase(templateImportMatch[1], base);
316
+ const templateRes = yield server.transformRequest(templateSpecifier);
317
+ return { filename: templateSpecifier, content: (_a = templateRes === null || templateRes === void 0 ? void 0 : templateRes.code) !== null && _a !== void 0 ? _a : '' };
318
+ }
319
+ if (content.includes('setComponentTemplate(')) {
320
+ return { filename, content };
321
+ }
322
+ return null;
323
+ });
324
+ }
325
+ // Determines which file's source should be scanned for named-block
326
+ // (`{{yield ... to="..."}}`) usage for the component at `filename`/`content`.
327
+ // Usually that's the component's own file (or its colocated `.hbs`, handled
328
+ // by resolveOwnTemplateSource). But a component with no template of its own
329
+ // renders whatever template is registered on its nearest ancestor class (see
330
+ // resolveOwnTemplateSource's doc comment) - previously this only ever
331
+ // scanned the child's own (template-less) file, silently dropping every
332
+ // named block other than "default" for any component subclassed without its
333
+ // own template. This walks the `extends` chain, resolving each ancestor's
334
+ // import the same way Vite already resolved it in the already-transformed
335
+ // source, until one with its own template is found.
336
+ function resolveYieldSource(server, base, filename, content) {
337
+ return __awaiter(this, void 0, void 0, function* () {
338
+ const own = yield resolveOwnTemplateSource(server, base, filename, content);
339
+ if (own) {
340
+ return own;
341
+ }
342
+ const seen = new Set([filename]);
343
+ let currentContent = content;
344
+ for (let i = 0; i < MAX_EXTENDS_DEPTH; i++) {
345
+ const superSource = findSuperclassImportSource(currentContent);
346
+ if (!superSource) {
347
+ break;
348
+ }
349
+ const superSpecifier = stripBase(superSource, base);
350
+ // Framework/addon base classes live in node_modules and are never
351
+ // hot-tracked by this plugin (see the node_modules skip in `transform`);
352
+ // stop there rather than walking into vendored internals.
353
+ if (seen.has(superSpecifier) || superSpecifier.includes('node_modules')) {
354
+ break;
355
+ }
356
+ seen.add(superSpecifier);
357
+ let superContent;
358
+ try {
359
+ // Unlike the `.hbs` specifier resolveOwnTemplateSource requests
360
+ // (always something Vite itself already resolved for a file we
361
+ // successfully transformed), this specifier can be anything a
362
+ // superclass import resolved to, including forms
363
+ // `transformRequest` can't handle. Failing to resolve an ancestor's
364
+ // template is the same "no named blocks found" outcome as before
365
+ // this fix existed - it must never fail the whole component load.
366
+ const superRes = yield server.transformRequest(superSpecifier);
367
+ superContent = superRes === null || superRes === void 0 ? void 0 : superRes.code;
368
+ }
369
+ catch (_a) {
370
+ break;
371
+ }
372
+ if (!superContent) {
373
+ break;
374
+ }
375
+ const superOwn = yield resolveOwnTemplateSource(server, base, superSpecifier, superContent);
376
+ if (superOwn) {
377
+ return superOwn;
378
+ }
379
+ currentContent = superContent;
380
+ }
381
+ return { filename, content };
382
+ });
383
+ }
180
384
  const virtualPrefix = '/ember-vite-hmr/virtual/component:';
181
385
  // Embroider's resolver registry. It's the binding the app entry's HMR hook
182
386
  // mutates, so we use it to locate that entry. Embroider keeps this as an
@@ -188,7 +392,8 @@ export function hmr(enableViteHmrForModes = ['development']) {
188
392
  return {
189
393
  name: 'hmr-plugin',
190
394
  enforce: 'post',
191
- config(_config, env) {
395
+ config(config, env) {
396
+ var _a;
192
397
  // The per-component hot wrapper (see getHotComponent) imports the first
193
398
  // four of these, but it is generated/served on demand, so it is never
194
399
  // part of the static module graph Vite's dependency scanner crawls on
@@ -207,6 +412,18 @@ export function hmr(enableViteHmrForModes = ['development']) {
207
412
  if (!enableViteHmrForModes.includes(env.mode)) {
208
413
  return;
209
414
  }
415
+ // With `optimizeDeps.noDiscovery: true`, Vite never scans the app's own
416
+ // source, so nothing else pulls these same glimmer subpaths into the
417
+ // optimizer. Forcing them into `include` then creates a second,
418
+ // separately pre-bundled copy of the glimmer VM alongside the
419
+ // unoptimized one the rest of the (unscanned) app actually uses,
420
+ // leading to "The global context for Glimmer VM was not set" (#554).
421
+ // There's nothing useful this hook can pre-declare in that mode, so
422
+ // skip it entirely rather than fight the user's own dep-optimization
423
+ // config.
424
+ if ((_a = config.optimizeDeps) === null || _a === void 0 ? void 0 : _a.noDiscovery) {
425
+ return;
426
+ }
210
427
  return {
211
428
  optimizeDeps: {
212
429
  include: [
@@ -277,31 +494,7 @@ export function hmr(enableViteHmrForModes = ['development']) {
277
494
  }
278
495
  const res = yield server.transformRequest(filename);
279
496
  const content = res === null || res === void 0 ? void 0 : res.code;
280
- // For a classic component with a separately resolved template (a colocated
281
- // `.hbs` file next to a backing class, or a template-only component), the
282
- // compiled backing module only *imports* its template (e.g. `import TEMPLATE
283
- // from "./foo.hbs?import"`); it doesn't inline it. Any {{yield ... to="..."}}
284
- // usage lives in that template file, so it has to be fetched and scanned
285
- // separately, otherwise named blocks other than "default" are never detected
286
- // and get silently dropped by the generated hot-reload wrapper.
287
- let yieldSourceFilename = filename;
288
- let yieldSourceContent = content;
289
- const templateImportMatch = content === null || content === void 0 ? void 0 : content.match(/\bfrom\s*['"]([^'"]+\.hbs(?:\?[^'"]*)?)['"]/);
290
- if (templateImportMatch) {
291
- // The compiled backing class embeds the template import as a
292
- // browser-facing URL (i.e. prefixed with the configured vite
293
- // `base`), since that's what gets sent to the client. But
294
- // `server.transformRequest` is an internal API keyed by root-
295
- // relative ids, so the base has to be stripped back off before
296
- // reusing that specifier here, or this 404s under a non-root base.
297
- let templateSpecifier = templateImportMatch[1];
298
- if (base !== '/' && templateSpecifier.startsWith(base)) {
299
- templateSpecifier = `/${templateSpecifier.slice(base.length)}`;
300
- }
301
- const templateRes = yield server.transformRequest(templateSpecifier);
302
- yieldSourceFilename = templateSpecifier;
303
- yieldSourceContent = templateRes === null || templateRes === void 0 ? void 0 : templateRes.code;
304
- }
497
+ const { filename: yieldSourceFilename, content: yieldSourceContent } = yield resolveYieldSource(server, base, filename, content !== null && content !== void 0 ? content : '');
305
498
  const resId = yield this.resolve(yieldSourceFilename, path.resolve(process.cwd(), 'package.json'));
306
499
  // Strip any query string so the cache key matches the plain file path that
307
500
  // `hotUpdate` below receives when that template file is edited.
@@ -406,103 +599,124 @@ export function hmr(enableViteHmrForModes = ['development']) {
406
599
  if (resourcePath.includes('node_modules')) {
407
600
  return source;
408
601
  }
409
- // Add hot reload statements for tracked imports (only non-node_modules)
410
- // Use babel visitor to extract __hmr_import_metadata__
411
- const result = parseSync(source, {
412
- filename: resourcePath,
413
- ast: true,
414
- code: false,
415
- configFile: false,
416
- babelrc: false,
417
- plugins: [
418
- ['@babel/plugin-syntax-typescript', { isTSX: true }],
419
- ['@babel/plugin-proposal-decorators', { version: '2022-03' }],
420
- ],
421
- });
422
- if (result) {
423
- let importVar = null;
424
- let bindings = [];
425
- const importStatements = [];
426
- // First pass: Extract metadata
427
- traverse(result, {
428
- ExportNamedDeclaration(path) {
429
- var _a, _b, _c;
430
- const declaration = path.node.declaration;
431
- // Check if this is: export const __hmr_import_metadata__ = {...}
432
- if ((declaration === null || declaration === void 0 ? void 0 : declaration.type) === 'VariableDeclaration' &&
433
- ((_c = (_b = (_a = declaration.declarations) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id) === null || _c === void 0 ? void 0 : _c.name) ===
434
- '__hmr_import_metadata__') {
435
- const init = declaration.declarations[0].init;
436
- if ((init === null || init === void 0 ? void 0 : init.type) === 'ObjectExpression') {
437
- // Extract importVar and bindings from the object
438
- for (const prop of init.properties) {
439
- if (prop.type === 'ObjectProperty' &&
440
- prop.key.type === 'Identifier') {
441
- if (prop.key.name === 'importVar' &&
442
- prop.value.type === 'StringLiteral') {
443
- importVar = prop.value.value;
444
- }
445
- else if (prop.key.name === 'bindings' &&
446
- prop.value.type === 'ArrayExpression') {
447
- bindings = prop.value.elements
448
- .filter((el) => (el === null || el === void 0 ? void 0 : el.type) === 'StringLiteral')
449
- .map((el) => el.value);
450
- }
451
- }
452
- }
453
- }
454
- }
455
- },
602
+ // Add hot reload statements for tracked imports.
603
+ //
604
+ // lib/babel-plugin.ts's hotReplaceAst already computes this exact
605
+ // metadata (importVar/bindings/importStatements) while babel processes
606
+ // this same file earlier in the pipeline, and shares it via
607
+ // hmrImportMetadataCache keyed by filename -- so the common case skips
608
+ // re-parsing and re-traversing this file from scratch. Only fall back
609
+ // to recovering it the slow way (parsing the already-babel-transformed
610
+ // source and pulling the __hmr_import_metadata__ export back out of
611
+ // it) when there's no cache entry, e.g. because this file's babel pass
612
+ // didn't go through hotReplaceAst in this process, or a concurrent
613
+ // babel pass for another file raced the cache write out (see that
614
+ // cache's own doc comment).
615
+ let importVar = null;
616
+ let bindings = [];
617
+ let importStatements = [];
618
+ const cached = hmrImportMetadataCache.get(resourcePath);
619
+ if (cached) {
620
+ importVar = cached.importVar;
621
+ bindings = cached.bindings;
622
+ importStatements = cached.importStatements;
623
+ }
624
+ else {
625
+ const result = parseSync(source, {
626
+ filename: resourcePath,
627
+ ast: true,
628
+ code: false,
629
+ configFile: false,
630
+ babelrc: false,
631
+ plugins: [
632
+ ['@babel/plugin-syntax-typescript', { isTSX: true }],
633
+ ['@babel/plugin-proposal-decorators', { version: '2022-03' }],
634
+ ],
456
635
  });
457
- // Second pass: Find matching imports (only if we have bindings to match)
458
- if (importVar && bindings.length > 0) {
636
+ if (result) {
637
+ // First pass: Extract metadata
459
638
  traverse(result, {
460
- ImportDeclaration(path) {
461
- const importSource = path.node.source.value;
462
- for (const specifier of path.node.specifiers) {
463
- const local = specifier.local.name;
464
- if (bindings.includes(local)) {
465
- let specifierName = 'default';
466
- if (specifier.type === 'ImportDefaultSpecifier') {
467
- specifierName = 'default';
468
- }
469
- else if (specifier.type === 'ImportSpecifier') {
470
- // For named imports, use the imported name
471
- // Handle both Identifier and StringLiteral types
472
- const imported = specifier.imported;
473
- specifierName =
474
- imported.type === 'Identifier'
475
- ? imported.name
476
- : imported.value;
477
- }
478
- else if (specifier.type === 'ImportNamespaceSpecifier') {
479
- specifierName = '*';
639
+ ExportNamedDeclaration(path) {
640
+ var _a, _b, _c;
641
+ const declaration = path.node.declaration;
642
+ // Check if this is: export const __hmr_import_metadata__ = {...}
643
+ if ((declaration === null || declaration === void 0 ? void 0 : declaration.type) === 'VariableDeclaration' &&
644
+ ((_c = (_b = (_a = declaration.declarations) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id) === null || _c === void 0 ? void 0 : _c.name) ===
645
+ '__hmr_import_metadata__') {
646
+ const init = declaration.declarations[0].init;
647
+ if ((init === null || init === void 0 ? void 0 : init.type) === 'ObjectExpression') {
648
+ // Extract importVar and bindings from the object
649
+ for (const prop of init.properties) {
650
+ if (prop.type === 'ObjectProperty' &&
651
+ prop.key.type === 'Identifier') {
652
+ if (prop.key.name === 'importVar' &&
653
+ prop.value.type === 'StringLiteral') {
654
+ importVar = prop.value.value;
655
+ }
656
+ else if (prop.key.name === 'bindings' &&
657
+ prop.value.type === 'ArrayExpression') {
658
+ bindings = prop.value.elements
659
+ .filter((el) => (el === null || el === void 0 ? void 0 : el.type) ===
660
+ 'StringLiteral')
661
+ .map((el) => el.value);
662
+ }
663
+ }
480
664
  }
481
- importStatements.push({
482
- local,
483
- source: importSource,
484
- specifier: specifierName,
485
- });
486
665
  }
487
666
  }
488
667
  },
489
668
  });
669
+ // Second pass: Find matching imports (only if we have bindings to match)
670
+ if (importVar && bindings.length > 0) {
671
+ traverse(result, {
672
+ ImportDeclaration(path) {
673
+ const importSource = path.node.source.value;
674
+ for (const specifier of path.node.specifiers) {
675
+ const local = specifier.local.name;
676
+ if (bindings.includes(local)) {
677
+ let specifierName = 'default';
678
+ if (specifier.type === 'ImportDefaultSpecifier') {
679
+ specifierName = 'default';
680
+ }
681
+ else if (specifier.type === 'ImportSpecifier') {
682
+ // For named imports, use the imported name
683
+ // Handle both Identifier and StringLiteral types
684
+ const imported = specifier.imported;
685
+ specifierName =
686
+ imported.type === 'Identifier'
687
+ ? imported.name
688
+ : imported.value;
689
+ }
690
+ else if (specifier.type === 'ImportNamespaceSpecifier') {
691
+ specifierName = '*';
692
+ }
693
+ importStatements.push({
694
+ local,
695
+ source: importSource,
696
+ specifier: specifierName,
697
+ });
698
+ }
699
+ }
700
+ },
701
+ });
702
+ }
490
703
  }
491
- // Process metadata if we found importVar (even with empty bindings)
492
- if (importVar) {
493
- // Generate hot reload code for each import (only if we have bindings)
494
- const hotReloadStatements = [];
495
- for (const imp of importStatements) {
496
- // Resolve the import to check if it's from node_modules
497
- const resolved = yield this.resolve(imp.source, resourcePath, {});
498
- if ((resolved === null || resolved === void 0 ? void 0 : resolved.id) &&
499
- normalizePath(resolved.id).includes('node_modules')) {
500
- // Skip node_modules imports
501
- continue;
502
- }
503
- const sourceId = imp.source.replace(/@embroider\/virtual/g, 'embroider_virtual');
504
- const virtualPath = `/ember-vite-hmr/virtual/component:${sourceId}::${imp.specifier}.gjs`;
505
- hotReloadStatements.push(`
704
+ }
705
+ // Process metadata if we found importVar (even with empty bindings)
706
+ if (importVar) {
707
+ // Generate hot reload code for each import (only if we have bindings)
708
+ const hotReloadStatements = [];
709
+ for (const imp of importStatements) {
710
+ // Resolve the import to check if it's from node_modules
711
+ const resolved = yield this.resolve(imp.source, resourcePath, {});
712
+ if ((resolved === null || resolved === void 0 ? void 0 : resolved.id) &&
713
+ normalizePath(resolved.id).includes('node_modules')) {
714
+ // Skip node_modules imports
715
+ continue;
716
+ }
717
+ const sourceId = imp.source.replace(/@embroider\/virtual/g, 'embroider_virtual');
718
+ const virtualPath = `/ember-vite-hmr/virtual/component:${sourceId}::${imp.specifier}.gjs`;
719
+ hotReloadStatements.push(`
506
720
  (async () => {
507
721
  const GlimmerComponent = (await import('@glimmer/component')).default;
508
722
  const { hasInternalComponentManager } = await import('@glimmer/manager');
@@ -524,17 +738,16 @@ export function hmr(enableViteHmrForModes = ['development']) {
524
738
  import.meta.hot.accept('${imp.source}');
525
739
  }
526
740
  })();`);
527
- }
528
- // Always remove the metadata export
529
- source = source.replace(/export const __hmr_import_metadata__[^;]+;/, '');
530
- // Add HMR code if we have any statements OR if we have bindings (even if all were skipped)
531
- if (hotReloadStatements.length > 0 || bindings.length > 0) {
532
- const hotReloadCode = `
741
+ }
742
+ // Always remove the metadata export
743
+ source = source.replace(/export const __hmr_import_metadata__[^;]+;/, '');
744
+ // Add HMR code if we have any statements OR if we have bindings (even if all were skipped)
745
+ if (hotReloadStatements.length > 0 || bindings.length > 0) {
746
+ const hotReloadCode = `
533
747
  if (import.meta.hot) {
534
748
  ${hotReloadStatements.join('\n')}
535
749
  }`;
536
- source = source + hotReloadCode;
537
- }
750
+ source = source + hotReloadCode;
538
751
  }
539
752
  }
540
753
  const supportedPaths = ['routers', 'controllers', 'routes', 'templates'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-vite-hmr",
3
- "version": "2.2.4",
3
+ "version": "2.4.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",
@@ -51,8 +51,9 @@
51
51
  "@babel/eslint-parser": "^7.28.6",
52
52
  "@babel/plugin-proposal-class-properties": "^7.16.7",
53
53
  "@babel/plugin-proposal-decorators": "^7.28.0",
54
+ "@babel/plugin-syntax-decorators": "^7.29.7",
54
55
  "@babel/plugin-transform-class-static-block": "^7.28.6",
55
- "@babel/runtime": "^8.0.0",
56
+ "@babel/runtime": "^7.28.4",
56
57
  "@embroider/addon-dev": "^8.3.0",
57
58
  "@embroider/compat": "^4.1.20",
58
59
  "@embroider/core": "^4.6.2",