Home >Web Front-end >CSS Tutorial >An Approach to Lazy Loading Custom Elements

An Approach to Lazy Loading Custom Elements

Joseph Gordon-Levitt
Joseph Gordon-LevittOriginal
2025-03-09 11:39:10208browse

An Approach to Lazy Loading Custom Elements

This article explores a lazy loading method for custom elements to improve web page performance. This method was inspired by colleagues' experiments, and its core idea was to automatically load the corresponding implementation code after custom elements were added to the DOM.

Usually, there is no need for such a complex lazy loading mechanism, but the techniques described in this article are still valuable for specific scenarios.

To maintain consistency, the lazy loader itself is also designed as a custom element for easy configuration through HTML. First, we need to gradually identify unresolved custom elements:

class AutoLoader extends HTMLElement {
  connectedCallback() {
    const scope = this.parentNode;
    this.discover(scope);
  }
}
customElements.define("ce-autoloader", AutoLoader);

Assuming we have preloaded this module (ideally using async method), we can add the <ce-autoloader></ce-autoloader> element to the document. This will immediately start the search process for all child elements that form the root element. By adding <ce-autoloader></ce-autoloader> to the corresponding container element, you can limit the scope of the lookup to the document's subtree, and even use multiple instances in different subtrees.

Next, we need to implement the discover method (as part of the above class): AutoLoader

discover(scope) {
  const candidates = [scope, ...scope.querySelectorAll("*")];
  for (const el of candidates) {
    const tag = el.localName;
    if (tag.includes("-") && !customElements.get(tag)) {
      this.load(tag);
    }
  }
}
This code checks for the root element and all its descendants (*). If the element is a custom element (a hyphenated label) but has not been upgraded yet, try to load the corresponding definition. This method may occupy a large amount of DOM query resources, so it needs to be handled with caution. We can reduce the load on the main thread by delaying execution:

connectedCallback() {
  const scope = this.parentNode;
  requestIdleCallback(() => {
    this.discover(scope);
  });
}

Not all browsers support it, you can use requestIdleCallback as a backup plan: requestAnimationFrame

const defer = window.requestIdleCallback || requestAnimationFrame;

class AutoLoader extends HTMLElement {
  connectedCallback() {
    const scope = this.parentNode;
    defer(() => {
      this.discover(scope);
    });
  }
  // ...
}
Now, we can implement the

method, dynamically inject the load elements: <script></script>

load(tag) {
  const el = document.createElement("script");
  const res = new Promise((resolve, reject) => {
    el.addEventListener("load", () => resolve(null));
    el.addEventListener("error", () => reject(new Error("未能找到自定义元素定义")));
  });
  el.src = this.elementURL(tag);
  document.head.appendChild(el);
  return res;
}

elementURL(tag) {
  return `${this.rootDir}/${tag}.js`;
}
Please note the hard-coded convention in

. The URL of the elementURL attribute assumes that there is a directory containing all custom element definitions (e.g. src<my-widget></my-widget>). We can adopt more complex strategies, but this is enough for our purposes. Delegate this URL to a separate method to allow project-specific subclassing if needed: /components/my-widget.js

class FancyLoader extends AutoLoader {
  elementURL(tag) {
    // 自定义逻辑
  }
}
Either way, we rely on

. This is the configurability mentioned earlier. Let's add a corresponding getter: this.rootDir

get rootDir() {
  const uri = this.getAttribute("root-dir");
  if (!uri) {
    throw new Error("无法自动加载自定义元素:缺少`root-dir`属性");
  }
  return uri.endsWith("/") ? uri.substring(0, uri.length - 1) : uri;
}
We don't need to use

because updating the observedAttributes property seems unnecessary at runtime. root-dir

Now, we can (and must) configure the element directory:

. <ce-autoloader root-dir="/components"></ce-autoloader>

With this, our autoloader works. But it only works for elements that already exist when the autoloader is initialized. We may also need to consider dynamically added elements. This is where

comes into play: MutationObserver

class AutoLoader extends HTMLElement {
  connectedCallback() {
    const scope = this.parentNode;
    this.discover(scope);
  }
}
customElements.define("ce-autoloader", AutoLoader);

In this way, the browser will notify us when a new element appears in the DOM (more precisely, our corresponding subtree), and we then use it to restart the lookup process.

Our autoloader is now fully usable. Future enhancements may include studying potential competition conditions and optimization. But for most scenarios, that's enough. If you have a different approach, please let me know in the comments and we can communicate with each other!

The above is the detailed content of An Approach to Lazy Loading Custom Elements. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn