Skip to content

Commit 399fe25

Browse files
feat(react): Next.js Support (#445)
* feat(react-output-target): generate functional components and ES modules (#432) * feat: migrate to lit/react component wrappers * exploration nextjs support * update Stencil with support for DSD * get rid of hydration errors * enhance output target * add tests * fix dep * remove type property in package.json * remove hydrate dir * move ssr support into custom export * clear hydrate folder again * skip lib check * skip lib check * better resolve light dom * remove unused import * match default dir with stencil * more improvements on light dom rendering * remove duplicate hydrate ot * remove style prop * fail if outdir is not set * import hydration script and ssr runtime only within the server component * validate hydrate output target to be set if hydrateModule option is set * improve implementation * separate files for server and client components * properly create server and client side components * don't parse children * use own runtime * validate that external runtime is set to 'true' * adjust test * update Stencil dev build * recognise that externalRuntime default is true * bring back light DOM rendering for better hydration results * revert light dom approach * minor formatting * update stencil * explicitly type component exports * unit test tweak * fix build * use latest Stencil release * fix test --------- Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
1 parent 24de01e commit 399fe25

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+5402
-2348
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ build/
88
.npmrc
99
*.tgz
1010
*.lerna_backup
11+
packages/example-project/component-library/hydrate

packages/example-project/component-library-react/src/components.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
'use client';
2+
13
/**
24
* This file was automatically generated by the Stencil React Output Target.
35
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
46
*/
57

68
/* eslint-disable */
79

8-
import type { EventName } from '@stencil/react-output-target/runtime';
10+
import type { EventName, StencilReactComponent } from '@stencil/react-output-target/runtime';
911
import { createComponent } from '@stencil/react-output-target/runtime';
1012
import {
1113
type CheckboxChangeEventDetail,
@@ -58,7 +60,10 @@ type MyButtonEvents = {
5860
onMyBlur: EventName<CustomEvent<void>>;
5961
};
6062

61-
export const MyButton = /*@__PURE__*/ createComponent<MyButtonElement, MyButtonEvents>({
63+
export const MyButton: StencilReactComponent<MyButtonElement, MyButtonEvents> = /*@__PURE__*/ createComponent<
64+
MyButtonElement,
65+
MyButtonEvents
66+
>({
6267
tagName: 'my-button',
6368
elementClass: MyButtonElement,
6469
react: React,
@@ -75,7 +80,10 @@ type MyCheckboxEvents = {
7580
onMyBlur: EventName<CustomEvent<void>>;
7681
};
7782

78-
export const MyCheckbox = /*@__PURE__*/ createComponent<MyCheckboxElement, MyCheckboxEvents>({
83+
export const MyCheckbox: StencilReactComponent<MyCheckboxElement, MyCheckboxEvents> = /*@__PURE__*/ createComponent<
84+
MyCheckboxElement,
85+
MyCheckboxEvents
86+
>({
7987
tagName: 'my-checkbox',
8088
elementClass: MyCheckboxElement,
8189
react: React,
@@ -89,7 +97,10 @@ export const MyCheckbox = /*@__PURE__*/ createComponent<MyCheckboxElement, MyChe
8997

9098
type MyComponentEvents = { onMyCustomEvent: EventName<CustomEvent<number>> };
9199

92-
export const MyComponent = /*@__PURE__*/ createComponent<MyComponentElement, MyComponentEvents>({
100+
export const MyComponent: StencilReactComponent<MyComponentElement, MyComponentEvents> = /*@__PURE__*/ createComponent<
101+
MyComponentElement,
102+
MyComponentEvents
103+
>({
93104
tagName: 'my-component',
94105
elementClass: MyComponentElement,
95106
react: React,
@@ -104,7 +115,10 @@ type MyInputEvents = {
104115
onMyFocus: EventName<CustomEvent<void>>;
105116
};
106117

107-
export const MyInput = /*@__PURE__*/ createComponent<MyInputElement, MyInputEvents>({
118+
export const MyInput: StencilReactComponent<MyInputElement, MyInputEvents> = /*@__PURE__*/ createComponent<
119+
MyInputElement,
120+
MyInputEvents
121+
>({
108122
tagName: 'my-input',
109123
elementClass: MyInputElement,
110124
react: React,
@@ -120,11 +134,14 @@ export const MyInput = /*@__PURE__*/ createComponent<MyInputElement, MyInputEven
120134
type MyPopoverEvents = {
121135
onMyPopoverDidPresent: EventName<CustomEvent<void>>;
122136
onMyPopoverWillPresent: EventName<CustomEvent<void>>;
123-
onMyPopoverWillDismiss: EventName<MyPopoverCustomEvent<OverlayEventDetail<any>>>;
124-
onMyPopoverDidDismiss: EventName<MyPopoverCustomEvent<OverlayEventDetail<any>>>;
137+
onMyPopoverWillDismiss: EventName<MyPopoverCustomEvent<OverlayEventDetail>>;
138+
onMyPopoverDidDismiss: EventName<MyPopoverCustomEvent<OverlayEventDetail>>;
125139
};
126140

127-
export const MyPopover = /*@__PURE__*/ createComponent<MyPopoverElement, MyPopoverEvents>({
141+
export const MyPopover: StencilReactComponent<MyPopoverElement, MyPopoverEvents> = /*@__PURE__*/ createComponent<
142+
MyPopoverElement,
143+
MyPopoverEvents
144+
>({
128145
tagName: 'my-popover',
129146
elementClass: MyPopoverElement,
130147
react: React,
@@ -143,7 +160,10 @@ type MyRadioEvents = {
143160
onMySelect: EventName<CustomEvent<void>>;
144161
};
145162

146-
export const MyRadio = /*@__PURE__*/ createComponent<MyRadioElement, MyRadioEvents>({
163+
export const MyRadio: StencilReactComponent<MyRadioElement, MyRadioEvents> = /*@__PURE__*/ createComponent<
164+
MyRadioElement,
165+
MyRadioEvents
166+
>({
147167
tagName: 'my-radio',
148168
elementClass: MyRadioElement,
149169
react: React,
@@ -157,21 +177,25 @@ export const MyRadio = /*@__PURE__*/ createComponent<MyRadioElement, MyRadioEven
157177

158178
type MyRadioGroupEvents = { onMyChange: EventName<MyRadioGroupCustomEvent<RadioGroupChangeEventDetail>> };
159179

160-
export const MyRadioGroup = /*@__PURE__*/ createComponent<MyRadioGroupElement, MyRadioGroupEvents>({
161-
tagName: 'my-radio-group',
162-
elementClass: MyRadioGroupElement,
163-
react: React,
164-
events: { onMyChange: 'myChange' } as MyRadioGroupEvents,
165-
defineCustomElement: defineMyRadioGroup,
166-
});
180+
export const MyRadioGroup: StencilReactComponent<MyRadioGroupElement, MyRadioGroupEvents> =
181+
/*@__PURE__*/ createComponent<MyRadioGroupElement, MyRadioGroupEvents>({
182+
tagName: 'my-radio-group',
183+
elementClass: MyRadioGroupElement,
184+
react: React,
185+
events: { onMyChange: 'myChange' } as MyRadioGroupEvents,
186+
defineCustomElement: defineMyRadioGroup,
187+
});
167188

168189
type MyRangeEvents = {
169190
onMyChange: EventName<MyRangeCustomEvent<RangeChangeEventDetail>>;
170191
onMyFocus: EventName<CustomEvent<void>>;
171192
onMyBlur: EventName<CustomEvent<void>>;
172193
};
173194

174-
export const MyRange = /*@__PURE__*/ createComponent<MyRangeElement, MyRangeEvents>({
195+
export const MyRange: StencilReactComponent<MyRangeElement, MyRangeEvents> = /*@__PURE__*/ createComponent<
196+
MyRangeElement,
197+
MyRangeEvents
198+
>({
175199
tagName: 'my-range',
176200
elementClass: MyRangeElement,
177201
react: React,

packages/example-project/component-library-vue/src/vue-component-lib/utils.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ export const defineContainer = <Props, VModelType = string | number | boolean>(
115115
if (routerLink === EMPTY_PROP) return;
116116

117117
if (navManager !== undefined) {
118+
/**
119+
* This prevents the browser from
120+
* performing a page reload when pressing
121+
* an Ionic component with routerLink.
122+
* The page reload interferes with routing
123+
* and causes ion-back-button to disappear
124+
* since the local history is wiped on reload.
125+
*/
126+
ev.preventDefault();
127+
118128
let navigationPayload: any = { event: ev };
119129
for (const key in props) {
120130
const value = props[key];
@@ -185,6 +195,17 @@ export const defineContainer = <Props, VModelType = string | number | boolean>(
185195
}
186196
}
187197

198+
// If router link is defined, add href to props
199+
// in order to properly render an anchor tag inside
200+
// of components that should become activatable and
201+
// focusable with router link.
202+
if (props[ROUTER_LINK_VALUE] !== EMPTY_PROP) {
203+
propsToAdd = {
204+
...propsToAdd,
205+
href: props[ROUTER_LINK_VALUE],
206+
};
207+
}
208+
188209
/**
189210
* vModelDirective is only needed on components that support v-model.
190211
* As a result, we conditionally call withDirectives with v-model components.

packages/example-project/component-library-vue/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"sourceMap": true,
1919
"jsx": "react",
2020
"target": "esnext",
21-
"types": ["jest"]
21+
"types": ["jest"],
22+
"skipLibCheck": true
2223
},
2324
"include": ["src/**/*.ts", "src/**/*.tsx"],
2425
"exclude": ["./__tests__/**", "node_modules", "setupTests.ts"],

packages/example-project/component-library/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"type": "git",
99
"url": "https://github.com/ionic-team/stencil-ds-output-targets.git"
1010
},
11-
"main": "dist/index.js",
11+
"main": "dist/index.cjs.js",
1212
"module": "dist/index.mjs",
1313
"es2015": "dist/esm/index.mjs",
1414
"es2017": "dist/esm/index.mjs",
@@ -31,7 +31,7 @@
3131
},
3232
"devDependencies": {
3333
"@stencil/angular-output-target": "workspace:*",
34-
"@stencil/core": "^4.16.0",
34+
"@stencil/core": "^4.21.0",
3535
"@stencil/react-output-target": "workspace:*",
3636
"@stencil/vue-output-target": "workspace:*",
3737
"@types/puppeteer": "2.0.1",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
:host {
22
display: block;
3+
color: green;
34
}

packages/example-project/component-library/src/components/my-input/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
| ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
1212
| `accept` | `accept` | If the value of the type attribute is `"file"`, then this attribute will indicate the types of files that the server accepts, otherwise it will be ignored. The value must be a comma-separated list of unique content type specifiers. | `string` | `undefined` |
1313
| `autocapitalize` | `autocapitalize` | Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. | `string` | `'off'` |
14-
| `autocomplete` | `autocomplete` | Indicates whether the value of the control can be automatically completed by the browser. | `"name" \| "on" \| "off" \| "honorific-prefix" \| "given-name" \| "additional-name" \| "family-name" \| "honorific-suffix" \| "nickname" \| "email" \| "username" \| "new-password" \| "current-password" \| "one-time-code" \| "organization-title" \| "organization" \| "street-address" \| "address-line1" \| "address-line2" \| "address-line3" \| "address-level4" \| "address-level3" \| "address-level2" \| "address-level1" \| "country" \| "country-name" \| "postal-code" \| "cc-name" \| "cc-given-name" \| "cc-additional-name" \| "cc-family-name" \| "cc-number" \| "cc-exp" \| "cc-exp-month" \| "cc-exp-year" \| "cc-csc" \| "cc-type" \| "transaction-currency" \| "transaction-amount" \| "language" \| "bday" \| "bday-day" \| "bday-month" \| "bday-year" \| "sex" \| "tel" \| "tel-country-code" \| "tel-national" \| "tel-area-code" \| "tel-local" \| "tel-extension" \| "impp" \| "url" \| "photo"` | `'off'` |
14+
| `autocomplete` | `autocomplete` | Indicates whether the value of the control can be automatically completed by the browser. | `"off" \| "on" \| "name" \| "honorific-prefix" \| "given-name" \| "additional-name" \| "family-name" \| "honorific-suffix" \| "nickname" \| "email" \| "username" \| "new-password" \| "current-password" \| "one-time-code" \| "organization-title" \| "organization" \| "street-address" \| "address-line1" \| "address-line2" \| "address-line3" \| "address-level4" \| "address-level3" \| "address-level2" \| "address-level1" \| "country" \| "country-name" \| "postal-code" \| "cc-name" \| "cc-given-name" \| "cc-additional-name" \| "cc-family-name" \| "cc-number" \| "cc-exp" \| "cc-exp-month" \| "cc-exp-year" \| "cc-csc" \| "cc-type" \| "transaction-currency" \| "transaction-amount" \| "language" \| "bday" \| "bday-day" \| "bday-month" \| "bday-year" \| "sex" \| "tel" \| "tel-country-code" \| "tel-national" \| "tel-area-code" \| "tel-local" \| "tel-extension" \| "impp" \| "url" \| "photo"` | `'off'` |
1515
| `autocorrect` | `autocorrect` | Whether auto correction should be enabled when the user is entering/editing the text value. | `"off" \| "on"` | `'off'` |
1616
| `autofocus` | `autofocus` | This Boolean attribute lets you specify that a form control should have input focus when the page loads. | `boolean` | `false` |
1717
| `clearInput` | `clear-input` | If `true`, a clear icon will appear in the input when there is a value. Clicking it clears the input. | `boolean` | `false` |

packages/example-project/component-library/stencil.config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,28 @@ export const config: Config = {
6161
reactOutputTarget({
6262
outDir: '../component-library-react/src',
6363
}),
64+
reactOutputTarget({
65+
outDir: '../next-app/src/app',
66+
hydrateModule: 'component-library/hydrate'
67+
}),
6468
vueOutputTarget({
6569
componentCorePackage: 'component-library',
6670
proxiesFile: '../component-library-vue/src/proxies.ts',
6771
componentModels: vueComponentModels,
6872
}),
6973
{
7074
type: 'dist-custom-elements',
75+
externalRuntime: false,
7176
dir: 'components'
7277
},
7378
{
7479
type: 'dist',
7580
esmLoaderPath: '../loader',
7681
},
82+
{
83+
type: 'dist-hydrate-script',
84+
dir: './hydrate',
85+
},
7786
{
7887
type: 'docs-readme',
7988
},

0 commit comments

Comments
 (0)