@financial-times/custom-code-component 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/custom-code-component.js +81 -59
- package/package.json +1 -1
- package/src/custom-code-component.ts +115 -51
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class l extends HTMLElement {
|
|
2
2
|
constructor() {
|
|
3
3
|
super(...arguments), this.RESERVED_ATTRS = /* @__PURE__ */ new Set([
|
|
4
4
|
"iframe",
|
|
@@ -7,86 +7,108 @@ class f extends HTMLElement {
|
|
|
7
7
|
"data-component-props",
|
|
8
8
|
"data-asset-type",
|
|
9
9
|
"shadow-open",
|
|
10
|
-
"env"
|
|
11
|
-
|
|
10
|
+
"env",
|
|
11
|
+
"load-timeout"
|
|
12
|
+
]), this.unmount = (t) => {
|
|
12
13
|
}, this.channel = new MessageChannel();
|
|
13
14
|
}
|
|
14
15
|
static get observedAttributes() {
|
|
15
16
|
return ["path", "version", "env", "data-component-props"];
|
|
16
17
|
}
|
|
17
18
|
async mount() {
|
|
18
|
-
var
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
var c;
|
|
20
|
+
if (!this.app)
|
|
21
|
+
throw new Error("CCC mounted without App");
|
|
22
|
+
const t = this.shadowRoot ?? this.attachShadow({ mode: this.mode }), o = this.app, u = JSON.parse(this.getAttribute("data-component-props")), m = Object.fromEntries(
|
|
23
|
+
[...this.attributes].filter((r) => !this.RESERVED_ATTRS.has(r.name)).map((r) => [r.name, r.value])
|
|
24
|
+
);
|
|
25
|
+
(c = this.shadowRoot) == null || c.replaceChildren();
|
|
26
|
+
const { unmount: s, onmessage: n } = o(
|
|
27
|
+
t,
|
|
28
|
+
{ ...m, data: u, port: this.channel.port2 },
|
|
29
|
+
...this.children
|
|
30
|
+
) || {};
|
|
31
|
+
s && (this.unmount = s), n && (this.onmessage = n);
|
|
32
|
+
}
|
|
33
|
+
async connectedCallback() {
|
|
34
|
+
var r;
|
|
35
|
+
const t = this.getAttribute("path"), o = this.getAttribute("version"), u = this.getAttribute("load-timeout") ?? 2e3, m = (r = this.getAttribute("env")) == null ? void 0 : r.toLowerCase().startsWith("d");
|
|
36
|
+
if (!t)
|
|
21
37
|
throw new Error(
|
|
22
38
|
"path attribute not specified in <custom-code-component>"
|
|
23
39
|
);
|
|
24
|
-
const [s,
|
|
25
|
-
if (!(!s || !
|
|
26
|
-
this.source =
|
|
40
|
+
const [s, n, c] = t.split("/").reverse();
|
|
41
|
+
if (!(!s || !n || !c)) {
|
|
42
|
+
this.source = m ? `http://localhost:5173/src/${s}/index.jsx` : `https://www.ft.com/__component/${c}/${n}${o ? `@${o}` : "@latest"}/${s}/${s}.js`;
|
|
43
|
+
try {
|
|
44
|
+
this.app = await new Promise((e, p) => {
|
|
45
|
+
const d = setTimeout(
|
|
46
|
+
() => p(new i("CCC import timeout")),
|
|
47
|
+
Number(u)
|
|
48
|
+
);
|
|
49
|
+
import(
|
|
50
|
+
/* webpackIgnore: true */
|
|
51
|
+
this.source
|
|
52
|
+
/* @vite-ignore */
|
|
53
|
+
).then(({ default: a }) => {
|
|
54
|
+
if (a)
|
|
55
|
+
clearTimeout(d), e(a);
|
|
56
|
+
else
|
|
57
|
+
throw new h(
|
|
58
|
+
"No component renderer default export found"
|
|
59
|
+
);
|
|
60
|
+
}).catch((a) => p(new h(a)));
|
|
61
|
+
});
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error(
|
|
64
|
+
`<custom-code-component> error during import from ${t}@${o}`
|
|
65
|
+
), delete this.dataset.cccReady, e instanceof i ? this.dataset.cccError = "import-timeout" : this.dataset.cccError = "import-failure", this.dispatchEvent(new ErrorEvent("error", e)), console.error(e);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
27
68
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
) || {};
|
|
43
|
-
o && (this.unmount = o), l && (this.onmessage = l);
|
|
44
|
-
}), (m = this.shadowRoot) == null || m.append(t);
|
|
45
|
-
} else {
|
|
46
|
-
const { unmount: t, onmessage: o } = await c.default(
|
|
47
|
-
this.shadowRoot,
|
|
48
|
-
{ ...d, data: p, port: this.channel.port2 },
|
|
49
|
-
...this.children
|
|
50
|
-
) || {};
|
|
51
|
-
t && (this.unmount = t), o && (this.onmessage = o);
|
|
52
|
-
}
|
|
53
|
-
} catch (c) {
|
|
54
|
-
console.info(`<custom-code-component> uncaught error from ${e}`), console.error(c);
|
|
69
|
+
this.mode = this.getAttribute("shadow-open") == "false" ? "closed" : "open", await this.mount(), this.dispatchEvent(
|
|
70
|
+
new CustomEvent("ccc-connected", {
|
|
71
|
+
bubbles: !0,
|
|
72
|
+
cancelable: !0,
|
|
73
|
+
detail: {
|
|
74
|
+
component: `${t}@${o}`,
|
|
75
|
+
source: this.source
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
), this.dataset.cccReady = "true", delete this.dataset.cccError;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.info(
|
|
81
|
+
`<custom-code-component> uncaught error during mount from ${t}@${o}`
|
|
82
|
+
), console.error(e), this.dispatchEvent(new ErrorEvent("error", e)), this.dataset.cccError = "mount-error", delete this.dataset.cccReady;
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
85
|
}
|
|
58
|
-
connectedCallback() {
|
|
59
|
-
const e = this.getAttribute("shadow-open") == "false" ? "closed" : "open";
|
|
60
|
-
this.attachShadow({ mode: e }), this.mount().then(() => {
|
|
61
|
-
const n = this.getAttribute("path"), i = this.getAttribute("version");
|
|
62
|
-
this.dispatchEvent(
|
|
63
|
-
new CustomEvent("ccc-connected", {
|
|
64
|
-
bubbles: !0,
|
|
65
|
-
cancelable: !0,
|
|
66
|
-
detail: {
|
|
67
|
-
component: `${n}@${i}`,
|
|
68
|
-
source: this.source
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
86
|
attributeChangedCallback() {
|
|
75
87
|
this.mount();
|
|
76
88
|
}
|
|
77
89
|
disconnectedCallback() {
|
|
78
|
-
const
|
|
79
|
-
console.info(`<custom-code-component:${
|
|
90
|
+
const t = this.getAttribute("path");
|
|
91
|
+
console.info(`<custom-code-component:${t}> disconnected`), typeof this.unmount == "function" && this.unmount();
|
|
80
92
|
}
|
|
81
93
|
onmessage() {
|
|
82
94
|
}
|
|
83
95
|
// I'm honestly not sure what to do with this
|
|
84
|
-
postMessage(
|
|
85
|
-
this.channel.port1.postMessage(
|
|
96
|
+
postMessage(t) {
|
|
97
|
+
this.channel.port1.postMessage(t);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const E = () => customElements.define("custom-code-component", l);
|
|
101
|
+
customElements && !customElements.get("custom-code-component") && E();
|
|
102
|
+
class i extends Error {
|
|
103
|
+
constructor(...t) {
|
|
104
|
+
super(...t), Error.captureStackTrace && Error.captureStackTrace(this, i), this.name = "CCCImportTimeoutError";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
class h extends Error {
|
|
108
|
+
constructor(...t) {
|
|
109
|
+
super(...t), Error.captureStackTrace && Error.captureStackTrace(this, h), this.name = "CCCImportError";
|
|
86
110
|
}
|
|
87
111
|
}
|
|
88
|
-
const g = () => customElements.define("custom-code-component", f);
|
|
89
|
-
customElements && !customElements.get("custom-code-component") && g();
|
|
90
112
|
export {
|
|
91
|
-
|
|
113
|
+
E as init
|
|
92
114
|
};
|
package/package.json
CHANGED
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ContentTree } from "@financial-times/content-tree";
|
|
7
|
-
|
|
7
|
+
import { BaseRenderer } from "../../ccc-sdk/src/renderers/AbstractBaseRenderer";
|
|
8
8
|
class FTCustomCodeComponent extends HTMLElement {
|
|
9
|
+
app: typeof BaseRenderer.prototype.render;
|
|
10
|
+
|
|
11
|
+
mode: "closed" | "open";
|
|
12
|
+
|
|
9
13
|
RESERVED_ATTRS = new Set([
|
|
10
14
|
"iframe",
|
|
11
15
|
"path",
|
|
@@ -14,17 +18,52 @@ class FTCustomCodeComponent extends HTMLElement {
|
|
|
14
18
|
"data-asset-type",
|
|
15
19
|
"shadow-open",
|
|
16
20
|
"env",
|
|
21
|
+
"load-timeout",
|
|
17
22
|
]);
|
|
18
23
|
|
|
19
24
|
static get observedAttributes() {
|
|
20
25
|
return ["path", "version", "env", "data-component-props"];
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
source: string
|
|
28
|
+
source: string;
|
|
24
29
|
|
|
25
30
|
async mount() {
|
|
31
|
+
if (!this.app) {
|
|
32
|
+
throw new Error("CCC mounted without App");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const shadow = this.shadowRoot ?? this.attachShadow({ mode: this.mode });
|
|
36
|
+
|
|
37
|
+
const App = this.app;
|
|
38
|
+
|
|
39
|
+
const data = JSON.parse(this.getAttribute("data-component-props")!);
|
|
40
|
+
|
|
41
|
+
const extraProps = Object.fromEntries(
|
|
42
|
+
[...this.attributes]
|
|
43
|
+
.filter((attribute) => !this.RESERVED_ATTRS.has(attribute.name))
|
|
44
|
+
.map((attribute) => [attribute.name, attribute.value])
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Clear old children
|
|
48
|
+
this.shadowRoot?.replaceChildren();
|
|
49
|
+
|
|
50
|
+
const { unmount, onmessage } =
|
|
51
|
+
App(
|
|
52
|
+
shadow,
|
|
53
|
+
{ ...extraProps, data, port: this.channel.port2 },
|
|
54
|
+
...this.children
|
|
55
|
+
) || {};
|
|
56
|
+
|
|
57
|
+
if (unmount) this.unmount = unmount;
|
|
58
|
+
if (onmessage) this.onmessage = onmessage;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
unmount = (root?: any) => {};
|
|
62
|
+
|
|
63
|
+
async connectedCallback() {
|
|
26
64
|
const path = this.getAttribute("path");
|
|
27
65
|
const componentVersionRange = this.getAttribute("version");
|
|
66
|
+
const timeout = this.getAttribute("load-timeout") ?? 2000;
|
|
28
67
|
const useLocalVersion = this.getAttribute("env")
|
|
29
68
|
?.toLowerCase()
|
|
30
69
|
.startsWith("d");
|
|
@@ -47,64 +86,48 @@ class FTCustomCodeComponent extends HTMLElement {
|
|
|
47
86
|
}/${componentName}/${componentName}.js`;
|
|
48
87
|
|
|
49
88
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
this.app = await new Promise((resolve, reject) => {
|
|
90
|
+
const to = setTimeout(
|
|
91
|
+
() => reject(new CCCTimeoutError("CCC import timeout")),
|
|
92
|
+
Number(timeout)
|
|
93
|
+
);
|
|
94
|
+
import(/* webpackIgnore: true */ this.source /* @vite-ignore */)
|
|
95
|
+
.then(({ default: componentRenderFunction }) => {
|
|
96
|
+
if (componentRenderFunction) {
|
|
97
|
+
clearTimeout(to);
|
|
98
|
+
resolve(componentRenderFunction);
|
|
99
|
+
} else
|
|
100
|
+
throw new CCCImportError(
|
|
101
|
+
"No component renderer default export found"
|
|
102
|
+
);
|
|
103
|
+
})
|
|
104
|
+
.catch((e) => reject(new CCCImportError(e)));
|
|
105
|
+
});
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.error(
|
|
108
|
+
`<custom-code-component> error during import from ${path}@${componentVersionRange}`
|
|
60
109
|
);
|
|
61
110
|
|
|
62
|
-
|
|
63
|
-
this.shadowRoot?.replaceChildren();
|
|
111
|
+
delete this.dataset.cccReady;
|
|
64
112
|
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
mountPoint.addEventListener("load", () => {
|
|
69
|
-
const { unmount, onmessage } =
|
|
70
|
-
App.default(
|
|
71
|
-
mountPoint.contentDocument,
|
|
72
|
-
{ ...extraProps, data, port: this.channel.port2 },
|
|
73
|
-
...this.children
|
|
74
|
-
) || {};
|
|
75
|
-
|
|
76
|
-
if (unmount) this.unmount = unmount;
|
|
77
|
-
if (onmessage) this.onmessage = onmessage;
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
this.shadowRoot?.append(mountPoint);
|
|
113
|
+
if (e instanceof CCCTimeoutError) {
|
|
114
|
+
this.dataset.cccError = "import-timeout";
|
|
81
115
|
} else {
|
|
82
|
-
|
|
83
|
-
(await App.default(
|
|
84
|
-
this.shadowRoot,
|
|
85
|
-
{ ...extraProps, data, port: this.channel.port2 },
|
|
86
|
-
...this.children
|
|
87
|
-
)) || {};
|
|
88
|
-
|
|
89
|
-
if (unmount) this.unmount = unmount;
|
|
90
|
-
if (onmessage) this.onmessage = onmessage;
|
|
116
|
+
this.dataset.cccError = "import-failure";
|
|
91
117
|
}
|
|
92
|
-
|
|
93
|
-
|
|
118
|
+
|
|
119
|
+
this.dispatchEvent(new ErrorEvent("error", e));
|
|
94
120
|
console.error(e);
|
|
121
|
+
|
|
122
|
+
return; // prevent mounting on caught error
|
|
95
123
|
}
|
|
96
|
-
}
|
|
97
124
|
|
|
98
|
-
|
|
125
|
+
try {
|
|
126
|
+
this.mode =
|
|
127
|
+
this.getAttribute("shadow-open") == "false" ? "closed" : "open";
|
|
99
128
|
|
|
100
|
-
|
|
101
|
-
const mode =
|
|
102
|
-
this.getAttribute("shadow-open") == "false" ? "closed" : "open";
|
|
129
|
+
await this.mount();
|
|
103
130
|
|
|
104
|
-
this.attachShadow({ mode });
|
|
105
|
-
this.mount().then(() => {
|
|
106
|
-
const path = this.getAttribute("path");
|
|
107
|
-
const componentVersionRange = this.getAttribute("version");
|
|
108
131
|
this.dispatchEvent(
|
|
109
132
|
new CustomEvent("ccc-connected", {
|
|
110
133
|
bubbles: true,
|
|
@@ -115,7 +138,20 @@ class FTCustomCodeComponent extends HTMLElement {
|
|
|
115
138
|
},
|
|
116
139
|
})
|
|
117
140
|
);
|
|
118
|
-
|
|
141
|
+
|
|
142
|
+
this.dataset.cccReady = "true";
|
|
143
|
+
delete this.dataset.cccError;
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.info(
|
|
146
|
+
`<custom-code-component> uncaught error during mount from ${path}@${componentVersionRange}`
|
|
147
|
+
);
|
|
148
|
+
console.error(e);
|
|
149
|
+
|
|
150
|
+
this.dispatchEvent(new ErrorEvent("error", e));
|
|
151
|
+
this.dataset.cccError = "mount-error";
|
|
152
|
+
|
|
153
|
+
delete this.dataset.cccReady;
|
|
154
|
+
}
|
|
119
155
|
}
|
|
120
156
|
|
|
121
157
|
attributeChangedCallback() {
|
|
@@ -158,3 +194,31 @@ export interface CustomCodeComponent extends ContentTree.Node {
|
|
|
158
194
|
[key: string]: string | boolean | undefined;
|
|
159
195
|
} | { children?: CustomCodeComponent | Array<CustomCodeComponent> };
|
|
160
196
|
}
|
|
197
|
+
|
|
198
|
+
class CCCTimeoutError extends Error {
|
|
199
|
+
constructor(...params) {
|
|
200
|
+
// Pass remaining arguments (including vendor specific ones) to parent constructor
|
|
201
|
+
super(...params);
|
|
202
|
+
|
|
203
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
204
|
+
if (Error.captureStackTrace) {
|
|
205
|
+
Error.captureStackTrace(this, CCCTimeoutError);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.name = "CCCImportTimeoutError";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class CCCImportError extends Error {
|
|
213
|
+
constructor(...params) {
|
|
214
|
+
// Pass remaining arguments (including vendor specific ones) to parent constructor
|
|
215
|
+
super(...params);
|
|
216
|
+
|
|
217
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
218
|
+
if (Error.captureStackTrace) {
|
|
219
|
+
Error.captureStackTrace(this, CCCImportError);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.name = "CCCImportError";
|
|
223
|
+
}
|
|
224
|
+
}
|