For some context: we are trying to see if we can ship some lightning web components of an app we are building so they could potentially be used standalone by other apps that don't use LWC yet.
The bug is that when using customElements.define(tagName, LightningWebComponentSubclass.CustomElementConstructor) to convert Lightning Web Components to native Web Components, you can't register custom elements that are dependencies of other components.
The scenario:
<custom-b></custom-b>CustomElementConstructor works without error: customElements.define('custom-a', CustomA.CustomElementConstructor)NotSupportedError below: customElements.define('custom-b', CustomB.CustomElementConstructor)When started with npm run watch, and loading in the browser, we get the following runtime error:
NotSupportedError: Failed to execute 'attachShadow' on 'Element': Shadow root cannot be created on a host which already hosts a shadow tree.

To reproduce you can clone this example project, run npm install, and then npm run watch and visit localhost:3001 in the browser.
Code where error gets triggered when defining LWC as regular web components using customElements.define:
// src/index.js
import A from './modules/custom/a/a';
import B from './modules/custom/b/b';
// no error when registering A which depends on B
customElements.define('custom-a', A.CustomElementConstructor);
// error when trying to register B: NotSupporteErorr: Failed to execute 'attachShadow' on 'Element'...
customElements.define('custom-b', B.CustomElementConstructor);
Component A:
// src/modules/custom/a/a.js
import { LightningElement } from 'lwc';
export default class extends LightningElement {}
<!-- src/modules/custom/a/a.html -->
<template>
This is the A Component
<custom-b></custom-b>
</template>
Component B:
// src/modules/custom/b/b.js
import { LightningElement } from 'lwc';
export default class extends LightningElement {}
<!-- src/modules/custom/b/b.html -->
<template>
This is the B component
</template>
No error is thrown
Error thrown:
NotSupportedError: Failed to execute 'attachShadow' on 'Element': Shadow root cannot be created on a host which already hosts a shadow tree.
All supported browsers, all versions.
Node 12.13.0
package.json
versions: "[email protected]"
I'll let the LWC team address how/if they plan to solve this problem, but here's the rollup plugin I used to solve it.
/*
This is VERY HACKY
When the LWC engine adds child components, it calls document.createElement()
with a tag name that matches the namespace/name of the component,
even though it's not actually a custom element.
https://github.com/salesforce/lwc/blob/master/packages/%40lwc/engine-dom/src/apis/create-element.ts#L108
This causes problems when the engine tries to mount a component
that is also a registed custom element.
This hack goes through the source code and adds a prefix to the lwc
elements so they don't collide with registed custom elements.
Compiled templates look like this when creating a custom component:
api_custom_element("th-header-desktop", _thHeaderDesktop, {})
*/
function HACK_namespaceLWC() {
return {
renderChunk: (code, chunk, options) => {
return {
code: code.replace(
/api_custom_element\(\"/g,
'api_custom_element("lwc-'
),
map: null
};
}
};
}
One thing to note about this solution is that you would need to add the prefix if you were referencing element names in the css / or querying the DOM with querySelector.
Thanks @aputinski. We could give that a try as a workaround in the meantime!
I also found this interesting comment with createElement:
While it seems like createElement is an escape hatch and not desired to be used directly, I wonder if the same check could be ported over to buildCustomElementConstructor? Not super familiar with the internals myself but the code paths look similar to createElement in the way that createVM is called:
I think that's referencing the top level createElement export of lwc and not the document.createElement used to insert new DOM elements during the lwc render cycle.
But I also have no idea what I'm taking about 馃槀
this will be fixed by #2007 :), good timing!
Just to clarify, "allow usage of previously defined custom elements tag names in a LWC template" is only for elements defined with CustomElementConstructor (meaning LWC will still throw at compile time for unresolved custom elements)?
meaning LWC will still throw at compile time for unresolved custom elements
No, the planned fix is generic enough for you to resolve to a constructor that is registered as a custom element in the global registry. we don't do anything with the constructor all the way down to the creation of the element. that means you can configure the compiler to resolve to a custom element that does not have to be lwc derivate.
Would love to see this land soon. ;-)
pending approval and release drills, but it will take a while to get it into platform, but in OSS it will be ready soon :)
OSS is what I care about. ;-)
fixed by #2007 and #2027
Most helpful comment
No, the planned fix is generic enough for you to resolve to a constructor that is registered as a custom element in the global registry. we don't do anything with the constructor all the way down to the creation of the element. that means you can configure the compiler to resolve to a custom element that does not have to be lwc derivate.