tungsten 0.3.1 → 0.3.2

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.
Files changed (103) hide show
  1. package/dist/Components.d.ts +2 -1
  2. package/dist/Components.d.ts.map +1 -1
  3. package/dist/Components.js +10 -1
  4. package/dist/DomRegion.js +1 -1
  5. package/dist/FrameworkConfig.d.ts +4 -0
  6. package/dist/FrameworkConfig.d.ts.map +1 -1
  7. package/dist/FunctionComponentStandIn.d.ts +2 -0
  8. package/dist/FunctionComponentStandIn.d.ts.map +1 -1
  9. package/dist/FunctionComponentStandIn.js +10 -1
  10. package/dist/FunctionComponentStandIn.sxyt.d.ts +2 -0
  11. package/dist/FunctionComponentStandIn.sxyt.d.ts.map +1 -0
  12. package/dist/FunctionComponentStandIn.sxyt.js +52 -0
  13. package/dist/Servers/DevServer.d.ts +2 -0
  14. package/dist/Servers/DevServer.d.ts.map +1 -1
  15. package/dist/Servers/DevServer.js +32 -5
  16. package/dist/Servers/devErrorPageAutoRetry.d.ts +4 -0
  17. package/dist/Servers/devErrorPageAutoRetry.d.ts.map +1 -0
  18. package/dist/Servers/devErrorPageAutoRetry.js +26 -0
  19. package/dist/Servers/devErrorPageAutoRetry.sxyt.d.ts +2 -0
  20. package/dist/Servers/devErrorPageAutoRetry.sxyt.d.ts.map +1 -0
  21. package/dist/Servers/devErrorPageAutoRetry.sxyt.js +117 -0
  22. package/dist/api/getExport.d.ts.map +1 -1
  23. package/dist/api/getExport.js +2 -1
  24. package/dist/babelPlugins/BabelAddDevCacheBust.d.ts +11 -0
  25. package/dist/babelPlugins/BabelAddDevCacheBust.d.ts.map +1 -0
  26. package/dist/babelPlugins/BabelAddDevCacheBust.js +68 -0
  27. package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.d.ts +2 -0
  28. package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.d.ts.map +1 -0
  29. package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.js +138 -0
  30. package/dist/bin/commands/dev.d.ts.map +1 -1
  31. package/dist/bin/commands/dev.js +2 -0
  32. package/dist/build/compileUserApp.js +2 -2
  33. package/dist/build/makeBabelUserDevConfig.d.ts.map +1 -1
  34. package/dist/build/makeBabelUserDevConfig.js +6 -1
  35. package/dist/defaultConfig.d.ts +2 -0
  36. package/dist/defaultConfig.d.ts.map +1 -1
  37. package/dist/defaultConfig.js +3 -1
  38. package/dist/env/getEnvSchemas.d.ts.map +1 -1
  39. package/dist/env/getEnvSchemas.js +2 -1
  40. package/dist/getComponent.d.ts.map +1 -1
  41. package/dist/getComponent.js +2 -1
  42. package/dist/getRoutes.d.ts.map +1 -1
  43. package/dist/getRoutes.js +2 -1
  44. package/dist/hmr/applyHmrModule.d.ts +11 -0
  45. package/dist/hmr/applyHmrModule.d.ts.map +1 -0
  46. package/dist/hmr/applyHmrModule.js +78 -0
  47. package/dist/hmr/applyHmrModule.sxyt.d.ts +2 -0
  48. package/dist/hmr/applyHmrModule.sxyt.d.ts.map +1 -0
  49. package/dist/hmr/applyHmrModule.sxyt.js +182 -0
  50. package/dist/hmr/componentIdentity.d.ts +9 -0
  51. package/dist/hmr/componentIdentity.d.ts.map +1 -0
  52. package/dist/hmr/componentIdentity.js +17 -0
  53. package/dist/hmr/instanceRegistry.d.ts +4 -0
  54. package/dist/hmr/instanceRegistry.d.ts.map +1 -0
  55. package/dist/hmr/instanceRegistry.js +6 -0
  56. package/dist/hmr/instanceRegistry.sxyt.d.ts +2 -0
  57. package/dist/hmr/instanceRegistry.sxyt.d.ts.map +1 -0
  58. package/dist/hmr/instanceRegistry.sxyt.js +24 -0
  59. package/dist/hmr/liveReloadHub.d.ts +10 -0
  60. package/dist/hmr/liveReloadHub.d.ts.map +1 -0
  61. package/dist/{dev → hmr}/liveReloadHub.js +8 -2
  62. package/dist/hmr/refreshStylesheets.d.ts +2 -0
  63. package/dist/hmr/refreshStylesheets.d.ts.map +1 -0
  64. package/dist/hmr/refreshStylesheets.js +26 -0
  65. package/dist/hmr/refreshStylesheets.sxyt.d.ts +2 -0
  66. package/dist/hmr/refreshStylesheets.sxyt.d.ts.map +1 -0
  67. package/dist/hmr/refreshStylesheets.sxyt.js +46 -0
  68. package/dist/hydrate/hydrate.d.ts +9 -1
  69. package/dist/hydrate/hydrate.d.ts.map +1 -1
  70. package/dist/hydrate/hydrate.js +31 -7
  71. package/dist/hydrate/hydrate.sxyt.d.ts +2 -0
  72. package/dist/hydrate/hydrate.sxyt.d.ts.map +1 -0
  73. package/dist/hydrate/hydrate.sxyt.js +302 -0
  74. package/dist/hydrate/reconcileHydratedAttributes.d.ts.map +1 -1
  75. package/dist/hydrate/reconcileHydratedAttributes.js +10 -2
  76. package/dist/hydrate/reconcileHydratedAttributes.sxyt.js +17 -0
  77. package/dist/importDevModuleWithRetry.d.ts +3 -0
  78. package/dist/importDevModuleWithRetry.d.ts.map +1 -0
  79. package/dist/importDevModuleWithRetry.js +46 -0
  80. package/dist/importDevModuleWithRetry.sxyt.d.ts +2 -0
  81. package/dist/importDevModuleWithRetry.sxyt.d.ts.map +1 -0
  82. package/dist/importDevModuleWithRetry.sxyt.js +104 -0
  83. package/dist/render/HeadData.d.ts +1 -0
  84. package/dist/render/HeadData.d.ts.map +1 -1
  85. package/dist/render/contentToHtml.d.ts.map +1 -1
  86. package/dist/render/contentToHtml.js +2 -1
  87. package/dist/render/processHead.d.ts.map +1 -1
  88. package/dist/render/processHead.js +47 -6
  89. package/dist/render/processHead.sxyt.d.ts +2 -0
  90. package/dist/render/processHead.sxyt.d.ts.map +1 -0
  91. package/dist/render/processHead.sxyt.js +93 -0
  92. package/dist/update/hotSwap.sxyt.d.ts +2 -0
  93. package/dist/update/hotSwap.sxyt.d.ts.map +1 -0
  94. package/dist/update/hotSwap.sxyt.js +252 -0
  95. package/dist/update/integrateUpdateVdom.d.ts +5 -1
  96. package/dist/update/integrateUpdateVdom.d.ts.map +1 -1
  97. package/dist/update/integrateUpdateVdom.js +107 -4
  98. package/dist/warnOnFrameworkChange.d.ts +2 -0
  99. package/dist/warnOnFrameworkChange.d.ts.map +1 -0
  100. package/dist/warnOnFrameworkChange.js +27 -0
  101. package/package.json +1 -1
  102. package/dist/dev/liveReloadHub.d.ts +0 -6
  103. package/dist/dev/liveReloadHub.d.ts.map +0 -1
