Skip to content

Exploration: HTML Module Imports and Exports #1059

@EisenbergEffect

Description

@EisenbergEffect

Background

Extensive discussion on Declarative Custom Elements has brought the CG to a point of realizing that a good starting point would be HTML Modules. HTML Modules would serve as a container for DCEs, but also other reusable HTML resources, such as templates.

This being the case, we need to focus in on exactly how exports AND imports would work. While the export side of HTML Modules has been explored previously, the import side is largely undefined.

Strawman

In order to get conversation going, I'll propose a few basic ideas here.

HTML Module Exports

I'll begin by submitting that there are five possible export types:

  • Templates
  • Fragments
  • Styles
  • Custom Elements
  • Element Registries

To export any one of these, they must have an id, which will become the symbol under which the export is provided, and they must have the export attribute. Elements with only an id can be referenced within the module, but are not able to be imported. Both id and export are required for that.

Here are a few examples.

<!-- Becomes an export named pricingCard of type HTMLTemplateElement -->
<template export id="pricingCard">
  <div class="pricing-card">
    <div class="header">
      <h4 class="name"></h4>
    </div>
    <div class="body">
      <h1 class="title"></h1>
      <ul class="features">
      </ul>
      <button type="button" class="subscribe-action"></button>
    </div>
  </div>
</template>

<!-- Becomes an export named sharedHeaderStyles of type CSSStyleSheet -->
<style export id="sharedHeaderStyles">
  .shared-header {
    display: flex;
    justify-content: center;
  }
</style>

<!-- Becomes an export named sharedHeader of type DocumentFragment -->
<header class="shared-header" export id="sharedHeader">
  <ul>
    <li><a href="./home">Home</a></li>
    <li><a href="./features">Features</a></li>
    <li><a href="./pricing">Pricing</a></li>
    <li><a href="./faq">FAQs</a></li>
    <li><a href="./about">About</a></li>
  </ul>
</header>

The above types exist today, but in the future we could also add <element> (or some other tag) for declarative custom elements and <registry> for a declarative custom element registry.

Importing HTML Modules in JS

Given the above example, we could import in JavaScript, using import assertions.

import { 
  pricingCard, 
  sharedHeaderStyles,
  sharedHeader
} from "./my-resources.html" with { type: "html" };

You should not be able to import the entire document, as that would break the encapsulation of the module. You should only be able to import elements with an id and an export attribute. We could also enable an element with an export and no id to be imported as the default export.

Importing HTML Modules in HTML

Importing into HTML involves several tasks:

  • Importing the resources
  • Declaratively using a DCE, template, or fragment
  • Defining a DCE

HTML already provides a way to declaratively use a custom element, but not a way to declaratively use a template or fragment. So, we'll need to add something new here.

We also have no way to declaratively define a custom element in a given HTML document or module.

First, let's tackle the issue of importing the resources from an HTML module. We could do that with an <import> element as such. Here are a few examples:

<import from="./my-resources.html#sharedHeader">
<import from="./my-resources.html#pricingCard" as="productCard">
<import from="./design-system.html#MyButton" as="ui-button">

The from attribute provides the path to the HTML module and also includes a fragment identifying the specific export. The as attribute provides a way to name the import within the current document or module. In the case of a template or fragment, this creates something with an id denoted by as. In the case of a custom element, it will call define with the value of as for the tag name.

To use an imported custom element, is no different than using any custom element:

<ui-button>Click Me!</ui-button>

Using a template or fragment is different. I propose adding a new element to handle this, named compose. It would look something like this:

<compose src="#sharedHeader"></compose>
<compose src="#productCard"></compose>

Note: We could enable the src attribute to both import and compose as a convenience. But I think importing and using are two different things and minimally need what is shown above.

Open Question: Could compose be a processing instruction or something else besides an element? Ideally, the composition should not affect the dom structure. It's really only a location where the composition should occur.

The above should all work when importing into documents or other HTML modules. One other scenario is worth mentioning: importing DCEs into a potential declarative custom element registry. That could look something like this:

<registry export id="my-registry">
  <import from="./my-element.html" as="your-element">
  <import from="./my-module.html#NamedElementTwo" as="element-two">
  <import from="./everything-from-another-registry.html">
  
  <element tag="an-inline-element"></element>
</registry>

And of course, you could import this entire registry into a document as follows:

<import from="./my-registry.html">

This would define all the contained elements in the current document, using their specified tag names. To change the names, we could add a mapping, similar to the way export parts work in shadow dom.

Feedback Request

Ok, that's a rough, initial sketch of how we might put some pieces together. Let's start the conversation around whether this meets all the needs, what are the semantics, etc.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions