Skip to content

Commit 34186b9

Browse files
committed
feat(): support app preview
1 parent 85a05ea commit 34186b9

File tree

3 files changed

+121
-44
lines changed

3 files changed

+121
-44
lines changed

etc/runtime.api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ declare namespace __secret_internals {
7474
RenderUseBrickResult,
7575
MountUseBrickResult,
7676
updateSnippetPreviewSettings,
77+
getBrickPackages,
7778
setRealTimeDataInspectRoot,
7879
addRealTimeDataInspectHook,
7980
legacyDoTransform,
@@ -195,6 +196,9 @@ export function getAuth(): object | undefined;
195196
// @public
196197
export function getBasePath(): string;
197198

199+
// @public (undocumented)
200+
function getBrickPackages(): BrickPackage[];
201+
198202
// @public (undocumented)
199203
function getBrickPackagesById(id: string): BrickPackage | undefined;
200204

packages/brick-container/src/bootstrap.ts

Lines changed: 115 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
resetReloadForError,
88
__secret_internals,
99
isNetworkError,
10+
getHistory,
1011
} from "@next-core/runtime";
1112
import { HttpRequestConfig, http } from "@next-core/http";
1213
import { i18n, initializeI18n } from "@next-core/i18n";
@@ -20,6 +21,7 @@ import {
2021
analytics,
2122
} from "@next-core/easyops-runtime";
2223
import "@next-core/theme";
24+
import type { BootstrapData, Storyboard } from "@next-core/types";
2325
import "./XMLHttpRequest.js";
2426
import { loadCheckLogin } from "./loadCheckLogin.js";
2527
import { fulfilStoryboard, loadBootstrapData } from "./loadBootstrapData.js";
@@ -30,6 +32,10 @@ import { getMock } from "./mocks.js";
3032
import { NS, K, locales } from "./i18n.js";
3133
import { DefaultError } from "./DefaultError.js";
3234

35+
const isAppPreview = !!new URLSearchParams(window.location.search).get(
36+
"_experimental_app_preview_"
37+
);
38+
3339
customElements.define("easyops-default-error", DefaultError);
3440

