简体   繁体   中英

Single HTTP request for HTML template file for multiple instances of the same Web Component

Say I have a Web Component defined like this:

// web-component.js
export class WebComponent extends HTMLElement {
  template = '/path/to/template.html';  
  tmpl = {};

  constructor() {
    super();
  }
    
  async connectedCallback() {
    const html = fetch(this.template).then(response => response.text());
    this.doSomething(await html);
  }
  
  doSomething(html) {
    console.log(html);
  }
}

document.addEventListener('DOMContentLoaded', customElements.define('web-component', WebComponent));

A template file like this:

//template.html
<template id="web-component">
    <h1>Text Goes Here</h1>
</template>

And a web page like this:

//index.html
    ....
    <head>
    <script type="module" src="/path/to/web-component.js"></script>
    </head>
    <body>
    <web-component>Foo</web-component>
    <web-component>Bar</web-component>
    <web-component>Baz</web-component>
    </body>
    ....

The web browser is making three http requests to fetch the same template file. I want to store the html from the template file somewhere on the client so I'm only making a single http request.

I tried this:

async connectedCallback() {
  const existing_template = document.body.querySelector('#web-component');
  console.log(existing_template);
  if (existing_template) {
    this.doSomething(existing_template);
  } else {
    const html = fetch(this.template).then(response => response.text());
    this.addTemplateToWebPage(await html);
    this.doSomething(await html);
}

addTemplateToWebPage(html) {
    const tag = document.createElement('body');
    tag.innerHTML = html;
    document.body.appendChild(tag.querySelector('template'));
}

But existing_template is always null, so I'm still making unnecessary http requests. I also tried this, with the same result:

connectedCallback() {
    this.doSomething();
  }

  async doSomething() {
    const existing_template = document.body.querySelector('#web-component');
    console.log(existing_template);
    if (existing_template) {
      this.doSomethingElse(existing_template);
    } else {
      const html = fetch(this.template).then(response => response.text());
      this.addTemplateToWebPage(await html);
      this.doSomethingElse(await html);
    }
  }

  doSomethingElse(html) {
    console.log('doing something else');
  }

How can I do this so I only have a single http request when calling the same template?

What you're doing to the HTML isn't clear, but an easy way is to create a custom fetch wrapper for your HTML template. Something like this:

const fetchTemplate = (() => {
  const cache = new Map();
  return async (path, options) => {
    if(cache.has(path)) return cache.get(path);
    const res = await fetch(path, options);
    const data = await res.text();
    cache.set(path, data);
    return data;
  };
})();

This presents a much more streamlined approach and can be easily adapted for other templates.

Usage:

async connectedCallback() {
  const html = await fetchTemplate(this.template);
  this.doSomething(html);
}

Since the original question refers to an "HTML" template, I'm not sure if, technically, this really answers it, but it has been a few days so I will relate how I solved my problem. Basically I just changed template.html to template.js, turned the template html code into a javascript literal, and export/imported it into my class. I guess the browser automatically caches the http request because it's only making a single http call.I didn't transpile or use any build tools... it just worked in the most recent versions of Firefox, Chrome and Edge (which is all I'm concerned about). It has been awhile since I have played with this stuff and I'm kinda blown away at the current browser support for vanilla javasript modules. When we finally get HTML imports that play nice with javascript modules, I'm hoping this approach makes it easy to refactor the code to accommodate the new technology.

My file structure is:

/www
- index.html
- /components
-- /my-component
--- component.js
--- template.js

index.html

<html>
    <head>
        <script type="module" src="/components/my-component/component.js">  </script>
    </head>
    <body>
        <my-component>One</my-component>
        <my-component>Two</my-component>
        <my-component>Three</my-component>
    </body>
</html>

template.js

export const template = `
<template id="my-template">
    <style>
        h1 { color: dimgray }
    </style>

    <div id="wrapper">
        <h1>Need Text</h1>
    </div>
</template>
`;

component.js

import { template } from './template.js';
export class MyComponent extends HTMLElement {
  shadow = {};
  tmpl = {};
  
  constructor() {
    super();
    this.shadow = this.attachShadow({mode:'open'});
   }
   
   connectedCallback() {
    this.setVars();
    this.build();
    this.render();
   }
   
   setVars() {
    const tmpl = this.setTemplate(template);
    this.tmpl = tmpl.querySelector('template').content;
   }
   
   setTemplate(html) {
     const range = document.createRange();
     return range.createContextualFragment(html);
   }
   
   build() {
    // do something 
   }
   
   render() {
    this.innerHTML = "";
    this.shadow.append(this.tmpl);
   }
  }
   
  document.addEventListener('DOMContentLoaded', () => customElements.define('my-component', MyComponent));
  • Add a static class property, initialized as FALSE
  • In the class constructor, check the value of the static property and, if needed, fetch the template file.
  • In the constructor, set the value of the static property with the result of the fetch.
  • Flag the connectedCallback method as async and use AWAIT to retrieve the static class property.
  • Clone the template to use in the instance(s).

my-component.js

class MyComponent extends HTMLElement {
  static file = '/path/to/template.html';
  static template = false;

  constructor() {
    super();
    if (!MyComponent.template) {
      MyComponent.template = MyComponent.fetchTemplate();
    }
  }

  async connectedCallback() {
    const tmpl = await MyComponent.template;
    this.tmpl = tmpl.cloneNode(true);
    console.log(this.tmpl);
  }

  static async fetchTemplate() {
    return await fetch (MyComponent.file)
    .then (response => response.text())
    .then (txt => {
      const frag = document.createRange().createContextualFragment(txt);
      return frag.querySelector('template').content;
    });
  }
}

template.html

<template id="my-component">
    <h1>Text Goes Here</h1>
</template>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM