feat(compiler): add support for form-associated custom elements#4784
feat(compiler): add support for form-associated custom elements#4784alicewriteswrongs merged 1 commit intomainfrom
Conversation
|
| Path | Error Count |
|---|---|
| src/dev-server/index.ts | 37 |
| src/mock-doc/serialize-node.ts | 36 |
| src/dev-server/server-process.ts | 32 |
| src/compiler/build/build-stats.ts | 27 |
| src/compiler/output-targets/dist-lazy/generate-lazy-module.ts | 25 |
| src/compiler/style/test/optimize-css.spec.ts | 23 |
| src/testing/puppeteer/puppeteer-element.ts | 23 |
| src/compiler/prerender/prerender-main.ts | 22 |
| src/runtime/vdom/vdom-render.ts | 20 |
| src/runtime/client-hydrate.ts | 19 |
| src/screenshot/connector-base.ts | 19 |
| src/compiler/config/test/validate-paths.spec.ts | 16 |
| src/dev-server/request-handler.ts | 15 |
| src/compiler/prerender/prerender-optimize.ts | 14 |
| src/compiler/sys/stencil-sys.ts | 14 |
| src/compiler/transpile/transpile-module.ts | 14 |
| src/runtime/vdom/vdom-annotations.ts | 14 |
| src/sys/node/node-sys.ts | 14 |
| src/compiler/build/build-finish.ts | 13 |
| src/compiler/prerender/prerender-queue.ts | 13 |
Our most common errors
| Typescript Error Code | Count |
|---|---|
| TS2345 | 424 |
| TS2322 | 398 |
| TS18048 | 310 |
| TS18047 | 100 |
| TS2722 | 38 |
| TS2532 | 34 |
| TS2531 | 23 |
| TS2454 | 14 |
| TS2352 | 13 |
| TS2769 | 10 |
| TS2790 | 10 |
| TS2538 | 8 |
| TS2344 | 5 |
| TS2416 | 4 |
| TS2493 | 3 |
| TS18046 | 2 |
| TS2684 | 1 |
| TS2488 | 1 |
| TS2430 | 1 |
Unused exports report
There are 12 unused exports on this PR. That's the same number of errors on main, so at least we're not creating new ones!
Unused exports
| File | Line | Identifier |
|---|---|---|
| src/runtime/bootstrap-lazy.ts | 21 | setNonce |
| src/screenshot/screenshot-fs.ts | 18 | readScreenshotData |
| src/testing/testing-utils.ts | 198 | withSilentWarn |
| src/utils/index.ts | 145 | CUSTOM |
| src/compiler/app-core/app-data.ts | 25 | BUILD |
| src/compiler/app-core/app-data.ts | 115 | Env |
| src/compiler/app-core/app-data.ts | 117 | NAMESPACE |
| src/compiler/fs-watch/fs-watch-rebuild.ts | 123 | updateCacheFromRebuild |
| src/compiler/types/validate-primary-package-output-target.ts | 62 | satisfies |
| src/compiler/types/validate-primary-package-output-target.ts | 62 | Record |
| src/testing/puppeteer/puppeteer-declarations.ts | 485 | WaitForEventOptions |
| src/compiler/sys/fetch/write-fetch-success.ts | 7 | writeFetchSuccessSync |
b62ed68 to
464ef1f
Compare
src/compiler/transformers/decorators-to-static/form-internals-decorator.ts
Outdated
Show resolved
Hide resolved
af5c47e to
02c50e9
Compare
alicewriteswrongs
left a comment
There was a problem hiding this comment.
just pointing out one thing. I've cleanup up a few TODO items and little things here and there, I think this is ready for a more thorough review
tanner-reits
left a comment
There was a problem hiding this comment.
All-in-all looks good imo. Noted a couple things, but no blockers!
src/compiler/transformers/decorators-to-static/form-internals-decorator.ts
Outdated
Show resolved
Hide resolved
rwaskiewicz
left a comment
There was a problem hiding this comment.
All in all looks good! Architecturally I think this looks great!
I took this for a spin this morning, and ran into a few things that I wanted to run by you/the team. Some are commented inline, others are higher level and are below in the summary here:
Unit testing
When unit testing a stencil component starter (npm init stencil@latest component) and updating the component to support element internals:

