Skip to content

Commit c91e0c8

Browse files
authored
fix(runtime): don't render when crashing (#2746)
1 parent 24db71b commit c91e0c8

File tree

8 files changed

+79
-21
lines changed

8 files changed

+79
-21
lines changed

src/client/client-log.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type * as d from '../declarations';
22
import { BUILD } from '@app-data';
33

4-
let customError: d.ErrorHandler ;
4+
let customError: d.ErrorHandler;
5+
56
export const consoleError: d.ErrorHandler = (e: any, el?: any) => (customError || console.error)(e, el);
67

78
export const STENCIL_DEV_MODE = BUILD.isTesting

src/hydrate/platform/index.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type * as d from '../../declarations';
22
import { addHostEventListeners } from '@runtime';
33

4+
let customError: d.ErrorHandler;
5+
46
export const cmpModules = new Map<string, { [exportName: string]: d.ComponentConstructor }>();
57

68
const getModule = (tagName: string): d.ComponentConstructor => {
@@ -71,12 +73,14 @@ export const writeTask = (cb: Function) => {
7173
const resolved = /*@__PURE__*/ Promise.resolve();
7274
export const nextTick = /*@__PURE__*/ (cb: () => void) => resolved.then(cb);
7375

74-
export const consoleError = (e: any) => {
76+
const defaultConsoleError = (e: any) => {
7577
if (e != null) {
7678
console.error(e.stack || e.message || e);
7779
}
7880
};
7981

82+
export const consoleError: d.ErrorHandler = (e: any, el?: any) => (customError || defaultConsoleError)(e, el);
83+
8084
export const consoleDevError = (..._: any[]) => {
8185
/* noop for hydrate */
8286
};
@@ -89,6 +93,8 @@ export const consoleDevInfo = (..._: any[]) => {
8993
/* noop for hydrate */
9094
};
9195

96+
export const setErrorHandler = (handler: d.ErrorHandler) => customError = handler;
97+
9298
/*hydrate context start*/ export const Context = {}; /*hydrate context end*/
9399

94100
export const plt: d.PlatformRuntime = {

src/runtime/test/render-vdom.spec.tsx

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, Element, Host, Prop, State, forceUpdate, getRenderingRef, h } from '@stencil/core';
1+
import { Component, Element, setErrorHandler, Host, Prop, State, forceUpdate, getRenderingRef, h } from '@stencil/core';
22
import { newSpecPage } from '@stencil/core/testing';
33

44
describe('render-vdom', () => {
@@ -465,6 +465,42 @@ describe('render-vdom', () => {
465465
`);
466466
});
467467

468+
it('render crash should not remove the content', async () => {
469+
let didError = false;
470+
setErrorHandler((err) => {
471+
didError = true;
472+
});
473+
@Component({ tag: 'cmp-a' })
474+
class CmpA {
475+
@Prop() crash = false;
476+
render() {
477+
if (this.crash) {
478+
throw new Error('YOLO');
479+
}
480+
return <div>Hello</div>;
481+
}
482+
}
483+
484+
const { root, waitForChanges } = await newSpecPage({
485+
components: [CmpA],
486+
487+
html: `<cmp-a></cmp-a>`,
488+
});
489+
490+
expect(root).toEqualHtml(`
491+
<cmp-a><div>Hello</div></cmp-a>
492+
`);
493+
494+
expect(didError).toBe(false);
495+
root.crash = true;
496+
await waitForChanges();
497+
498+
expect(root).toEqualHtml(`
499+
<cmp-a><div>Hello</div></cmp-a>
500+
`);
501+
expect(didError).toBe(true);
502+
});
503+
468504
it('Hello VDOM, html option', async () => {
469505
@Component({ tag: 'cmp-a' })
470506
class CmpA {

src/runtime/update-component.ts

+20-15
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,10 @@ const updateComponent = async (hostRef: d.HostRef, instance: any, isInitialLoad:
8181
hostRef.$flags$ |= HOST_FLAGS.devOnRender;
8282
}
8383

84-
if (BUILD.hasRenderFn || BUILD.reflect) {
85-
if (BUILD.vdomRender || BUILD.reflect) {
86-
// looks like we've got child nodes to render into this host element
87-
// or we need to update the css class/attrs on the host element
88-
// DOM WRITE!
89-
if (BUILD.hydrateServerSide) {
90-
renderVdom(hostRef, await callRender(hostRef, instance));
91-
} else {
92-
renderVdom(hostRef, callRender(hostRef, instance));
93-
}
94-
} else {
95-
elm.textContent = callRender(hostRef, instance);
96-
}
84+
if (BUILD.hydrateServerSide) {
85+
await callRender(hostRef, instance, elm);
86+
} else {
87+
callRender(hostRef, instance, elm);
9788
}
9889
if (BUILD.cssVarShim && plt.$cssShim$) {
9990
plt.$cssShim$.updateHost(elm);
@@ -149,7 +140,7 @@ const updateComponent = async (hostRef: d.HostRef, instance: any, isInitialLoad:
149140

150141
let renderingRef: any = null;
151142

152-
const callRender = (hostRef: d.HostRef, instance: any) => {
143+
const callRender = (hostRef: d.HostRef, instance: any, elm: HTMLElement) => {
153144
// in order for bundlers to correctly treeshake the BUILD object
154145
// we need to ensure BUILD is not deoptimized within a try/catch
155146
// https://rollupjs.org/guide/en/#treeshake tryCatchDeoptimization
@@ -169,11 +160,25 @@ const callRender = (hostRef: d.HostRef, instance: any) => {
169160
if (updatable || lazyLoad) {
170161
hostRef.$flags$ |= HOST_FLAGS.hasRendered;
171162
}
163+
if (BUILD.hasRenderFn || BUILD.reflect) {
164+
if (BUILD.vdomRender || BUILD.reflect) {
165+
// looks like we've got child nodes to render into this host element
166+
// or we need to update the css class/attrs on the host element
167+
// DOM WRITE!
168+
if (BUILD.hydrateServerSide) {
169+
return Promise.resolve(instance).then(value => renderVdom(hostRef, value))
170+
} else {
171+
renderVdom(hostRef, instance);
172+
}
173+
} else {
174+
elm.textContent = instance;
175+
}
176+
}
172177
} catch (e) {
173178
consoleError(e, hostRef.$hostElement$);
174179
}
175180
renderingRef = null;
176-
return instance;
181+
return null;
177182
};
178183

179184
export const getRenderingRef = () => renderingRef;

src/testing/jest/jest-setup-test-framework.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { setupGlobal, teardownGlobal } from '@stencil/core/mock-doc';
55
import { setupMockFetch } from '../mock-fetch';
66
import { HtmlSerializer } from './jest-serializer';
77
import { resetBuildConditionals } from '../reset-build-conditionals';
8-
import { resetPlatform, stopAutoApplyChanges, modeResolutionChain } from '@stencil/core/internal/testing';
8+
import { resetPlatform, stopAutoApplyChanges, modeResolutionChain, setErrorHandler } from '@stencil/core/internal/testing';
99

1010
declare const global: d.JestEnvironmentGlobal;
1111

@@ -22,6 +22,7 @@ export function jestSetupTestFramework() {
2222
beforeEach(() => {
2323
// reset the platform for this new test
2424
resetPlatform();
25+
setErrorHandler(undefined);
2526
resetBuildConditionals(BUILD);
2627
modeResolutionChain.length = 0;
2728
});

src/testing/platform/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export { Build } from './testing-build';
22
export { Env } from '@app-data';
3-
export { consoleDevError, consoleDevInfo, consoleDevWarn, consoleError } from './testing-log';
3+
export { consoleDevError, consoleDevInfo, consoleDevWarn, consoleError, setErrorHandler } from './testing-log';
44
export {
55
Context,
66
isMemberInElement,

src/testing/platform/testing-log.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
import type * as d from '../../declarations';
12
import { caughtErrors } from './testing-constants';
23

3-
export const consoleError = (e: any) => {
4+
let customError: d.ErrorHandler;
5+
6+
const defaultConsoleError = (e: any) => {
7+
console.log('here', customError);
48
caughtErrors.push(e);
59
};
610

11+
export const consoleError: d.ErrorHandler = (e: any, el?: any) => (customError || defaultConsoleError)(e, el);
12+
713
export const consoleDevError = (...e: any[]) => {
814
caughtErrors.push(new Error(e.join(', ')));
915
};
@@ -15,3 +21,5 @@ export const consoleDevWarn = (..._: any[]) => {
1521
export const consoleDevInfo = (..._: any[]) => {
1622
/* noop for testing */
1723
};
24+
25+
export const setErrorHandler = (handler: d.ErrorHandler) => customError = handler;

src/testing/platform/testing-platform.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type * as d from '@stencil/core/internal';
22
import { cstrs, hostRefs, moduleLoaded, styles } from './testing-constants';
3+
import { setErrorHandler } from './testing-log';
34
import { flushAll, resetTaskQueue } from './testing-task-queue';
45
import { win } from './testing-window';
56

0 commit comments

Comments
 (0)