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.
- package/dist/Components.d.ts +2 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.js +10 -1
- package/dist/DomRegion.js +1 -1
- package/dist/FrameworkConfig.d.ts +4 -0
- package/dist/FrameworkConfig.d.ts.map +1 -1
- package/dist/FunctionComponentStandIn.d.ts +2 -0
- package/dist/FunctionComponentStandIn.d.ts.map +1 -1
- package/dist/FunctionComponentStandIn.js +10 -1
- package/dist/FunctionComponentStandIn.sxyt.d.ts +2 -0
- package/dist/FunctionComponentStandIn.sxyt.d.ts.map +1 -0
- package/dist/FunctionComponentStandIn.sxyt.js +52 -0
- package/dist/Servers/DevServer.d.ts +2 -0
- package/dist/Servers/DevServer.d.ts.map +1 -1
- package/dist/Servers/DevServer.js +32 -5
- package/dist/Servers/devErrorPageAutoRetry.d.ts +4 -0
- package/dist/Servers/devErrorPageAutoRetry.d.ts.map +1 -0
- package/dist/Servers/devErrorPageAutoRetry.js +26 -0
- package/dist/Servers/devErrorPageAutoRetry.sxyt.d.ts +2 -0
- package/dist/Servers/devErrorPageAutoRetry.sxyt.d.ts.map +1 -0
- package/dist/Servers/devErrorPageAutoRetry.sxyt.js +117 -0
- package/dist/api/getExport.d.ts.map +1 -1
- package/dist/api/getExport.js +2 -1
- package/dist/babelPlugins/BabelAddDevCacheBust.d.ts +11 -0
- package/dist/babelPlugins/BabelAddDevCacheBust.d.ts.map +1 -0
- package/dist/babelPlugins/BabelAddDevCacheBust.js +68 -0
- package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.d.ts +2 -0
- package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.d.ts.map +1 -0
- package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.js +138 -0
- package/dist/bin/commands/dev.d.ts.map +1 -1
- package/dist/bin/commands/dev.js +2 -0
- package/dist/build/compileUserApp.js +2 -2
- package/dist/build/makeBabelUserDevConfig.d.ts.map +1 -1
- package/dist/build/makeBabelUserDevConfig.js +6 -1
- package/dist/defaultConfig.d.ts +2 -0
- package/dist/defaultConfig.d.ts.map +1 -1
- package/dist/defaultConfig.js +3 -1
- package/dist/env/getEnvSchemas.d.ts.map +1 -1
- package/dist/env/getEnvSchemas.js +2 -1
- package/dist/getComponent.d.ts.map +1 -1
- package/dist/getComponent.js +2 -1
- package/dist/getRoutes.d.ts.map +1 -1
- package/dist/getRoutes.js +2 -1
- package/dist/hmr/applyHmrModule.d.ts +11 -0
- package/dist/hmr/applyHmrModule.d.ts.map +1 -0
- package/dist/hmr/applyHmrModule.js +78 -0
- package/dist/hmr/applyHmrModule.sxyt.d.ts +2 -0
- package/dist/hmr/applyHmrModule.sxyt.d.ts.map +1 -0
- package/dist/hmr/applyHmrModule.sxyt.js +182 -0
- package/dist/hmr/componentIdentity.d.ts +9 -0
- package/dist/hmr/componentIdentity.d.ts.map +1 -0
- package/dist/hmr/componentIdentity.js +17 -0
- package/dist/hmr/instanceRegistry.d.ts +4 -0
- package/dist/hmr/instanceRegistry.d.ts.map +1 -0
- package/dist/hmr/instanceRegistry.js +6 -0
- package/dist/hmr/instanceRegistry.sxyt.d.ts +2 -0
- package/dist/hmr/instanceRegistry.sxyt.d.ts.map +1 -0
- package/dist/hmr/instanceRegistry.sxyt.js +24 -0
- package/dist/hmr/liveReloadHub.d.ts +10 -0
- package/dist/hmr/liveReloadHub.d.ts.map +1 -0
- package/dist/{dev → hmr}/liveReloadHub.js +8 -2
- package/dist/hmr/refreshStylesheets.d.ts +2 -0
- package/dist/hmr/refreshStylesheets.d.ts.map +1 -0
- package/dist/hmr/refreshStylesheets.js +26 -0
- package/dist/hmr/refreshStylesheets.sxyt.d.ts +2 -0
- package/dist/hmr/refreshStylesheets.sxyt.d.ts.map +1 -0
- package/dist/hmr/refreshStylesheets.sxyt.js +46 -0
- package/dist/hydrate/hydrate.d.ts +9 -1
- package/dist/hydrate/hydrate.d.ts.map +1 -1
- package/dist/hydrate/hydrate.js +31 -7
- package/dist/hydrate/hydrate.sxyt.d.ts +2 -0
- package/dist/hydrate/hydrate.sxyt.d.ts.map +1 -0
- package/dist/hydrate/hydrate.sxyt.js +302 -0
- package/dist/hydrate/reconcileHydratedAttributes.d.ts.map +1 -1
- package/dist/hydrate/reconcileHydratedAttributes.js +10 -2
- package/dist/hydrate/reconcileHydratedAttributes.sxyt.js +17 -0
- package/dist/importDevModuleWithRetry.d.ts +3 -0
- package/dist/importDevModuleWithRetry.d.ts.map +1 -0
- package/dist/importDevModuleWithRetry.js +46 -0
- package/dist/importDevModuleWithRetry.sxyt.d.ts +2 -0
- package/dist/importDevModuleWithRetry.sxyt.d.ts.map +1 -0
- package/dist/importDevModuleWithRetry.sxyt.js +104 -0
- package/dist/render/HeadData.d.ts +1 -0
- package/dist/render/HeadData.d.ts.map +1 -1
- package/dist/render/contentToHtml.d.ts.map +1 -1
- package/dist/render/contentToHtml.js +2 -1
- package/dist/render/processHead.d.ts.map +1 -1
- package/dist/render/processHead.js +47 -6
- package/dist/render/processHead.sxyt.d.ts +2 -0
- package/dist/render/processHead.sxyt.d.ts.map +1 -0
- package/dist/render/processHead.sxyt.js +93 -0
- package/dist/update/hotSwap.sxyt.d.ts +2 -0
- package/dist/update/hotSwap.sxyt.d.ts.map +1 -0
- package/dist/update/hotSwap.sxyt.js +252 -0
- package/dist/update/integrateUpdateVdom.d.ts +5 -1
- package/dist/update/integrateUpdateVdom.d.ts.map +1 -1
- package/dist/update/integrateUpdateVdom.js +107 -4
- package/dist/warnOnFrameworkChange.d.ts +2 -0
- package/dist/warnOnFrameworkChange.d.ts.map +1 -0
- package/dist/warnOnFrameworkChange.js +27 -0
- package/package.json +1 -1
- package/dist/dev/liveReloadHub.d.ts +0 -6
- 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 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"integrateUpdateVdom.d.ts","sourceRoot":"","sources":["../../src/update/integrateUpdateVdom.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 @@
|
|
|
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 +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"}
|