I get the following error running tests on the project (npm t):
FAIL src/components/my-component/my-component.spec.ts
● Console
console.log
undefined
at MyComponent.getText (src/components/my-component/my-component.tsx:30:13)
console.log
undefined
at MyComponent.getText (src/components/my-component/my-component.tsx:30:13)
● my-component › renders
TypeError: hostRef.$hostElement$.attachInternals is not a function
8 | formAssociated: true
9 | }
> 10 | })
| ^
11 | export class MyComponent {
12 | @FormInternals() internals: ElementInternals;
13 |
at new MyComponent (src/components/my-component/my-component.tsx:10:44)
at initializeComponent (node_modules/@stencil/core/internal/testing/index.js:581:5)
● my-component › renders with values
TypeError: hostRef.$hostElement$.attachInternals is not a function
8 | formAssociated: true
9 | }
> 10 | })
| ^
11 | export class MyComponent {
12 | @FormInternals() internals: ElementInternals;
13 |
at new MyComponent (src/components/my-component/my-component.tsx:10:44)
at initializeComponent (node_modules/@stencil/core/internal/testing/index.js:581:5)
Did you see this at all in your testing? Anything abundantly clear that I'm doing wrong here?
HMR console errors
In testing this with the dev server, it looks like on every reload we get a DOMException: NotSupportedError as a result of trying to call AttachInternals() again. In the video below, I have the component that we generate from running npm init stencil@latest component, adapted to use formAssociated and @FormInternals (you should be able to see the changes I made to the source file in the left gutter of the editor there):
Screen.Recording.2023-09-15.at.8.48.08.AM.mov
I also tried:
- Adding a second copy of this component to
index.html- it appears we'll get an error for each component in the DOM - I verified this happens when a component does/doesn't have a parent
<form>
Karma Testing
Could we add a Karma test that verifies that we can compile a component that uses form internals & capture some form data coming from a custom element?
src/compiler/transformers/decorators-to-static/component-decorator.ts
Outdated
Show resolved
Hide resolved
src/compiler/transformers/decorators-to-static/component-decorator.ts
Outdated
Show resolved
Hide resolved
src/compiler/transformers/decorators-to-static/decorators-constants.ts
Outdated
Show resolved
Hide resolved
54c41de to
122a9bd
Compare
|
on the higher-level things: testingI think we'll need to probably add an element internals polyfill to mock-doc in order to support this stuff in unit tests. I'll look into adding this today. With Karma tests I was planning to add a test suite for all this behavior but held off until the rest of the code was stabilized and whatnot, so likewise I'll dig into that today too. HMR stuffI've run into that error during local dev myself, essentially it's because you can't |
d51af42 to
0931e64
Compare
|
@rwaskiewicz @tanner-reits I managed to figure out the Karma issue, basically I ended up just adding |
7201415 to
827373a
Compare
|
Alright I also just added an "implementation" of Screen.Recording.2023-09-25.at.3.13.05.PM.mov |
a48e908 to
baf5570
Compare
| // it('should link up to the surrounding form', async () => { | ||
| // const formEl = app.querySelector("form"); | ||
| // await waitForChanges(); | ||
| // const formData = new FormData(formEl); | ||
| // expect(formData.get("test-input")).toBe("my default value"); | ||
| // }); |
There was a problem hiding this comment.
Is there a specific reason for having this code commented out?
There was a problem hiding this comment.
just haven't gotten it working yet! I need to circle back once I've sorted out the HMR stuff
2965076 to
1b7ac63
Compare
|
Ok so I haven't yet gone through the whole diff with a fine-toothed comb to make sure that all testing code and temporary changes and whatnot are all removed, but as of right now I think all of the functionality stuff I needed to get in place is finished. So HMR should now be fully supported! 🎉 So while I don't think this is quite ready for another close code review I do think it's ready for more functional testing |
alicewriteswrongs
left a comment
There was a problem hiding this comment.
noting one thing
| @@ -55,64 +57,49 @@ export const parseStaticComponentMeta = ( | |||
| const isCollectionDependency = moduleFile.isCollectionDependency; | |||
| const encapsulation = parseStaticEncapsulation(staticMembers); | |||
| const cmp: d.ComponentCompilerMeta = { | |||
There was a problem hiding this comment.
the diff here looks scarier than it is, I only actually changed two lines but the object wasn't alphabetized
one Q for reviewers: do we think this is safe? It's possible that some of the helper methods here have side-effects which would mean that we can't assume it's possible to safely re-order them
There was a problem hiding this comment.
Could we pull this change out into a separate pull request? That ought to help us keep the scope of this small(er) and that way, we can worry about that separate from the element internals implementation work.
FWIW, I think one could argue that this was fine in the order it was originally in (at least in part) - the left hand side keeps things like class member-related items together. I'm not opposed to the change, just want to throw it out there that alphabetizing is something we should do judiciously as opposed to as the default (I've seen us as a team do that in a few PRs now).
There was a problem hiding this comment.
I ended up backing that change out already as I was trying to debug the karma issues, pushing that code up now
fb011f9 to
fd268d3
Compare
|
Ok I believe this is now ready for code review. There are some issues with the Karma tests which I'm working on ironing out right now, but I think the code should still be ready for review |
rwaskiewicz
left a comment
There was a problem hiding this comment.
Small number of asks - I haven't battle tested this yet, I'll do that once the dust has settled on Karma tests
| * lazy-load ready Stencil component. | ||
| * | ||
| * In order to create a lazy-loaded form-associated component we need to access | ||
| * the underlying host element (via the "$hostElement$" prop on {@link d.hostRef}) |
There was a problem hiding this comment.
should this be:
| * the underlying host element (via the "$hostElement$" prop on {@link d.hostRef}) | |
| * the underlying host element (via the "$hostElement$" prop on {@link d.HostRef}) |
There was a problem hiding this comment.
hmm I wish something would throw an error if those @link bits didn't resolve correctly :/
|
|
||
| import { objectLiteralToObjectMap } from '../transform-utils'; | ||
| import type { StencilDecorator } from './decorators-constants'; | ||
| import { StencilDecorator } from './decorators-constants'; |
There was a problem hiding this comment.
It looks like we only use StencilDecorator as a type in this file - is there a particular reason we remove the type keyword from the import statement?
| @@ -55,64 +57,49 @@ export const parseStaticComponentMeta = ( | |||
| const isCollectionDependency = moduleFile.isCollectionDependency; | |||
| const encapsulation = parseStaticEncapsulation(staticMembers); | |||
| const cmp: d.ComponentCompilerMeta = { | |||
There was a problem hiding this comment.
Could we pull this change out into a separate pull request? That ought to help us keep the scope of this small(er) and that way, we can worry about that separate from the element internals implementation work.
FWIW, I think one could argue that this was fine in the order it was originally in (at least in part) - the left hand side keeps things like class member-related items together. I'm not opposed to the change, just want to throw it out there that alphabetizing is something we should do judiciously as opposed to as the default (I've seen us as a team do that in a few PRs now).
55e24ba to
81a628c
Compare
|
some documentation up here: stenciljs/site#1247 |
tanner-reits
left a comment
There was a problem hiding this comment.
Code-wise, looks solid! Haven't had a change to test it out yet, so will get around to that asap!
| __style: MockCSSStyleDeclaration | null | undefined; | ||
|
|
||
| attachInternals(): MockElementInternals { | ||
| return new Proxy({} as unknown as MockElementInternals, { |
There was a problem hiding this comment.
appreciate all the documentation here!
81a628c to
0e8548b
Compare
0e8548b to
bd92e5b
Compare
|
alright at long last the build is fully passing here, so I believe this is all ready for a code review! |
rwaskiewicz
left a comment
There was a problem hiding this comment.
Looks good! I had one non-blocking question, and found one bug that I think we can work on next week (and don't need to handle just this second).
Video of the bug in question below. In it, I have the output of npm init stencil@latest component with this branch installed. When I flip the bit on formAssociated, I get a few console errors in the browser:
Screen.Recording.2023-10-12.at.4.18.37.PM.mov
test/karma/stencil.config.ts
Outdated
| namespace: 'TestApp', | ||
| srcDir: 'test-app', | ||
| tsconfig: 'tsconfig-stencil.json', | ||
| sourceMap: false, |
There was a problem hiding this comment.
Were sourcemaps messing with the mangling?
There was a problem hiding this comment.
I think so, although I think I just inadvertently left that change in after fixing the real issue - I flipped it back and just pushed that up
|
@rwaskiewicz does that only happen with HMR or on first page load too? |
|
@alicewriteswrongs it always happens in HMR when the value goes from |
c6cc9af to
d196b66
Compare
This adds support for building form-associated custom elements in Stencil components, allowing Stencil components to participate in HTML forms in a rich manner. This is a popular request in the Stencil community (see #2284). The new form-association technology is exposed to the component author via the following changes: - A new option called `formAssociated` has been added to the [`ComponentOptions`](https://github.com/ionic-team/stencil/blob/06f6fad174c32b270ce239afab5002c23d30ccbc/src/declarations/stencil-public-runtime.ts#L10-L55) interface. - A new `@AttachInternals()` decorator can be used to indicate a property on a Stencil component to which an [`ElementInternals`](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) object will be bound at runtime. - Using `@AttachInternals()` is supported both for lazy builds ([`www`](https://stenciljs.com/docs/www), [`dist`](https://stenciljs.com/docs/distribution)) as well as for [`dist-custom-elements`](https://stenciljs.com/docs/custom-elements). The new behavior is implemented at compile-time, and so should result in only very minimal increases in code / bundle size. Support exists for using form-associated components in both the lazy and the CE output targets, as well as some extremely minimal provisions for testing. Documentation for this feature was added to the Stencil site here: stenciljs/site#1247
d196b66 to
9f2360b
Compare
Form-associated custom elements were added to Stencil in stenciljs/core#4784 for version 4.5.0. This adds documentation explaining the changes to the component authoring DSL and also breaking down some examples of how the functionality could be used to build out a component.
Form-associated custom elements were added to Stencil in stenciljs/core#4784 for version 4.5.0. This adds documentation explaining the changes to the component authoring DSL and also breaking down some examples of how the functionality could be used to build out a component.
This adds support for building form-associated custom elements in Stencil components, allowing Stencil components to participate in HTML forms in a rich manner. This is a popular request in the Stencil community (see #2284).
A minimal Stencil component that uses the new APIs to integrate with a form could look like this:
A few things to note:
formAssociatedhas been added to theComponentOptionsinterface.@AttachInternals()decorator can be used to indicate a property on a Stencil component to which anElementInternalsobject will be bound at runtime.@AttachInternals()is supported both for lazy builds (www,dist) as well as fordist-custom-elements.The new behavior is implemented at compile-time, and so should result in only very minimal increases in code / bundle size.
Note
Browser support for the APIs that this feature depends on is still relatively low (~84% as of this writing) and the Stencil team does not plan to officially support or incorporate any polyfills for the browser functionality. Accordingly, this is a 'use at your own risk' feature: it is up to you as a developer to ensure the browsers you need to support have shipped the necessary APIs.
What is the current behavior?
Stencil does not support form-associated custom elements! Forms and Stencil components just aren't talking to each other as much as they could be.
What is the new behavior?
The Stencil compiler now supports emitting form-associated custom elements in the lazy and 'native' output targets.
Does this introduce a breaking change?
Testing
There are new tests which cover most of the functionality here.
There's an example Stencil project making use of these APIs here: https://github.com/alicewriteswrongs/stencil-element-internals-example
Other information