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,93 @@
1
+ import { expect } from 'chai';
2
+ import { Window } from 'happy-dom';
3
+ import { describe } from 'sxy-test-runner';
4
+ import { processHead } from "./processHead.js";
5
+ import { $props, $type } from "../contentNodeSymbols.js";
6
+ import { Instance } from "../Instance/Instance.js";
7
+ import { initStores } from "../Servers/initStores.js";
8
+ import { UpdateScheduler } from "../update/UpdateScheduler.js";
9
+ function makeHeadData(hmr) {
10
+ const location = '/';
11
+ const globalData = {};
12
+ const matchedRouteInfo = {
13
+ key: null,
14
+ params: {},
15
+ content: null
16
+ };
17
+ const {
18
+ internalStore,
19
+ stores,
20
+ storeSubscriptions
21
+ } = initStores(location, null, globalData, matchedRouteInfo);
22
+ const instanceConfig = {
23
+ prod: false,
24
+ useRootComponent: true,
25
+ clientSideNav: false,
26
+ siteBasePath: '/',
27
+ staticSite: false
28
+ };
29
+ const emptyRoutes = {}; // eslint-disable-line no-type-assertion/no-type-assertion -- not exercised by this test
30
+ const window = new Window();
31
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
32
+ const scheduler = new UpdateScheduler();
33
+ // navigation: false sidesteps Instance's popstate listener setup, which reaches for the
34
+ // process-wide `window` global when no runtimeContext is given at all - see hydrate.sxyt.tsx
35
+ const instance = new Instance(location, instanceConfig, null, emptyRoutes, emptyRoutes,
36
+ // eslint-disable-line no-type-assertion/no-type-assertion
37
+ internalStore, stores, storeSubscriptions, globalData, matchedRouteInfo, {
38
+ document,
39
+ window: document.defaultView,
40
+ scheduler,
41
+ navigation: false
42
+ });
43
+ return {
44
+ found: false,
45
+ instance,
46
+ clientImportAliases: {},
47
+ importAliases: {},
48
+ compilationLocation: '',
49
+ dev: true,
50
+ tungstenPublicEnv: undefined,
51
+ siteBasePath: '',
52
+ hmr
53
+ };
54
+ }
55
+ function reloadScriptText(headNode) {
56
+ const children = headNode[$props].children ?? [];
57
+ const scripts = children.filter(node => node[$type] === 'script');
58
+ const reloadScript = scripts.find(node => typeof node[$props].children === 'string' && node[$props].children.includes('__tungsten_live_reload__'));
59
+ return reloadScript?.[$props].children; // eslint-disable-line no-type-assertion/no-type-assertion -- narrowed by the find() predicate above
60
+ }
61
+ describe('processHead() - hmr config option', ({
62
+ it
63
+ }) => {
64
+ it('injects the full Tier 2 hot-swap script when hmr is true', () => {
65
+ const headNode = {
66
+ [$type]: 'head',
67
+ [$props]: {}
68
+ };
69
+ processHead(headNode, makeHeadData(true));
70
+ const script = reloadScriptText(headNode);
71
+ expect(script).to.contain('applyHmrModule');
72
+ expect(script).to.contain('refreshStylesheets');
73
+ });
74
+ it(`injects only the Tier 1 always-full-reload script when hmr is 'reload'`, () => {
75
+ const headNode = {
76
+ [$type]: 'head',
77
+ [$props]: {}
78
+ };
79
+ processHead(headNode, makeHeadData('reload'));
80
+ const script = reloadScriptText(headNode);
81
+ expect(script).to.not.contain('applyHmrModule');
82
+ expect(script).to.not.contain('refreshStylesheets');
83
+ expect(script).to.contain('location.reload()');
84
+ });
85
+ it('injects no live-reload script at all when hmr is false', () => {
86
+ const headNode = {
87
+ [$type]: 'head',
88
+ [$props]: {}
89
+ };
90
+ processHead(headNode, makeHeadData(false));
91
+ expect(reloadScriptText(headNode)).to.equal(undefined);
92
+ });
93
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hotSwap.sxyt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotSwap.sxyt.d.ts","sourceRoot":"","sources":["../../src/update/hotSwap.sxyt.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,252 @@
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 { hotSwapFunctionComponent, hotSwapClassComponent } from "./integrateUpdateVdom.js";
10
+ import { TungstenStatefulComponent } from "../Components.js";
11
+ import { WideRenderedContentNodeIsComponent } from "../RenderedContentNode.js";
12
+ import { AnyComponentIsFunctionComponentStandIn } from "../AnyComponent.js";
13
+ import { jsxs as _jsxs, jsx as _jsx } from "tungsten/jsxRuntime.js";
14
+ const Greeting = ({
15
+ name
16
+ }) => {
17
+ return _jsxs("div", {
18
+ className: "greeting",
19
+ children: ["Hello ", name]
20
+ });
21
+ };
22
+ describe('hotSwapFunctionComponent', ({
23
+ it
24
+ }) => {
25
+ it('produces the same markup a normal render does - no leaked "children" attribute', async () => {
26
+ const window = new Window();
27
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
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 before = testApp.container.innerHTML;
40
+ const wrapper = testApp.component.__currentContent;
41
+ if (!WideRenderedContentNodeIsComponent(wrapper) || !AnyComponentIsFunctionComponentStandIn(wrapper.element)) {
42
+ throw new Error('expected the root to directly render a single function component');
43
+ }
44
+ const oldElement = wrapper.element;
45
+ // a second, distinct function reference standing in for "the recompiled version"
46
+ const NewGreeting = ({
47
+ name
48
+ }) => {
49
+ return _jsxs("div", {
50
+ className: "greeting",
51
+ children: ["Hello ", name]
52
+ });
53
+ };
54
+ const swapped = await hotSwapFunctionComponent(oldElement, NewGreeting);
55
+ expect(swapped).to.not.equal(null);
56
+ const after = testApp.container.innerHTML;
57
+ expect(before).to.not.contain('children=');
58
+ expect(after).to.not.contain('children=');
59
+ expect(after).to.equal(before);
60
+ await testApp.unmount();
61
+ });
62
+ it('actually renders the NEW function\'s output, not the old one', 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
+ class Root extends TungstenSimpleComponent {
66
+ constructor(...args) {
67
+ super(...args);
68
+ _defineProperty(this, "content", () => _jsx(Greeting, {
69
+ name: "Ada"
70
+ }));
71
+ }
72
+ }
73
+ const testApp = await createTestApp(Root, {
74
+ document
75
+ });
76
+ const wrapper = testApp.component.__currentContent;
77
+ if (!WideRenderedContentNodeIsComponent(wrapper) || !AnyComponentIsFunctionComponentStandIn(wrapper.element)) {
78
+ throw new Error('expected the root to directly render a single function component');
79
+ }
80
+ const oldElement = wrapper.element;
81
+ // deliberately DIFFERENT markup - simulating an actual source edit
82
+ const NewGreeting = ({
83
+ name
84
+ }) => {
85
+ return _jsxs("div", {
86
+ className: "greeting",
87
+ children: ["Hi there ", name]
88
+ });
89
+ };
90
+ const swapped = await hotSwapFunctionComponent(oldElement, NewGreeting);
91
+ expect(swapped).to.not.equal(null);
92
+ expect(testApp.container.innerHTML).to.contain('Hi there Ada');
93
+ expect(testApp.container.innerHTML).to.not.contain('Hello Ada');
94
+ await testApp.unmount();
95
+ });
96
+ });
97
+ describe('hotSwapClassComponent', ({
98
+ it
99
+ }) => {
100
+ it('hot-swaps a page-level component with several top-level children (h1, select/option, button)', async () => {
101
+ class Page extends TungstenStatefulComponent {
102
+ constructor(...args) {
103
+ super(...args);
104
+ _defineProperty(this, "state", {
105
+ value: 5
106
+ });
107
+ _defineProperty(this, "content", () => _jsxs("div", {
108
+ children: [_jsx("h1", {
109
+ children: "Welcome"
110
+ }), _jsx("p", {
111
+ children: "one"
112
+ }), _jsx("p", {
113
+ children: "two"
114
+ }), _jsxs("select", {
115
+ value: this.state.value,
116
+ children: [_jsx("option", {
117
+ children: " 2 "
118
+ }), _jsx("option", {
119
+ children: " 4 "
120
+ }), _jsx("option", {
121
+ children: _jsx("span", {
122
+ children: "5"
123
+ })
124
+ }), _jsx("option", {
125
+ children: " 8 "
126
+ })]
127
+ }), _jsx("button", {
128
+ onClick: () => {
129
+ this.state.value = 8;
130
+ },
131
+ children: "set"
132
+ })]
133
+ }));
134
+ }
135
+ }
136
+ const window = new Window();
137
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
138
+ class Root extends TungstenSimpleComponent {
139
+ constructor(...args) {
140
+ super(...args);
141
+ _defineProperty(this, "content", () => _jsx(Page, {}));
142
+ }
143
+ }
144
+ const testApp = await createTestApp(Root, {
145
+ document
146
+ });
147
+ const wrapper = testApp.component.__currentContent;
148
+ if (!WideRenderedContentNodeIsComponent(wrapper) || AnyComponentIsFunctionComponentStandIn(wrapper.element)) {
149
+ throw new Error('expected the root to directly render a single class component');
150
+ }
151
+ const oldElement = wrapper.element;
152
+ class NewPage extends TungstenStatefulComponent {
153
+ constructor(...args) {
154
+ super(...args);
155
+ _defineProperty(this, "state", {
156
+ value: 5
157
+ });
158
+ _defineProperty(this, "content", () => _jsxs("div", {
159
+ children: [_jsx("h1", {
160
+ children: "Welcome (edited)"
161
+ }), _jsx("p", {
162
+ children: "one"
163
+ }), _jsx("p", {
164
+ children: "two"
165
+ }), _jsxs("select", {
166
+ value: this.state.value,
167
+ children: [_jsx("option", {
168
+ children: " 2 "
169
+ }), _jsx("option", {
170
+ children: " 4 "
171
+ }), _jsx("option", {
172
+ children: _jsx("span", {
173
+ children: "5"
174
+ })
175
+ }), _jsx("option", {
176
+ children: " 8 "
177
+ })]
178
+ }), _jsx("button", {
179
+ onClick: () => {
180
+ this.state.value = 8;
181
+ },
182
+ children: "set"
183
+ })]
184
+ }));
185
+ }
186
+ }
187
+ const swapped = await hotSwapClassComponent(oldElement, NewPage);
188
+ expect(swapped).to.not.equal(null);
189
+ expect(testApp.container.innerHTML).to.contain('Welcome (edited)');
190
+ await testApp.unmount();
191
+ });
192
+ it('surfaces a clear error when swapped-in content references an undefined component', async () => {
193
+ // Investigating a "TungstenError: new DomRegion() error: first child is undefined" crash
194
+ // seen while live-testing HMR (hot-swapping Home, playground's page-level component, a
195
+ // moment after Home.tsx's import of `Orphan` became a dangling reference to a just-removed
196
+ // export). The previous test proves a clean multi-child page-level swap works fine, so
197
+ // that wasn't a general page-level-swap bug. This test rules out the other suspect: an
198
+ // undefined component reference does NOT reach DomRegion at all - it's caught much
199
+ // earlier, with a clear JSX runtime error. So the live DomRegion crash's exact trigger
200
+ // remains unconfirmed; what's confirmed is that the outer .catch() in applyHmrModule's
201
+ // caller safely falls back to a full reload either way (verified live), so it isn't a
202
+ // silent failure - just an unclear error message for a rare, transient mid-edit state.
203
+ const UndefinedComponent = undefined; // eslint-disable-line no-type-assertion/no-type-assertion -- simulating an import resolving to undefined, exactly like a removed export would
204
+ const window = new Window();
205
+ const document = window.document; // eslint-disable-line no-type-assertion/no-type-assertion -- happy-dom's Document is structurally compatible
206
+ class Page extends TungstenStatefulComponent {
207
+ constructor(...args) {
208
+ super(...args);
209
+ _defineProperty(this, "state", {});
210
+ _defineProperty(this, "content", () => _jsx("div", {
211
+ children: _jsx("h1", {
212
+ children: "Welcome"
213
+ })
214
+ }));
215
+ }
216
+ }
217
+ class Root extends TungstenSimpleComponent {
218
+ constructor(...args) {
219
+ super(...args);
220
+ _defineProperty(this, "content", () => _jsx(Page, {}));
221
+ }
222
+ }
223
+ const testApp = await createTestApp(Root, {
224
+ document
225
+ });
226
+ const wrapper = testApp.component.__currentContent;
227
+ if (!WideRenderedContentNodeIsComponent(wrapper) || AnyComponentIsFunctionComponentStandIn(wrapper.element)) {
228
+ throw new Error('expected the root to directly render a single class component');
229
+ }
230
+ const oldElement = wrapper.element;
231
+ class NewPage extends TungstenStatefulComponent {
232
+ constructor(...args) {
233
+ super(...args);
234
+ _defineProperty(this, "state", {});
235
+ _defineProperty(this, "content", () => _jsxs("div", {
236
+ children: [_jsx("h1", {
237
+ children: "Welcome (edited)"
238
+ }), _jsx(UndefinedComponent, {})]
239
+ }));
240
+ }
241
+ }
242
+ let caught;
243
+ try {
244
+ await hotSwapClassComponent(oldElement, NewPage);
245
+ } catch (error) {
246
+ caught = error;
247
+ }
248
+ expect(caught).to.not.equal(undefined);
249
+ expect(String(caught)).to.contain('did not recognize element');
250
+ await testApp.unmount();
251
+ });
252
+ });
@@ -1,12 +1,14 @@
1
1
  import { DomPointer } from '../DomPointer.js';
2
2
  import { ContentNode, ContentNodeHtml, ContentNodeComponentAny } from '../ContentNode.js';
3
3
  import { Instance } from '../Instance/Instance.js';
4
+ import { FunctionElement } from '../FunctionComponentStandIn.js';
4
5
  import { TungstenElement } from '../TungstenElement.js';
5
6
  import { AnyComponent } from '../AnyComponent.js';
6
7
  import { $ContentNull } from '../symbols.js';
7
8
  import { RenderedContentNode, RenderedContentNodeComponent } from '../RenderedContentNode.js';
8
9
  import { UpdateResult } from './UpdateResult.js';
9
- import type { TungstenBaseComponentType } from '../Components.jsx';
10
+ import type { TungstenBaseComponent, TungstenBaseComponentType } from '../Components.jsx';
11
+ import type { FunctionComponent } from '../FunctionComponent.js';
10
12
  export type TungstenHTMLElement = HTMLElement & {
11
13
  __tungsten_click_handler: (this: HTMLElement, ev: MouseEvent) => any;
12
14
  [key: string]: unknown;
@@ -28,5 +30,7 @@ export type MountedTungstenRoot<ComponentType extends TungstenBaseComponentType<
28
30
  };
29
31
  export declare function createTungstenRootComponent<ComponentType extends TungstenBaseComponentType<any, any>>(RootComponent: ComponentType, props: InstanceType<ComponentType>['props'], routeParams: Record<string, string> | undefined, routeData: unknown, instance: Instance, container: HTMLElement): Promise<MountedTungstenRoot<ComponentType>>;
30
32
  export declare function deleteTungstenRoot(root: MountedTungstenRoot<TungstenBaseComponentType<any, any>>): void;
33
+ export declare function hotSwapFunctionComponent(oldElement: FunctionElement<any, any>, NewType: FunctionComponent<any, any>): Promise<FunctionElement<any, any> | null>;
34
+ export declare function hotSwapClassComponent(oldElement: TungstenBaseComponent<any, any>, NewType: TungstenBaseComponentType<any, any>): Promise<TungstenBaseComponent<any, any> | null>;
31
35
  export {};
32
36
  //# sourceMappingURL=integrateUpdateVdom.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integrateUpdateVdom.d.ts","sourceRoot":"","sources":["../../src/update/integrateUpdateVdom.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EACH,WAAW,EAAwC,eAAe,EAElE,uBAAuB,EAG1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAKlD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGjD,OAAO,EAAE,YAAY,EAAgB,MAAM,eAAe,CAAA;AAE1D,OAAO,EACe,mBAAmB,EAIT,4BAA4B,EAI3D,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAYhD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAGlE,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG;IAC5C,wBAAwB,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,UAAU,KAAK,GAAG,CAAA;IACpE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACzB,CAAA;AAYD,wBAAsB,0BAA0B,CAC5C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,GAC/C,OAAO,CAAC,YAAY,CAAC,CA0GvB;AA8gBD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,mBAAmB,QAuC5E;AAgED,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,QAkK7E;AA0WD,KAAK,2BAA2B,GAAG;IAC/B,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,uBAAuB,CAAC;IAE9B,UAAU,EAAE,UAAU,CAAA;CACzB,CAAA;AACD,wBAAsB,uBAAuB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,2BAA2B,GAC3G,OAAO,CAAC,4BAA4B,CAAC,CAU1C;AAED,MAAM,MAAM,mBAAmB,CAC3B,aAAa,SAAS,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,IACzD;IACA,SAAS,EAAE,YAAY,CAAC,aAAa,CAAC,CAAA;IACtC,cAAc,EAAE,OAAO,CAAA;IACvB,cAAc,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,wBAAsB,2BAA2B,CAC7C,aAAa,SAAS,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,EAEzD,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAC3C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC/C,SAAS,EAAE,OAAO,EAClB,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,WAAW,GACvB,OAAO,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAgD7C;AAED,wBAAgB,kBAAkB,CAC9B,IAAI,EAAE,mBAAmB,CAAC,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAC/D,IAAI,CAON"}
1
+ {"version":3,"file":"integrateUpdateVdom.d.ts","sourceRoot":"","sources":["../../src/update/integrateUpdateVdom.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EACH,WAAW,EAAwC,eAAe,EAElE,uBAAuB,EAG1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAIlD,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGjD,OAAO,EAAE,YAAY,EAAgB,MAAM,eAAe,CAAA;AAE1D,OAAO,EACe,mBAAmB,EAIT,4BAA4B,EAI3D,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAYhD,OAAO,KAAK,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AACzF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAIhE,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG;IAC5C,wBAAwB,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,UAAU,KAAK,GAAG,CAAA;IACpE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACzB,CAAA;AAYD,wBAAsB,0BAA0B,CAC5C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,GAC/C,OAAO,CAAC,YAAY,CAAC,CA0GvB;AA8gBD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,mBAAmB,QAuC5E;AAgED,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,QAkK7E;AA0WD,KAAK,2BAA2B,GAAG;IAC/B,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,uBAAuB,CAAC;IAE9B,UAAU,EAAE,UAAU,CAAA;CACzB,CAAA;AACD,wBAAsB,uBAAuB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,2BAA2B,GAC3G,OAAO,CAAC,4BAA4B,CAAC,CAU1C;AAED,MAAM,MAAM,mBAAmB,CAC3B,aAAa,SAAS,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,IACzD;IACA,SAAS,EAAE,YAAY,CAAC,aAAa,CAAC,CAAA;IACtC,cAAc,EAAE,OAAO,CAAA;IACvB,cAAc,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,wBAAsB,2BAA2B,CAC7C,aAAa,SAAS,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,EAEzD,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAC3C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC/C,SAAS,EAAE,OAAO,EAClB,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,WAAW,GACvB,OAAO,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAgD7C;AAED,wBAAgB,kBAAkB,CAC9B,IAAI,EAAE,mBAAmB,CAAC,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAC/D,IAAI,CAON;AAmOD,wBAAsB,wBAAwB,CAC1C,UAAU,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,GAC5E,OAAO,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAiC3C;AAED,wBAAsB,qBAAqB,CACvC,UAAU,EAAE,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,GAC1F,OAAO,CAAC,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAyCjD"}
@@ -22,6 +22,7 @@ import { validChildren } from "../validChildren.js";
22
22
  import { setCurrentComponent } from 'tungsten/hooks/setCurrentComponent.js';
23
23
  import { clearCurrentComponent } from 'tungsten/hooks/clearCurrentComponent.js';
24
24
  import { isDev } from "../isDev.js";
25
+ import { $original } from "../componentProxyHandlers.js";
25
26
  function debug(...texts) {
26
27
  //console.log(...texts)
27
28
  //console.log(getStackTrace().split('\n')[2])
@@ -1135,12 +1136,14 @@ async function createTungstenClassComponent(parent, instance, node, domPointer)
1135
1136
  //console.log('created dom region', element.__domRegion)
1136
1137
  element.__currentContent = renderedContent;
1137
1138
  element.__didMount();
1138
- return {
1139
+ const wrapper = {
1139
1140
  ...node,
1140
1141
  element,
1141
1142
  openingComment,
1142
1143
  closingComment
1143
1144
  };
1145
+ element.__renderedNode = wrapper;
1146
+ return wrapper;
1144
1147
  }
1145
1148
  let xx = 0;
1146
1149
  async function createTungstenFunctionComponent(parent, instance, node, domPointer) {
@@ -1186,12 +1189,14 @@ async function createTungstenFunctionComponent(parent, instance, node, domPointe
1186
1189
  //console.log('&& dom region first element', element.__domRegion.firstElement)
1187
1190
  //console.log('&& dom region last element', element.__domRegion.lastElement)
1188
1191
  element.__currentContent = renderedContent;
1189
- return {
1192
+ const wrapper = {
1190
1193
  ...node,
1191
1194
  element,
1192
1195
  openingComment,
1193
1196
  closingComment
1194
1197
  };
1198
+ element.__renderedNode = wrapper;
1199
+ return wrapper;
1195
1200
  }
1196
1201
  async function updateClassComponent({
1197
1202
  newNode,
@@ -1209,12 +1214,14 @@ async function updateClassComponent({
1209
1214
  //throwError('','')
1210
1215
  const updateResult = await element.__update();
1211
1216
  domPointer.increment(updateResult.nodesLength); // bump the dom pointer index forward by the number of nodes within that element's content
1212
- return {
1217
+ const wrapper = {
1213
1218
  ...newNode,
1214
1219
  element,
1215
1220
  openingComment: oldNode.openingComment,
1216
1221
  closingComment: oldNode.closingComment
1217
1222
  };
1223
+ element.__renderedNode = wrapper;
1224
+ return wrapper;
1218
1225
  }
1219
1226
  async function updateFunctionComponent({
1220
1227
  newNode,
@@ -1227,10 +1234,106 @@ async function updateFunctionComponent({
1227
1234
  standIn.props = newNode[$props];
1228
1235
  const updateResult = await standIn.update();
1229
1236
  domPointer.increment(updateResult.nodesLength); // bump the dom pointer index forward by the number of nodes within that element's content
1230
- return {
1237
+ const wrapper = {
1231
1238
  ...newNode,
1232
1239
  element: standIn,
1233
1240
  openingComment: oldNode.openingComment,
1234
1241
  closingComment: oldNode.closingComment
1235
1242
  };
1243
+ standIn.__renderedNode = wrapper;
1244
+ return wrapper;
1245
+ }
1246
+ function hotSwapPrepare(oldElement) {
1247
+ const wrapper = oldElement.__renderedNode;
1248
+ if (wrapper === undefined || oldElement.__domRegion === undefined) return null;
1249
+ const parent = oldElement.__domRegion.parent;
1250
+ const openingCommentIndex = Array.prototype.indexOf.call(parent.childNodes, wrapper.openingComment);
1251
+ if (openingCommentIndex < 0) return null;
1252
+ const domStartIndex = openingCommentIndex + 1;
1253
+ // tear down everything between the comments, keep the comments themselves
1254
+ oldElement.__delete();
1255
+ deleteNodeIfRendered(oldElement.__currentContent);
1256
+ oldElement.__deleted();
1257
+ return {
1258
+ parent,
1259
+ domPointer: new DomPointer(parent, domStartIndex),
1260
+ domStartIndex
1261
+ };
1262
+ }
1263
+ export async function hotSwapFunctionComponent(oldElement, NewType) {
1264
+ const prep = hotSwapPrepare(oldElement);
1265
+ if (prep === null) return null;
1266
+ const {
1267
+ parent,
1268
+ domPointer,
1269
+ domStartIndex
1270
+ } = prep;
1271
+ const wrapper = oldElement.__renderedNode; // present - checked in hotSwapPrepare
1272
+ if (wrapper === undefined) return null;
1273
+ const standIn = new FunctionElement(NewType, oldElement.props, oldElement.routeParams, oldElement.routeData, oldElement.__instance, oldElement.parentComponent);
1274
+ // carry the old slot-indexed hook state across - see tungsten/docs/hmr.md for the
1275
+ // (unverifiable, accepted) risk this carries when hook shape changed between edits
1276
+ standIn.state = oldElement.state;
1277
+ standIn.effects = oldElement.effects;
1278
+ standIn.memos = oldElement.memos;
1279
+ standIn.callbacks = oldElement.callbacks;
1280
+ setCurrentComponent(standIn);
1281
+ const content = await getStyledContentFromFunctionComponent(standIn);
1282
+ clearCurrentComponent();
1283
+ const renderedContent = await createNode({
1284
+ tungstenElement: standIn,
1285
+ instance: oldElement.__instance,
1286
+ newNode: content,
1287
+ domPointer,
1288
+ parentNode: null
1289
+ });
1290
+ standIn.__domRegion = new DomRegion(parent, domStartIndex, domPointer.index - domStartIndex);
1291
+ standIn.__currentContent = renderedContent;
1292
+ wrapper.element = standIn;
1293
+ wrapper[$props] = standIn.props;
1294
+ standIn.__renderedNode = wrapper;
1295
+ return standIn;
1296
+ }
1297
+ export async function hotSwapClassComponent(oldElement, NewType) {
1298
+ const prep = hotSwapPrepare(oldElement);
1299
+ if (prep === null) return null;
1300
+ const {
1301
+ parent,
1302
+ domPointer,
1303
+ domStartIndex
1304
+ } = prep;
1305
+ const wrapper = oldElement.__renderedNode; // present - checked in hotSwapPrepare
1306
+ if (wrapper === undefined) return null;
1307
+ const newElement = new NewType({
1308
+ props: oldElement.props,
1309
+ routeParams: oldElement.routeParams,
1310
+ routeData: oldElement.routeData,
1311
+ instance: oldElement.__instance,
1312
+ parentElement: oldElement.parentComponent
1313
+ });
1314
+ // carry the old state across, unwrapping the old proxy so the new instance's own
1315
+ // constructor-created proxy (bound to itself) is what ends up owning it
1316
+ if ('state' in oldElement && oldElement.state !== undefined && 'state' in newElement) {
1317
+ const oldState = oldElement.state; // eslint-disable-line no-type-assertion/no-type-assertion -- state shape is unknown to this generic dev-only code path
1318
+ const rawOldState = $original in oldState ? oldState[$original] // eslint-disable-line no-type-assertion/no-type-assertion -- $original always yields the raw backing object
1319
+ : oldState;
1320
+ const newState = newElement.state; // eslint-disable-line no-type-assertion/no-type-assertion -- state shape is unknown to this generic dev-only code path
1321
+ for (const key in rawOldState) newState[key] = rawOldState[key];
1322
+ }
1323
+ await newElement.__init();
1324
+ const content = await newElement.__initialContent();
1325
+ const renderedContent = await createNode({
1326
+ tungstenElement: newElement,
1327
+ instance: oldElement.__instance,
1328
+ newNode: content,
1329
+ domPointer,
1330
+ parentNode: null
1331
+ });
1332
+ newElement.__domRegion = new DomRegion(parent, domStartIndex, domPointer.index - domStartIndex);
1333
+ newElement.__currentContent = renderedContent;
1334
+ newElement.__didMount();
1335
+ wrapper.element = newElement;
1336
+ wrapper[$props] = newElement.props;
1337
+ newElement.__renderedNode = wrapper;
1338
+ return newElement;
1236
1339
  }
@@ -0,0 +1,2 @@
1
+ export declare function warnOnFrameworkChange(): void;
2
+ //# sourceMappingURL=warnOnFrameworkChange.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"warnOnFrameworkChange.d.ts","sourceRoot":"","sources":["../src/warnOnFrameworkChange.ts"],"names":[],"mappings":"AASA,wBAAgB,qBAAqB,IAAI,IAAI,CAmB5C"}
@@ -0,0 +1,27 @@
1
+ import { join } from 'path';
2
+ import chokidar from 'chokidar';
3
+ import { getFrameworkDir } from "./getFrameworkDir.js";
4
+ import { cliWarn } from "./cliOutput.js";
5
+ // Dev mode only, opt-in via warnOnFrameworkChange in tungsten.config.js. Node's ESM module
6
+ // cache can't be invalidated at runtime, so an already-running dev server can't pick up a
7
+ // change to the tungsten package itself (e.g. a linked/workspace copy being rebuilt) without a
8
+ // full process restart - this just tells the developer that's needed, rather than attempting it.
9
+ export function warnOnFrameworkChange() {
10
+ const frameworkDistDir = join(getFrameworkDir(), 'dist');
11
+ // debounced so one rebuild (many files touched at once) produces one warning, not one per file
12
+ let debounceTimer = null;
13
+ const watcher = chokidar.watch(frameworkDistDir, {
14
+ ignoreInitial: true,
15
+ awaitWriteFinish: {
16
+ stabilityThreshold: 200,
17
+ pollInterval: 20
18
+ }
19
+ });
20
+ watcher.on('all', () => {
21
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
22
+ debounceTimer = setTimeout(() => {
23
+ debounceTimer = null;
24
+ cliWarn(`tungsten framework files changed (${frameworkDistDir}) - restart the dev server` + ` to pick up the change.`);
25
+ }, 300);
26
+ });
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tungsten",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +0,0 @@
1
- export type ReloadEventKind = 'js' | 'css';
2
- type ReloadListener = (kind: ReloadEventKind) => void;
3
- export declare function onReloadEvent(listener: ReloadListener): () => void;
4
- export declare function emitReloadEvent(kind: ReloadEventKind): void;
5
- export {};
6
- //# sourceMappingURL=liveReloadHub.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"liveReloadHub.d.ts","sourceRoot":"","sources":["../../src/dev/liveReloadHub.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,KAAK,CAAA;AAE1C,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,GAAG,IAAI,CAE3D"}