-
Notifications
You must be signed in to change notification settings - Fork 785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(compiler): add support for form-associated custom elements #4784
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a specific reason for having this code commented out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm I wish something would throw an error if those @link
bits didn't resolve correctly :/
@@ -1,7 +1,7 @@ | |||
import ts from 'typescript'; | |||
|
|||
import { objectLiteralToObjectMap } from '../transform-utils'; | |||
import type { StencilDecorator } from './decorators-constants'; | |||
import { StencilDecorator } from './decorators-constants'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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: ionic-team/stencil-site#1247 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code-wise, looks solid! Haven't had a change to test it out yet, so will get around to that asap!
export class MockElement extends MockNode { | ||
namespaceURI: string | null; | ||
__attributeMap: MockAttributeMap | null | undefined; | ||
__shadowRoot: ShadowRoot | null | undefined; | ||
__style: MockCSSStyleDeclaration | null | undefined; | ||
|
||
attachInternals(): MockElementInternals { | ||
return new Proxy({} as unknown as MockElementInternals, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -7,6 +7,7 @@ export const config: Config = { | |||
namespace: 'TestApp', | |||
srcDir: 'test-app', | |||
tsconfig: 'tsconfig-stencil.json', | |||
sourceMap: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were sourcemaps messing with the mangling?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
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: ionic-team/stencil-site#1247
d196b66
to
9f2360b
Compare
Form-associated custom elements were added to Stencil in ionic-team/stencil#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 ionic-team/stencil#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:
formAssociated
has been added to theComponentOptions
interface.@AttachInternals()
decorator can be used to indicate a property on a Stencil component to which anElementInternals
object 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