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,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 @@
|
|
|
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 @@
|
|
|
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
|
-
|
|
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 @@
|
|
|
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 @@
|
|
|
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 {
|
|
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;
|
|
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"}
|
package/dist/hydrate/hydrate.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"hydrate.sxyt.d.ts","sourceRoot":"","sources":["../../src/hydrate/hydrate.sxyt.tsx"],"names":[],"mappings":""}
|