Vaadin 24集成自定义Web组件出现Illegal Constructor错误求助
问题
我们将Vaadin Flow应用从Vaadin 14迁移至Vaadin 24后,大部分问题已解决,但第三方团队提供的自定义Web组件出现异常。该组件在对方Webpack 5项目中正常运行,且曾在我们的Vaadin 14项目中可用,为适配Vaadin 24已升级相关npm包版本。
组件配置
JS组件文件
- my-comp.js(升级无改动)
import { MyComp } from './src/MyComp' import { MyCompPreview } from './src/MyCompPreview' window.customElements.define('my-comp', MyComp) window.customElements.define('my-comp-preview', MyCompPreview)
- MyComp类(原无构造函数,删除后问题依旧)
export class MyComp extends ScopedElementsMixin(LitElement) { constructor () { super() //已调试确认执行 } static get properties () { return { place: String, dir: String, data: Object, pars: Object } } async firstUpdated (changedProperties) { console.log("first updated") this.data = JSON.parse(this.data); console.log("place id -> ", this.place); console.log("dir -> ", this.dir); console.log("data -> ", this.pars); super.firstUpdated(changedProperties) } }
- MyCompPreview类(升级无改动)
export class MyCompPreview extends MyComp { static get properties () { return { ...super.properties, factory: Function } } firstUpdated () { console.log("preview first updated") super.firstUpdated() } }
集成组件的Java代码
@Tag("my-comp-preview") @JsModule("./src/comps/my-comp.js") public class MyComp extends Component implements HasSize, HasStyle { public MyComp(String place, String json) { setProps(place, json); } public void setProps(String place, String json) { setData(json); setPlace(place); setDir(place); } public void setPlace(String place) { this.getElement().setProperty("place", place); } public void setData(String jsonData) { this.getElement().setProperty("data", jsonData); } public void setDir(String dir) { this.getElement().setProperty("dir", dir); } }
报错信息
实例化组件时触发错误:
generated-flow-imports-rV4Ldkr4.js:833 Uncaught TypeError: Illegal constructor at MyCompPreview.attachShadow (generated-flow-imports-rV4Ldkr4.js:833:25) ...(省略后续栈信息)
报错的attachShadow方法片段:
attachShadow(options) { const { scopedElements } = ( /** @type {typeof ScopedElementsHost} */ this.constructor ); const shouldCreateRegistry = !this.registry || // @ts-ignore this.registry === this.constructor.__registry && !Object.prototype.hasOwnProperty.call(this.constructor, "__registry"); if (shouldCreateRegistry) { this.registry = new CustomElementRegistry(); /// 此处报错,提示非法构造 for (const [tagName, klass] of Object.entries(scopedElements ?? {})) { this.registry.define(tagName, klass); } } return super.attachShadow({ ...options, // The polyfill currently expects the registry to be passed as `customElements` customElements: this.registry, // But the proposal has moved forward, and renamed it to `registry` // For backwards compatibility, we pass it as both registry: this.registry }); }
其他情况:该组件用于根据JSON数据及place/dir属性展示不同布局,我们其他Web组件集成方式相同但未出现此问题,已在Vaadin初始项目中复现该问题。查阅Vaadin及open-wc文档未找到相关内容,未直接调用new MyCompPreview,组件已注册为自定义元素,且曾在Vaadin14正常运行,与组件开发者沟通无果。
疑问:是否需要在Vaadin 24应用中引入Scoped Custom Element Registry polyfill?若需要,在Vaadin自动生成index.html的情况下该如何操作?
排查与解决方案
核心原因
报错中new CustomElementRegistry()提示非法构造,是因为原生浏览器目前不支持原生的Scoped Custom Element Registry,而组件依赖的ScopedElementsMixin(来自open-wc的scoped-elements方案)必须依赖对应的polyfill才能正常工作。Vaadin 14默认可能间接引入了该polyfill,而Vaadin 24的前端环境未包含,导致迁移后报错。
必须引入polyfill的验证
根据open-wc的官方文档,使用scoped-elements必须提前引入polyfill:
import '@webcomponents/scoped-custom-element-registry';
Vaadin 24中引入polyfill的方法
方法1:组件入口文件引入(推荐)
直接在第三方组件的入口文件my-comp.js最顶部添加polyfill导入:
import '@webcomponents/scoped-custom-element-registry'; import { MyComp } from './src/MyComp' import { MyCompPreview } from './src/MyCompPreview' window.customElements.define('my-comp', MyComp) window.customElements.define('my-comp-preview', MyCompPreview)
这种方式仅在该组件加载时引入polyfill,不会影响全局其他组件,避免冲突。
方法2:全局引入(适用于多个组件依赖的情况)
如果多个自定义组件都需要该polyfill,可以通过Vaadin的自定义index.html模板实现全局引入:
- 在项目的
src/main/resources/META-INF/resources/目录下创建index.html文件(若不存在) - 在
<head>标签中添加polyfill的导入:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vaadin App</title> <!-- 引入scoped custom element registry polyfill --> <script type="module" src="@webcomponents/scoped-custom-element-registry"></script> </head> <body> <div id="outlet"></div> </body> </html>
Vaadin会自动识别并使用该自定义模板代替默认生成的index.html。
额外排查点
- 检查
@open-wc/scoped-elements和@webcomponents/scoped-custom-element-registry的版本兼容性,确保两者版本匹配 - 确认组件类上是否正确配置了
scopedElements静态属性(报错中scopedElements ?? {}说明当前可能为undefined,若组件内部依赖该属性注册子元素,需确保其正确初始化) - 检查Vaadin 24的前端打包配置,确认没有排除该polyfill的打包,或存在其他冲突的polyfill
内容的提问来源于stack exchange,提问作者Mari

