Safari browser does not support build-in custom elements, only anonymous custom elements.

Compatibility is shown in the following figure.

That is, Safari only supports the following HTML-formatted UI components by default.

1
2
3
4
<ui-tips></ui-tips>
<ui-drop></ui-drop>
<ui-tab></ui-tab>
<ui-lighttip></ui-lighttip>

The following web components components, which extend on native HTML elements via the is attribute, are not supported.

1
2
3
4
<input is="ui-color">
<select is="ui-select"></select>
<form is="ui-form"></form>
<table is="ui-table"></table>

That’s a problem, otherwise great UI components are verbose and tedious when they can only be implemented using anonymous custom elements.

Or, be ruthless and ignore the Safari user community, which you can, and just wait to be fired.

So, the best solution is to make the Safari browser support the development of built-in custom element components as well.

How to do it?

Polyfill build-in custom element

The solution is relatively simple, there is a special polyfill, just introduce it.

Project address.

https://github.com/WebReflection/custom-elements-builtin

For scenarios where custom elements are supported, but not built-in custom elements, mainly for the Safari browser.

The index.js is a non-compressed version and es.js is a compressed version.

When using it, you can directly use it as follows.

1
2
3
4
<script>
if (!(self.chrome || self.navigator))
  document.write('<script src="//unpkg.com/@webreflection/custom-elements-builtin"><\x2fscript>');
</script>

However, if judgment plus document.write is not a good use, but we can’t directly quote //unpkg.com/@webreflection/custom-elements-builtin from this address.

According to the author, browser characteristics are always changing, and it is not reliable to do browser type determination.

This is true, but instead of making a browser distinction, we can just make a distinction based on API characteristics.

So, I made a built-in judgment process for the original JS code.

The judgment logic is shown in the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class AnyClass extends HTMLBRElement {
  constructor () {
    super();

    this.someMethod = true;
  }
}

if (!customElements.get('any-class')) {
  customElements.define('any-class', AnyClass, {
    extends: 'br'
  });
}

// Does it support `build-in custom element`?
const isSupportBuildIn = document.createElement('br', {
  is: 'any-class'
}).someMethod;

It is safe to judge based on features, the return value of isSupportBuildIn will be true if the browser supports built-in custom elements, and undefined if the browser does not.

The optimized JS code I have open sourced and put on gitee, see: https://gitee.com/zhangxinxu/build-in-custom-element-polyfill

Problems with perfect operation

This Polyfill is really amazing, the original Web Components code does not need any modification, the component function is perfectly supported in Safari browser, and all components are working well.

Then, when it comes to the business code, the problem is found when the components are passed with references.

For example, the referenced code is as follows.

1
2
3
4
5
<script src="safari-polyfill.js"></script>
<script type="module" src="my-components.js"></script>
<script type="module">
myComponent.someMethod();
</script>

The myComponent.someMethod() method executes fine in browsers that natively support built-in custom elements.

However, in Safari, it reports an error and undefined cannot be executed as a function.

The reason for this is that the implementation of safari-polyfill.js has a limitation that makes the initialization of custom elements occur later than in native browsers.

That is, in Safari, when the myComponent.someMethod() method is executed, the myComponent element has not yet become a built-in custom element.

As a result, the execution will error out.

My workaround is to trigger a custom 'connected' event in the connectedCallback lifecycle function, so that the element is already componentized when the business code is executed by binding the 'connected' event.

The code is illustrated in the code section of the component.

1
2
3
4
5
6
7
connectedCallback () {
  this.dispatchEvent(new CustomEvent('connected'), {
    detail: {
      type: 'my-components'
    }
  });
}

Then, the business code is transformed to look like this.

1
2
3
4
5
6
7
<script src="safari-polyfill.js"></script>
<script type="module" src="my-components.js"></script>
<script> 
myComponent.addEventListener('connected', function () {
    this.someMethod();
});
</script>

This ensures that the component must have been initialized by the time the someMethod() method is executed.


Reference https://www.zhangxinxu.com/wordpress/2021/04/safari-buildin-custom-element-polyfill/