3541
analytics.initialize(
@@ -115,61 +121,126 @@ const requestEnd = (): void => {
115121
window.addEventListener("request.start", requestStart);
116122
window.addEventListener("request.end", requestEnd);
117123

118-
const runtime = createRuntime({
119-
hooks: {
120-
auth,
121-
fulfilStoryboard,
122-
checkPermissions,
123-
flowApi,
124-
checkInstalledApps,
125-
menu,
126-
images: { imagesFactory, widgetImagesFactory },
127-
messageDispatcher,
128-
pageView: analytics.pageView,
129-
},
130-
});
124+
function doCreateRuntime() {
125+
return createRuntime({
126+
hooks: {
127+
auth,
128+
checkPermissions,
129+
flowApi,
130+
checkInstalledApps,
131+
menu,
132+
images: { imagesFactory, widgetImagesFactory },
133+
messageDispatcher,
134+
...(isAppPreview
135+
? null
136+
: {
137+
fulfilStoryboard,
138+
pageView: analytics.pageView,
139+
}),
140+
},
141+
});
142+
}
131143

132144
initializeI18n(NS, locales);
133145

146+
interface PreviewWindow extends Window {
147+
_preview_only_appPreviewer?: AppPreviewer;
148+
}
149+
150+
class AppPreviewer {
151+
#setupCalled = false;
152+
153+
async setup(data: BootstrapData, url: string) {
154+
try {
155+
if (this.#setupCalled) {
156+
throw new Error(
157+
"_preview_only_setupAppPreview can only be called once"
158+
);
159+
}
160+
this.#setupCalled = true;
161+
history.replaceState(null, "", url);
162+
const runtime = doCreateRuntime();
163+
await loadCheckLogin();
164+
await runtime.bootstrap(data);
165+
} catch (error) {
166+
handleError(error);
167+
}
168+
requestEnd();
169+
}
170+
171+
async update(appId: string, storyboard: Partial<Storyboard>) {
172+
__secret_internals.updateStoryboard(appId, storyboard);
173+
}
174+
175+
reload() {
176+
getHistory().reload();
177+
}
178+
179+
push(url: string) {
180+
getHistory().push(url);
181+
}
182+
183+
replace(url: string) {
184+
getHistory().replace(url);
185+
}
186+
187+
goBack() {
188+
getHistory().goBack();
189+
}
190+
191+
goForward() {
192+
getHistory().goForward();
193+
}
194+
}
195+
134196
async function main() {
135197
try {
136-
const [, bootstrapData] = await Promise.all([
137-
loadCheckLogin(),
138-
loadBootstrapData(),
139-
]);
140-
await runtime.bootstrap(bootstrapData);
141-
resetReloadForError();
198+
if (isAppPreview) {
199+
requestStart();
200+
(window as PreviewWindow)._preview_only_appPreviewer = new AppPreviewer();
201+
return "ok";
202+
} else {
203+
const runtime = doCreateRuntime();
204+
const [, bootstrapData] = await Promise.all([
205+
loadCheckLogin(),
206+
loadBootstrapData(),
207+
]);
208+
await runtime.bootstrap(bootstrapData);
209+
resetReloadForError();
210+
}
142211
return "ok";
143212
} catch (error) {
144-
// eslint-disable-next-line no-console
145-
console.error("bootstrap failed:", error);
146-
147-
if (shouldReloadForError(error)) {
148-
location.reload();
149-
return "failed";
150-
}
213+
handleError(error);
214+
return "failed";
215+
}
216+
}
151217

152-
// `.bootstrap-error` makes loading-bar invisible.
153-
document.body.classList.add("bootstrap-error");
154-
155-
const errorElement = document.createElement(
156-
"easyops-default-error"
157-
) as DefaultError;
158-
errorElement.errorTitle = isNetworkError(error)
159-
? i18n.t(`${NS}:${K.NETWORK_ERROR}`)
160-
: i18n.t(`${NS}:${K.BOOTSTRAP_ERROR}`);
161-
errorElement.textContent = httpErrorToString(error);
162-
const linkElement = document.createElement("a");
163-
linkElement.slot = "link";
164-
linkElement.href = location.href;
165-
linkElement.textContent = i18n.t(`${NS}:${K.RELOAD}`);
166-
errorElement.appendChild(linkElement);
167-
168-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
169-
document.querySelector("#main-mount-point")!.replaceChildren(errorElement);
218+
function handleError(error: unknown) {
219+
// eslint-disable-next-line no-console
220+
console.error("bootstrap failed:", error);
170221

222+
if (shouldReloadForError(error)) {
223+
location.reload();
171224
return "failed";
172225
}
226+
227+
// `.bootstrap-error` makes loading-bar invisible.
228+
document.body.classList.add("bootstrap-error");
229+
230+
const errorElement = document.createElement(
231+
"easyops-default-error"
232+
) as DefaultError;
233+
errorElement.errorTitle = isNetworkError(error)
234+
? i18n.t(`${NS}:${K.NETWORK_ERROR}`)
235+
: i18n.t(`${NS}:${K.BOOTSTRAP_ERROR}`);
236+
errorElement.textContent = httpErrorToString(error);
237+
const linkElement = document.createElement("a");
238+
linkElement.slot = "link";
239+
linkElement.href = location.href;
240+
linkElement.textContent = i18n.t(`${NS}:${K.RELOAD}`);
241+
errorElement.appendChild(linkElement);
242+
243+
document.querySelector("#main-mount-point")!.replaceChildren(errorElement);
173244
}
174245

175246
const bootstrapStatus = main();

packages/runtime/src/internal/secret_internals.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ export function getAllContextValues({
365365
return runtimeContext.ctxStore.getAllValues();
366366
}
367367

368+
export { getBrickPackages };
369+
368370
export function getBrickPackagesById(id: string) {
369371
return getBrickPackages().find((pkg) =>
370372
pkg.id ? pkg.id === id : pkg.filePath.startsWith(`${id}/`)

0 commit comments

Comments
 (0)