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

An Approach to Lazy Loading Custom Elements

Joseph Gordon-Levitt
Release: 2025-03-09 11:39:10
Original
216 people have browsed it

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);
Copy after login
Copy after login

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);
    }
  }
}
Copy after login
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);
  });
}
Copy after login

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);
    });
  }
  // ...
}
Copy after login
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`;
}
Copy after login
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) {
    // 自定义逻辑
  }
}
Copy after login
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;
}
Copy after login
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);
Copy after login
Copy after login

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 of this Website
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
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template