Skip to content

Commit e6c0593

Browse files
feat(runtime): export a render method from the runtime (#6245)
* feat(runtime): export a render method * fix unit tests * add missing export
1 parent 6e58761 commit e6c0593

File tree

11 files changed

+129
-12
lines changed

11 files changed

+129
-12
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
},
6565
"./compiler/*": {
6666
"types": "./compiler/*",
67-
"import": "./compiler/*"
67+
"import": "./compiler/*",
68+
"require": "./compiler/*"
6869
},
6970
"./screenshot": {
7071
"types": "./screenshot/index.d.ts",

src/compiler/output-targets/dist-custom-elements/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export const generateEntryPoint = (
255255

256256
// Exports that are always present
257257
exports.push(
258-
`export { getAssetPath, setAssetPath, setNonce, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';`,
258+
`export { getAssetPath, setAssetPath, setNonce, setPlatformOptions, render } from '${STENCIL_INTERNAL_CLIENT_ID}';`,
259259
`export * from '${USER_INDEX_ENTRY_ID}';`,
260260
);
261261

src/compiler/output-targets/test/output-targets-dist-custom-elements.spec.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('Custom Elements output target', () => {
7272
});
7373

7474
expect(entryPoint).toEqual(`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';
75-
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';
75+
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions, render } from '${STENCIL_INTERNAL_CLIENT_ID}';
7676
export * from '${USER_INDEX_ENTRY_ID}';
7777
7878
globalScripts();
@@ -86,7 +86,7 @@ globalScripts();
8686
});
8787

8888
expect(entryPoint)
89-
.toEqual(`export { getAssetPath, setAssetPath, setNonce, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';
89+
.toEqual(`export { getAssetPath, setAssetPath, setNonce, setPlatformOptions, render } from '${STENCIL_INTERNAL_CLIENT_ID}';
9090
export * from '${USER_INDEX_ENTRY_ID}';
9191
`);
9292
});
@@ -164,7 +164,7 @@ export * from '${USER_INDEX_ENTRY_ID}';
164164
addCustomElementInputs(buildCtx, bundleOptions, config.outputTargets[0] as OutputTargetDistCustomElements);
165165
expect(bundleOptions.loader['\0core']).toEqual(
166166
`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';
167-
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';
167+
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions, render } from '${STENCIL_INTERNAL_CLIENT_ID}';
168168
export * from '${USER_INDEX_ENTRY_ID}';
169169
170170
globalScripts();
@@ -197,7 +197,7 @@ globalScripts();
197197
addCustomElementInputs(buildCtx, bundleOptions, config.outputTargets[0] as OutputTargetDistCustomElements);
198198
expect(bundleOptions.loader['\0core']).toEqual(
199199
`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';
200-
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';
200+
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions, render } from '${STENCIL_INTERNAL_CLIENT_ID}';
201201
export * from '${USER_INDEX_ENTRY_ID}';
202202
export { StubCmp, defineCustomElement as defineCustomElementStubCmp } from '\0StubCmp';
203203
export { MyBestComponent, defineCustomElement as defineCustomElementMyBestComponent } from '\0MyBestComponent';
@@ -224,7 +224,7 @@ globalScripts();
224224
addCustomElementInputs(buildCtx, bundleOptions, config.outputTargets[0] as OutputTargetDistCustomElements);
225225
expect(bundleOptions.loader['\0core']).toEqual(
226226
`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';
227-
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';
227+
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions, render } from '${STENCIL_INTERNAL_CLIENT_ID}';
228228
export * from '${USER_INDEX_ENTRY_ID}';
229229
export { ComponentWithJsx, defineCustomElement as defineCustomElementComponentWithJsx } from '\0ComponentWithJsx';
230230
@@ -259,7 +259,7 @@ globalScripts();
259259
`import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';
260260
import { StubCmp } from '\0StubCmp';
261261
import { MyBestComponent } from '\0MyBestComponent';
262-
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';
262+
export { getAssetPath, setAssetPath, setNonce, setPlatformOptions, render } from '${STENCIL_INTERNAL_CLIENT_ID}';
263263
export * from '${USER_INDEX_ENTRY_ID}';
264264
265265
globalScripts();

src/declarations/stencil-public-runtime.ts

+20
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,26 @@ export declare function setPlatformHelpers(helpers: {
302302
*/
303303
export declare function getAssetPath(path: string): string;
304304

305+
/**
306+
* Method to render a virtual DOM tree to a container element.
307+
*
308+
* @example
309+
* ```tsx
310+
* import { render } from '@stencil/core';
311+
*
312+
* const vnode = (
313+
* <div>
314+
* <h1>Hello, world!</h1>
315+
* </div>
316+
* );
317+
* render(vnode, document.body);
318+
* ```
319+
*
320+
* @param vnode - The virtual DOM tree to render
321+
* @param container - The container element to render the virtual DOM tree to
322+
*/
323+
export declare function render(vnode: VNode, container: Element): void;
324+
305325
/**
306326
* Used to manually set the base path where assets can be found. For lazy-loaded
307327
* builds the asset path is automatically set and assets copied to the correct

src/internal/stencil-core/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export {
4141
Method,
4242
Prop,
4343
readTask,
44+
render,
4445
setAssetPath,
4546
setErrorHandler,
4647
setMode,

src/internal/stencil-core/index.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
export {
22
Build,
3+
forceUpdate,
34
getAssetPath,
45
getElement,
56
getMode,
67
getRenderingRef,
7-
forceUpdate,
88
h,
99
Host,
1010
readTask,
11-
setMode,
11+
render,
1212
setAssetPath,
13+
setErrorHandler,
14+
setMode,
1315
setPlatformHelpers,
1416
writeTask,
15-
setErrorHandler,
1617
} from '../client/index.js';

src/runtime/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export { setNonce } from './nonce';
1212
export { parsePropertyValue } from './parse-property-value';
1313
export { setPlatformOptions } from './platform-options';
1414
export { proxyComponent } from './proxy-component';
15+
export { render } from './render';
1516
export { getValue, setValue } from './set-value';
1617
export { forceUpdate, getRenderingRef, postUpdateComponent } from './update-component';
1718
export { h, Host } from './vdom/h';

src/runtime/render.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type * as d from '../declarations';
2+
import { renderVdom } from './vdom/vdom-render';
3+
4+
/**
5+
* Method to render a virtual DOM tree to a container element.
6+
*
7+
* @example
8+
* ```tsx
9+
* import { render } from '@stencil/core';
10+
*
11+
* const vnode = (
12+
* <div>
13+
* <h1>Hello, world!</h1>
14+
* </div>
15+
* );
16+
* render(vnode, document.body);
17+
* ```
18+
*
19+
* @param vnode - The virtual DOM tree to render
20+
* @param container - The container element to render the virtual DOM tree to
21+
*/
22+
export function render(vnode: d.VNode, container: Element) {
23+
const cmpMeta: d.ComponentRuntimeMeta = {
24+
$flags$: 0,
25+
$tagName$: container.tagName,
26+
};
27+
28+
const ref: d.HostRef = {
29+
$flags$: 0,
30+
$cmpMeta$: cmpMeta,
31+
$hostElement$: container as d.HostElement,
32+
};
33+
34+
renderVdom(ref, vnode);
35+
}

test/wdio/render/render.test.tsx

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { h, render } from '@stencil/core';
2+
import { $, expect } from '@wdio/globals';
3+
4+
import { defineCustomElement } from '../test-components/complex-properties.js';
5+
6+
describe('Render VDOM', () => {
7+
beforeEach(async () => {
8+
document.querySelector('div')?.remove();
9+
});
10+
11+
it('can render an arbitrary VDOM tree', async () => {
12+
const vdom = h('div', { className: 'test' }, 'Hello, world!');
13+
render(vdom, document.body);
14+
15+
await expect($(document.body).$('div')).toMatchInlineSnapshot(`"<div class="test">Hello, world!</div>"`);
16+
});
17+
18+
it('can render a VDOM with a Stencil component', async () => {
19+
defineCustomElement();
20+
21+
const vdom = (
22+
<div>
23+
<complex-properties
24+
foo={{ bar: 123, loo: [1, 2, 3], qux: { quux: Symbol('quux') } }}
25+
baz={new Map([['foo', { qux: Symbol('quux') }]])}
26+
quux={new Set(['foo'])}
27+
corge={new Set([{ foo: { bar: 'foo' } }])}
28+
grault={Infinity}
29+
waldo={null}
30+
kidsNames={['John', 'Jane', 'Jim']}
31+
></complex-properties>
32+
</div>
33+
);
34+
render(vdom, document.body);
35+
await expect($(document.body).$('div')).toMatchInlineSnapshot(`
36+
"<div>
37+
<complex-properties class="hydrated">
38+
<template shadowrootmode="open">
39+
<ul>
40+
<li>this.foo.bar: 123</li>
41+
<li>this.foo.loo: 1, 2, 3</li>
42+
<li>this.foo.qux: symbol</li>
43+
<li>this.baz.get('foo'): symbol</li>
44+
<li>this.quux.has('foo'): true</li>
45+
<li>this.grault: true</li>
46+
<li>this.waldo: true</li>
47+
<li>this.kidsNames: John, Jane, Jim</li>
48+
</ul>
49+
</template>
50+
</complex-properties>
51+
</div>"
52+
`);
53+
});
54+
});

test/wdio/scoped-id-in-nested-classname/cmp.test.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { h } from '@stencil/core';
22
import { render } from '@wdio/browser-runner/stencil';
3+
import { $, expect } from '@wdio/globals';
34

45
describe('scope-id-in-nested-classname', function () {
56
it('should have root scope id in the nested element as classname', async () => {
67
render({
8+
components: [],
79
template: () => <cmp-level-1></cmp-level-1>,
810
});
911
await $('cmp-level-3').waitForStable();
@@ -19,6 +21,7 @@ describe('scope-id-in-nested-classname', function () {
1921

2022
it('should not have root scope id in slotted / user provided nested element as classname', async () => {
2123
render({
24+
components: [],
2225
template: () => (
2326
<cmp-level-1>
2427
<span id="test-element">Test</span>

test/wdio/setup.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const testRequiresManualSetup =
1717
window.__wdioSpec__.includes('global-script') ||
1818
window.__wdioSpec__.endsWith('custom-tag-name.test.tsx') ||
1919
window.__wdioSpec__.endsWith('page-list.test.ts') ||
20-
window.__wdioSpec__.endsWith('event-re-register.test.tsx');
20+
window.__wdioSpec__.endsWith('event-re-register.test.tsx') ||
21+
window.__wdioSpec__.endsWith('render.test.tsx');
2122

2223
/**
2324
* setup all components defined in tests except for those where we want to manually setup

0 commit comments

Comments
 (0)