ember-vite-hmr 2.3.0 → 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/README.md +31 -0
- package/dist/instance-initializers/setup-hmr-manager.js +42 -3
- package/dist/instance-initializers/setup-hmr-manager.js.map +1 -1
- package/dist/lib/babel-plugin/private-members.d.ts +3 -0
- package/dist/lib/babel-plugin/private-members.js +291 -0
- package/dist/lib/babel-plugin/service-proxy.d.ts +3 -0
- package/dist/lib/babel-plugin/service-proxy.js +300 -0
- package/dist/lib/babel-plugin/template-imports.d.ts +46 -0
- package/dist/lib/babel-plugin/template-imports.js +250 -0
- package/dist/lib/babel-plugin.d.ts +3 -31
- package/dist/lib/babel-plugin.js +45 -419
- package/dist/lib/hmr.js +312 -123
- package/package.json +2 -1
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
|
|
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});
|
|
@@ -188,6 +199,188 @@ function normalizePath(inputPath) {
|
|
|
188
199
|
// Always convert backslashes to forward slashes
|
|
189
200
|
return inputPath.replace(/\\/g, '/');
|
|
190
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
|
+
}
|
|
191
384
|
const virtualPrefix = '/ember-vite-hmr/virtual/component:';
|
|
192
385
|
// Embroider's resolver registry. It's the binding the app entry's HMR hook
|
|
193
386
|
// mutates, so we use it to locate that entry. Embroider keeps this as an
|
|
@@ -301,31 +494,7 @@ export function hmr(enableViteHmrForModes = ['development']) {
|
|
|
301
494
|
}
|
|
302
495
|
const res = yield server.transformRequest(filename);
|
|
303
496
|
const content = res === null || res === void 0 ? void 0 : res.code;
|
|
304
|
-
|
|
305
|
-
// `.hbs` file next to a backing class, or a template-only component), the
|
|
306
|
-
// compiled backing module only *imports* its template (e.g. `import TEMPLATE
|
|
307
|
-
// from "./foo.hbs?import"`); it doesn't inline it. Any {{yield ... to="..."}}
|
|
308
|
-
// usage lives in that template file, so it has to be fetched and scanned
|
|
309
|
-
// separately, otherwise named blocks other than "default" are never detected
|
|
310
|
-
// and get silently dropped by the generated hot-reload wrapper.
|
|
311
|
-
let yieldSourceFilename = filename;
|
|
312
|
-
let yieldSourceContent = content;
|
|
313
|
-
const templateImportMatch = content === null || content === void 0 ? void 0 : content.match(/\bfrom\s*['"]([^'"]+\.hbs(?:\?[^'"]*)?)['"]/);
|
|
314
|
-
if (templateImportMatch) {
|
|
315
|
-
// The compiled backing class embeds the template import as a
|
|
316
|
-
// browser-facing URL (i.e. prefixed with the configured vite
|
|
317
|
-
// `base`), since that's what gets sent to the client. But
|
|
318
|
-
// `server.transformRequest` is an internal API keyed by root-
|
|
319
|
-
// relative ids, so the base has to be stripped back off before
|
|
320
|
-
// reusing that specifier here, or this 404s under a non-root base.
|
|
321
|
-
let templateSpecifier = templateImportMatch[1];
|
|
322
|
-
if (base !== '/' && templateSpecifier.startsWith(base)) {
|
|
323
|
-
templateSpecifier = `/${templateSpecifier.slice(base.length)}`;
|
|
324
|
-
}
|
|
325
|
-
const templateRes = yield server.transformRequest(templateSpecifier);
|
|
326
|
-
yieldSourceFilename = templateSpecifier;
|
|
327
|
-
yieldSourceContent = templateRes === null || templateRes === void 0 ? void 0 : templateRes.code;
|
|
328
|
-
}
|
|
497
|
+
const { filename: yieldSourceFilename, content: yieldSourceContent } = yield resolveYieldSource(server, base, filename, content !== null && content !== void 0 ? content : '');
|
|
329
498
|
const resId = yield this.resolve(yieldSourceFilename, path.resolve(process.cwd(), 'package.json'));
|
|
330
499
|
// Strip any query string so the cache key matches the plain file path that
|
|
331
500
|
// `hotUpdate` below receives when that template file is edited.
|
|
@@ -430,103 +599,124 @@ export function hmr(enableViteHmrForModes = ['development']) {
|
|
|
430
599
|
if (resourcePath.includes('node_modules')) {
|
|
431
600
|
return source;
|
|
432
601
|
}
|
|
433
|
-
// Add hot reload statements for tracked imports
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
prop.value.type === 'StringLiteral') {
|
|
467
|
-
importVar = prop.value.value;
|
|
468
|
-
}
|
|
469
|
-
else if (prop.key.name === 'bindings' &&
|
|
470
|
-
prop.value.type === 'ArrayExpression') {
|
|
471
|
-
bindings = prop.value.elements
|
|
472
|
-
.filter((el) => (el === null || el === void 0 ? void 0 : el.type) === 'StringLiteral')
|
|
473
|
-
.map((el) => el.value);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
},
|
|
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
|
+
],
|
|
480
635
|
});
|
|
481
|
-
|
|
482
|
-
|
|
636
|
+
if (result) {
|
|
637
|
+
// First pass: Extract metadata
|
|
483
638
|
traverse(result, {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
+
}
|
|
504
664
|
}
|
|
505
|
-
importStatements.push({
|
|
506
|
-
local,
|
|
507
|
-
source: importSource,
|
|
508
|
-
specifier: specifierName,
|
|
509
|
-
});
|
|
510
665
|
}
|
|
511
666
|
}
|
|
512
667
|
},
|
|
513
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
|
+
}
|
|
514
703
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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(`
|
|
530
720
|
(async () => {
|
|
531
721
|
const GlimmerComponent = (await import('@glimmer/component')).default;
|
|
532
722
|
const { hasInternalComponentManager } = await import('@glimmer/manager');
|
|
@@ -548,17 +738,16 @@ export function hmr(enableViteHmrForModes = ['development']) {
|
|
|
548
738
|
import.meta.hot.accept('${imp.source}');
|
|
549
739
|
}
|
|
550
740
|
})();`);
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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 = `
|
|
557
747
|
if (import.meta.hot) {
|
|
558
748
|
${hotReloadStatements.join('\n')}
|
|
559
749
|
}`;
|
|
560
|
-
|
|
561
|
-
}
|
|
750
|
+
source = source + hotReloadCode;
|
|
562
751
|
}
|
|
563
752
|
}
|
|
564
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.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "provides hmr support for ember apps",
|
|
6
6
|
"keywords": [
|
|
@@ -51,6 +51,7 @@
|
|
|
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
56
|
"@babel/runtime": "^7.28.4",
|
|
56
57
|
"@embroider/addon-dev": "^8.3.0",
|