@@ -0,0 +1,182 @@
1
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
2
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
3
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
4
+ import { expect } from 'chai';
5
+ import { Window } from 'happy-dom';
6
+ import { describe } from 'sxy-test-runner';
7
+ import { TungstenSimpleComponent } from "../Components.js";
8
+ import { createTestApp } from "../testing/index.js";
9
+ import { WideRenderedContentNodeIsComponent } from "../RenderedContentNode.js";
10
+ import { AnyComponentIsFunctionComponentStandIn } from "../AnyComponent.js";
11
+ import { setCurrentInstance } from "./instanceRegistry.js";
12
+ import { applyHmrModule } from "./applyHmrModule.js";
13
+ import { jsxs as _jsxs, jsx as _jsx } from "tungsten/jsxRuntime.js";
14
+ describe('applyHmrModule()', ({
15
+ it
16
+ }) => {
17
+ it('matches and swaps a live component by name+kind', async () => {
18
+ const window = new Window();
19
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
20
+ const Greeting = ({
21
+ name
22
+ }) => {
23
+ return _jsxs("div", {
24
+ className: "greeting",
25
+ children: ["Hello ", name]
26
+ });
27
+ };
28
+ class Root extends TungstenSimpleComponent {
29
+ constructor(...args) {
30
+ super(...args);
31
+ _defineProperty(this, "content", () => _jsx(Greeting, {
32
+ name: "Ada"
33
+ }));
34
+ }
35
+ }
36
+ const testApp = await createTestApp(Root, {
37
+ document
38
+ });
39
+ const wrapper = testApp.component.__currentContent;
40
+ if (!WideRenderedContentNodeIsComponent(wrapper) || !AnyComponentIsFunctionComponentStandIn(wrapper.element)) {
41
+ throw new Error('expected the root to directly render a single function component');
42
+ }
43
+ setCurrentInstance(wrapper.element.__instance);
44
+ const NewGreeting = ({
45
+ name
46
+ }) => {
47
+ return _jsxs("div", {
48
+ className: "greeting",
49
+ children: ["Hi there ", name]
50
+ });
51
+ };
52
+ Object.defineProperty(NewGreeting, 'name', {
53
+ value: 'Greeting'
54
+ });
55
+ const result = await applyHmrModule({
56
+ Greeting: NewGreeting
57
+ }, '/test/applyHmrModule-match.tsx');
58
+ expect(result.matched).to.equal(true);
59
+ expect(result.orphaned).to.equal(false);
60
+ await testApp.unmount();
61
+ });
62
+ it('reports orphaned when a still-live component export disappears from the same file', async () => {
63
+ const window = new Window();
64
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
65
+ const Greeting = ({
66
+ name
67
+ }) => {
68
+ return _jsxs("div", {
69
+ className: "greeting",
70
+ children: ["Hello ", name]
71
+ });
72
+ };
73
+ class Root extends TungstenSimpleComponent {
74
+ constructor(...args) {
75
+ super(...args);
76
+ _defineProperty(this, "content", () => _jsx(Greeting, {
77
+ name: "Ada"
78
+ }));
79
+ }
80
+ }
81
+ const testApp = await createTestApp(Root, {
82
+ document
83
+ });
84
+ const wrapper = testApp.component.__currentContent;
85
+ if (!WideRenderedContentNodeIsComponent(wrapper) || !AnyComponentIsFunctionComponentStandIn(wrapper.element)) {
86
+ throw new Error('expected the root to directly render a single function component');
87
+ }
88
+ setCurrentInstance(wrapper.element.__instance);
89
+ const file = '/test/applyHmrModule-orphan.tsx';
90
+ // first edit: file still exports Greeting - establishes the "previously exported" baseline
91
+ const NewGreeting = ({
92
+ name
93
+ }) => {
94
+ return _jsxs("div", {
95
+ className: "greeting",
96
+ children: ["Hi there ", name]
97
+ });
98
+ };
99
+ Object.defineProperty(NewGreeting, 'name', {
100
+ value: 'Greeting'
101
+ });
102
+ const firstResult = await applyHmrModule({
103
+ Greeting: NewGreeting
104
+ }, file);
105
+ expect(firstResult.matched).to.equal(true);
106
+ expect(firstResult.orphaned).to.equal(false);
107
+ // second edit to the SAME file: Greeting has been renamed away - the live Greeting
108
+ // instance (now running NewGreeting's code) is still there, but nothing in the fresh
109
+ // module matches it any more
110
+ const Renamed = ({
111
+ name
112
+ }) => {
113
+ return _jsxs("div", {
114
+ className: "greeting",
115
+ children: ["Renamed ", name]
116
+ });
117
+ };
118
+ Object.defineProperty(Renamed, 'name', {
119
+ value: 'Renamed'
120
+ });
121
+ const secondResult = await applyHmrModule({
122
+ Renamed
123
+ }, file);
124
+ expect(secondResult.matched).to.equal(false);
125
+ expect(secondResult.orphaned).to.equal(true);
126
+ await testApp.unmount();
127
+ });
128
+ it('hot-swaps a component moved to a different file, since identity matching is name+kind based, not file-scoped', async () => {
129
+ const window = new Window();
130
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
131
+ const Greeting = ({
132
+ name
133
+ }) => {
134
+ return _jsxs("div", {
135
+ className: "greeting",
136
+ children: ["Hello ", name]
137
+ });
138
+ };
139
+ class Root extends TungstenSimpleComponent {
140
+ constructor(...args) {
141
+ super(...args);
142
+ _defineProperty(this, "content", () => _jsx(Greeting, {
143
+ name: "Ada"
144
+ }));
145
+ }
146
+ }
147
+ const testApp = await createTestApp(Root, {
148
+ document
149
+ });
150
+ const wrapper = testApp.component.__currentContent;
151
+ if (!WideRenderedContentNodeIsComponent(wrapper) || !AnyComponentIsFunctionComponentStandIn(wrapper.element)) {
152
+ throw new Error('expected the root to directly render a single function component');
153
+ }
154
+ setCurrentInstance(wrapper.element.__instance);
155
+ // simulate a single edit that moves Greeting out of its original file into a brand new
156
+ // one - both calls happen on the FIRST ever edit to each file, so neither has a
157
+ // previously-exported baseline to compare against (previousFileExportIdentities starts
158
+ // empty per file either way)
159
+ const MovedGreeting = ({
160
+ name
161
+ }) => {
162
+ return _jsxs("div", {
163
+ className: "greeting",
164
+ children: ["Moved hello ", name]
165
+ });
166
+ };
167
+ Object.defineProperty(MovedGreeting, 'name', {
168
+ value: 'Greeting'
169
+ });
170
+ const oldFileResult = await applyHmrModule({}, '/test/applyHmrModule-move-old-file.tsx');
171
+ const newFileResult = await applyHmrModule({
172
+ Greeting: MovedGreeting
173
+ }, '/test/applyHmrModule-move-new-file.tsx');
174
+ // the old file's own result correctly sees nothing to do (it's genuinely empty now) and
175
+ // isn't where the swap happens - the new file's result is what should show the match,
176
+ // since applyHmrModule matches by identity across the whole live registry, not per file
177
+ expect(oldFileResult.matched).to.equal(false);
178
+ expect(newFileResult.matched).to.equal(true);
179
+ expect(newFileResult.orphaned).to.equal(false);
180
+ await testApp.unmount();
181
+ });
182
+ });
@@ -0,0 +1,9 @@
1
+ import type { AnyComponent } from '../AnyComponent.js';
2
+ import type { AnyComponentType } from '../AnyComponentType.js';
3
+ export type ComponentKind = 'class' | 'function';
4
+ export declare function componentTypeIdentityKey(Type: AnyComponentType): string;
5
+ export declare function componentTypeKind(Type: AnyComponentType): ComponentKind;
6
+ export declare function liveComponentType(component: AnyComponent): AnyComponentType;
7
+ export declare function liveComponentKind(component: AnyComponent): ComponentKind;
8
+ export declare function liveComponentIdentityKey(component: AnyComponent): string;
9
+ //# sourceMappingURL=componentIdentity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentIdentity.d.ts","sourceRoot":"","sources":["../../src/hmr/componentIdentity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAO9D,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEhD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAEvE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CAEvE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,YAAY,GAAG,gBAAgB,CAI3E;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,YAAY,GAAG,aAAa,CAExE;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAExE"}
@@ -0,0 +1,17 @@
1
+ import { AnyComponentIsFunctionComponentStandIn } from "../AnyComponent.js";
2
+ import { AnyComponentTypeIsClassComponentType } from "../AnyComponentType.js";
3
+ export function componentTypeIdentityKey(Type) {
4
+ return Type.tagName || Type.name || Type.displayName || '--unnamed--';
5
+ }
6
+ export function componentTypeKind(Type) {
7
+ return AnyComponentTypeIsClassComponentType(Type) ? 'class' : 'function';
8
+ }
9
+ export function liveComponentType(component) {
10
+ return AnyComponentIsFunctionComponentStandIn(component) ? component.func : component.constructor; // eslint-disable-line no-type-assertion/no-type-assertion -- constructor of a TungstenBaseComponent instance is its component type
11
+ }
12
+ export function liveComponentKind(component) {
13
+ return AnyComponentIsFunctionComponentStandIn(component) ? 'function' : 'class';
14
+ }
15
+ export function liveComponentIdentityKey(component) {
16
+ return componentTypeIdentityKey(liveComponentType(component));
17
+ }
@@ -0,0 +1,4 @@
1
+ import type { Instance } from '../Instance/Instance.js';
2
+ export declare function setCurrentInstance(instance: Instance): void;
3
+ export declare function getCurrentInstance(): Instance | undefined;
4
+ //# sourceMappingURL=instanceRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instanceRegistry.d.ts","sourceRoot":"","sources":["../../src/hmr/instanceRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAiBvD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAE3D;AAED,wBAAgB,kBAAkB,IAAI,QAAQ,GAAG,SAAS,CAEzD"}
@@ -0,0 +1,6 @@
1
+ export function setCurrentInstance(instance) {
2
+ globalThis.__tungstenHmrInstance = instance; // eslint-disable-line no-type-assertion/no-type-assertion -- narrowing globalThis to add one optional dev-only field
3
+ }
4
+ export function getCurrentInstance() {
5
+ return globalThis.__tungstenHmrInstance; // eslint-disable-line no-type-assertion/no-type-assertion -- narrowing globalThis to add one optional dev-only field
6
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=instanceRegistry.sxyt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instanceRegistry.sxyt.d.ts","sourceRoot":"","sources":["../../src/hmr/instanceRegistry.sxyt.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ import { expect } from 'chai';
2
+ import { describe } from 'sxy-test-runner';
3
+ describe('instanceRegistry() - cross module-graph visibility', ({
4
+ it
5
+ }) => {
6
+ it('setCurrentInstance() is visible from a separately-instantiated copy of the module', async () => {
7
+ // In the real dev client, hydrate.tsx (and its call to setCurrentInstance) is baked into
8
+ // the rolled-up, single-file client.js bundle, while applyHmrModule.js (and whatever it
9
+ // imports, including this module) is loaded completely separately - as a plain, unbundled
10
+ // ES module fetched fresh from the package-copy location. Those are two distinct module
11
+ // instantiations of this same source file, each with their own copy of any module-scope
12
+ // state. A cache-busting query string reproduces exactly that split here: Node's ESM
13
+ // loader treats these two specifiers as separate module records, the same way the browser
14
+ // treats the bundle and the package-copy as unrelated.
15
+ const moduleA = await import("./instanceRegistry.js");
16
+ const copySpecifier = './instanceRegistry.js' + '?copy=1';
17
+ const moduleB = await import(copySpecifier);
18
+ const fakeInstance = {
19
+ marker: 'fake-instance-for-test'
20
+ }; // eslint-disable-line no-type-assertion/no-type-assertion, @typescript-eslint/no-explicit-any -- only the identity of this object matters for the test
21
+ moduleA.setCurrentInstance(fakeInstance);
22
+ expect(moduleB.getCurrentInstance()).to.equal(fakeInstance);
23
+ });
24
+ });
@@ -0,0 +1,10 @@
1
+ export type ReloadEventKind = 'js' | 'css';
2
+ export type ReloadEventInfo = {
3
+ kind: ReloadEventKind;
4
+ file?: string;
5
+ };
6
+ type ReloadListener = (info: ReloadEventInfo) => void;
7
+ export declare function onReloadEvent(listener: ReloadListener): () => void;
8
+ export declare function emitReloadEvent(kind: ReloadEventKind, file?: string): void;
9
+ export {};
10
+ //# sourceMappingURL=liveReloadHub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveReloadHub.d.ts","sourceRoot":"","sources":["../../src/hmr/liveReloadHub.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,KAAK,CAAA;AAE1C,MAAM,MAAM,eAAe,GAAG;IAC1B,IAAI,EAAE,eAAe,CAAA;IAGrB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;AAIrD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,IAAI,CAGlE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAG1E"}
@@ -7,6 +7,12 @@ export function onReloadEvent(listener) {
7
7
  listeners.add(listener);
8
8
  return () => listeners.delete(listener);
9
9
  }
10
- export function emitReloadEvent(kind) {
11
- for (const listener of listeners) listener(kind);
10
+ export function emitReloadEvent(kind, file) {
11
+ const info = file !== undefined ? {
12
+ kind,
13
+ file
14
+ } : {
15
+ kind
16
+ };
17
+ for (const listener of listeners) listener(info);
12
18
  }
@@ -0,0 +1,2 @@
1
+ export declare function refreshStylesheets(doc?: Document): Promise<void>[];
2
+ //# sourceMappingURL=refreshStylesheets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refreshStylesheets.d.ts","sourceRoot":"","sources":["../../src/hmr/refreshStylesheets.ts"],"names":[],"mappings":"AAMA,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,QAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAY5E"}
@@ -0,0 +1,26 @@
1
+ // dev-only: cache-bust every stylesheet link on a CSS (or style-affecting JS) change, without a
2
+ // gap where no stylesheet is applied. Setting an existing <link>'s href directly can produce a
3
+ // visible flash - the load timing isn't guaranteed the same way across browsers - so instead a
4
+ // new <link> is inserted right after the old one and only removed once the new one has finished
5
+ // loading (or errored, so a failed fetch doesn't leave two stylesheets applied forever). There's
6
+ // a brief window where both are applied at once; that's harmless for a same-file edit.
7
+ export function refreshStylesheets(doc = document) {
8
+ const oldLinks = [...doc.querySelectorAll('link[rel="stylesheet"]')];
9
+ return oldLinks.map(oldLink => new Promise(resolve => {
10
+ const url = oldLink.getAttribute('href')?.split('?')[0] ?? '';
11
+ const newLink = doc.createElement('link');
12
+ newLink.rel = 'stylesheet';
13
+ newLink.href = `${url}?t=${Date.now()}`;
14
+ const finish = () => {
15
+ oldLink.remove();
16
+ resolve();
17
+ };
18
+ newLink.addEventListener('load', finish, {
19
+ once: true
20
+ });
21
+ newLink.addEventListener('error', finish, {
22
+ once: true
23
+ });
24
+ oldLink.insertAdjacentElement('afterend', newLink);
25
+ }));
26
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=refreshStylesheets.sxyt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refreshStylesheets.sxyt.d.ts","sourceRoot":"","sources":["../../src/hmr/refreshStylesheets.sxyt.ts"],"names":[],"mappings":""}
@@ -0,0 +1,46 @@
1
+ import { expect } from 'chai';
2
+ import { Window } from 'happy-dom';
3
+ import { describe } from 'sxy-test-runner';
4
+ import { refreshStylesheets } from "./refreshStylesheets.js";
5
+ describe('refreshStylesheets()', ({
6
+ it
7
+ }) => {
8
+ it('adds the new stylesheet link and waits for it to load before removing the old one', async () => {
9
+ const window = new Window();
10
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
11
+ const oldLink = document.createElement('link');
12
+ oldLink.rel = 'stylesheet';
13
+ oldLink.href = '/.componentCss/ccss.css';
14
+ document.head.appendChild(oldLink);
15
+ const donePromises = refreshStylesheets(document);
16
+ expect(donePromises).to.have.length(1);
17
+ // give the (synchronous, in this test) DOM mutation a tick to land
18
+ await Promise.resolve();
19
+ const links = document.querySelectorAll('link[rel="stylesheet"]');
20
+ expect(links.length).to.equal(2);
21
+ expect(document.contains(oldLink)).to.equal(true);
22
+ const newLink = [...links].find(link => link !== oldLink); // eslint-disable-line no-type-assertion/no-type-assertion -- narrowed by the length check above
23
+ expect(newLink).to.not.equal(undefined);
24
+ expect(newLink.getAttribute('href')).to.match(/^\/\.componentCss\/ccss\.css\?t=\d+$/);
25
+ // simulate the browser finishing the fetch+parse of the new stylesheet
26
+ newLink.dispatchEvent(new window.Event('load')); // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Event is structurally compatible
27
+ await donePromises[0];
28
+ expect(document.contains(oldLink)).to.equal(false);
29
+ expect(document.querySelectorAll('link[rel="stylesheet"]').length).to.equal(1);
30
+ });
31
+ it('removes the old link even if the new stylesheet fails to load, rather than leaving both forever', async () => {
32
+ const window = new Window();
33
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
34
+ const oldLink = document.createElement('link');
35
+ oldLink.rel = 'stylesheet';
36
+ oldLink.href = '/.componentCss/ccss.css';
37
+ document.head.appendChild(oldLink);
38
+ const [done] = refreshStylesheets(document);
39
+ await Promise.resolve();
40
+ const newLink = [...document.querySelectorAll('link[rel="stylesheet"]')].find(link => link !== oldLink); // eslint-disable-line no-type-assertion/no-type-assertion -- there are exactly two links at this point, one of them is oldLink
41
+ expect(newLink).to.not.equal(undefined);
42
+ newLink.dispatchEvent(new window.Event('error')); // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Event is structurally compatible
43
+ await done;
44
+ expect(document.contains(oldLink)).to.equal(false);
45
+ });
46
+ });
@@ -1,5 +1,13 @@
1
1
  import type { InstanceConfig } from '../Instance/InstanceConfig.js';
2
2
  import type { AppRoutes } from '../routing/AppRoutes.js';
3
- import { TungstenBaseComponentType } from '../Components.jsx';
3
+ import { DomPointer } from '../DomPointer.js';
4
+ import { TungstenBaseComponentType, TungstenBaseComponent } from '../Components.jsx';
5
+ import { Instance } from '../Instance/Instance.js';
6
+ import '../hmr/applyHmrModule.js';
7
+ type HydrateMeta = {
8
+ insideForm: boolean;
9
+ };
4
10
  export declare function hydrate(appRoutes: AppRoutes, RootComponent: TungstenBaseComponentType | null, instanceConfig: InstanceConfig): Promise<void>;
11
+ export declare function hydrateRootComponent(instance: Instance, RootComponent: TungstenBaseComponentType, domPointer: DomPointer, meta: HydrateMeta): Promise<TungstenBaseComponent<object, undefined>>;
12
+ export {};
5
13
  //# sourceMappingURL=hydrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../../src/hydrate/hydrate.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAQnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AA0BxD,OAAO,EAAE,yBAAyB,EAAoD,MAAM,mBAAmB,CAAA;AAoC/G,wBAAsB,OAAO,CACzB,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,yBAAyB,GAAG,IAAI,EAC/C,cAAc,EAAE,cAAc,iBAyDjC"}
1
+ {"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../../src/hydrate/hydrate.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAQnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAoBxD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAM7C,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAA6B,MAAM,mBAAmB,CAAA;AAE/G,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AA0BlD,OAAO,0BAA0B,CAAA;AASjC,KAAK,WAAW,GAAG;IACf,UAAU,EAAE,OAAO,CAAA;CACtB,CAAA;AAKD,wBAAsB,OAAO,CACzB,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,yBAAyB,GAAG,IAAI,EAC/C,cAAc,EAAE,cAAc,iBA0DjC;AAyBD,wBAAsB,oBAAoB,CACtC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,yBAAyB,EACxC,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,WAAW,qDA4DpB"}
@@ -7,7 +7,7 @@ import { DomPointer } from "../DomPointer.js";
7
7
  import { DomRegion } from "../DomRegion.js";
8
8
  //import { FunctionComponent } from '../FunctionComponent.js'
9
9
  import { FunctionElement } from "../FunctionComponentStandIn.js";
10
- import { getContentFromFunctionComponent } from "../getContentFromFunctionComponent.js";
10
+ import { getStyledContentFromFunctionComponent } from "../getStyledContentFromFunctionComponent.js";
11
11
  import { StringIsHtmlEvent, htmlEvents } from "../htmlEvents.js";
12
12
  import { TungstenStatefulComponent } from "../Components.js";
13
13
  import { TungstenError } from "../TungstenError.js";
@@ -29,6 +29,14 @@ import { MatchedRoute } from "../routing/MatchedRoute.js";
29
29
  import { isDev } from "../isDev.js";
30
30
  import { warn } from "../appOutput.js";
31
31
  import { reconcileHydratedAttributes } from "./reconcileHydratedAttributes.js";
32
+ import { setCurrentInstance } from "../hmr/instanceRegistry.js";
33
+ // side-effect only: pulls applyHmrModule.js (and its clean, non-dynamic-import dependencies)
34
+ // into the framework's traced dependency graph so it gets copied to .packages/ and is servable
35
+ // to the browser. It is invoked from a raw, untraced inline script (processHead.ts) instead of
36
+ // a real import here, because it does a computed-URL dynamic import() to fetch whichever file
37
+ // just recompiled - and any dynamic import() with a non-literal argument anywhere in the traced
38
+ // graph makes the framework's package-copying step throw. See tungsten/docs/hmr.md.
39
+ import "../hmr/applyHmrModule.js";
32
40
  import { jsx as _jsx } from "tungsten/jsxRuntime.js";
33
41
  function debug(...texts) {
34
42
  console.log(...texts);
@@ -56,6 +64,7 @@ export async function hydrate(appRoutes, RootComponent, instanceConfig) {
56
64
  // const stores: TrueStore<any>[] = createStores(null, matchedRouteInfo, globalData)
57
65
  //console.log('hydration instance config', instanceConfig)
58
66
  const instance = new Instance(location, instanceConfig, null, appRoutes, routes, internalStore, stores, storeSubscriptions, globalData, matchedRouteInfo);
67
+ if (isDev) setCurrentInstance(instance);
59
68
  //const routes = instance.getRoutes()
60
69
  // we know which route was matched
61
70
  // if (typeof __tungsten_router_data__ !== 'undefined') {
@@ -105,7 +114,7 @@ routes) {
105
114
  };
106
115
  }
107
116
  }
108
- async function hydrateRootComponent(instance, RootComponent, domPointer, meta) {
117
+ export async function hydrateRootComponent(instance, RootComponent, domPointer, meta) {
109
118
  debug('hydrateRootComponent');
110
119
  const element = new RootComponent({
111
120
  props: {},
@@ -146,6 +155,7 @@ async function hydrateRootComponent(instance, RootComponent, domPointer, meta) {
146
155
  // closingComment
147
156
  // )
148
157
  element.__didHydrate();
158
+ return element;
149
159
  }
150
160
  // if the root comp has a title, save it as the default title to use after navigation in the instance
151
161
  function handleRootTitle(rootElement) {
@@ -226,7 +236,12 @@ async function hydrateClassComponent(node, parentElement, domPointer, instance,
226
236
  const updateResult = await integrateElementUpdateVdom(element, initialContent);
227
237
  element.__currentContent = updateResult.renderedContent;
228
238
  element.__didHydrate();
229
- return makeRenderedContentNodeClassComponent(node, element, openingComment, closingComment);
239
+ const wrapper = makeRenderedContentNodeClassComponent(node, element, openingComment, closingComment);
240
+ // the reconciler's own equivalents (integrateUpdateVdom.ts) set this back-reference so
241
+ // HMR can locate/replace a live component's wrapper node - hydration builds its own
242
+ // wrapper independently and has to set it too. See tungsten/docs/hmr.md.
243
+ element.__renderedNode = wrapper;
244
+ return wrapper;
230
245
  } else {
231
246
  const renderedContent = await hydrateNode(initialContent, element, domPointer, instance, meta);
232
247
  const closingNode = domPointer.get();
@@ -243,7 +258,12 @@ async function hydrateClassComponent(node, parentElement, domPointer, instance,
243
258
  //console.log('hydrated', element.constructor.name, 'with content', renderedContent)
244
259
  element.__currentContent = renderedContent;
245
260
  element.__didHydrate();
246
- return makeRenderedContentNodeClassComponent(node, element, openingComment, closingComment);
261
+ const wrapper = makeRenderedContentNodeClassComponent(node, element, openingComment, closingComment);
262
+ // the reconciler's own equivalents (integrateUpdateVdom.ts) set this back-reference so
263
+ // HMR can locate/replace a live component's wrapper node - hydration builds its own
264
+ // wrapper independently and has to set it too. See tungsten/docs/hmr.md.
265
+ element.__renderedNode = wrapper;
266
+ return wrapper;
247
267
  }
248
268
  }
249
269
  async function renderClassComponent({
@@ -306,8 +326,7 @@ async function hydrateFunctionComponent(node, parentElement, domPointer, instanc
306
326
  //// InstantiateFunctionComponent???
307
327
  const functionComponentStandIn = new FunctionElement(Component, props, routeParams, routeData, instance, parentElement);
308
328
  setCurrentComponent(functionComponentStandIn);
309
- const content = await getContentFromFunctionComponent(functionComponentStandIn);
310
- //const content = await getContentFromFunctionComponent(Component, props)
329
+ const content = await getStyledContentFromFunctionComponent(functionComponentStandIn);
311
330
  clearCurrentComponent();
312
331
  //// Add to parent childNodes?
313
332
  const domIndexStart = domPointer.index;
@@ -328,7 +347,12 @@ async function hydrateFunctionComponent(node, parentElement, domPointer, instanc
328
347
  functionComponentStandIn.__domRegion = new DomRegion(domPointer.parent, domIndexStart, domIndexCount);
329
348
  functionComponentStandIn.__currentContent = renderedTree;
330
349
  //return functionComponentStandIn
331
- return makeRenderedContentNodeFunctionComponent(node, functionComponentStandIn, openingComment, closingComment);
350
+ const wrapper = makeRenderedContentNodeFunctionComponent(node, functionComponentStandIn, openingComment, closingComment);
351
+ // the reconciler's own equivalents (integrateUpdateVdom.ts) set this back-reference so HMR
352
+ // can locate/replace a live component's wrapper node - hydration builds its own wrapper
353
+ // independently and has to set it too. See tungsten/docs/hmr.md.
354
+ functionComponentStandIn.__renderedNode = wrapper;
355
+ return wrapper;
332
356
  }
333
357
  //// why don't we have html, nodes in here?
334
358
  async function hydrateNode(node, parentElement, domPointer, instance, meta) {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hydrate.sxyt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydrate.sxyt.d.ts","sourceRoot":"","sources":["../../src/hydrate/hydrate.sxyt.tsx"],"names":[],"mappings":""}