19

I am creating a web component using native implementation, which in it's html template has links to images. However, those links only work if they are absolute, or relative to the main document, which means, that that component is not reusable, or portable. Also, it is very counterintuitive.

Currently, I add a data-url_prefix attribute to all elements that need to use images. Then, when creating a shadow root for my custom element, I replace a {{URL_PREFIX}} with the value of that argument.

My solution seems very bad. I would be very glad if you advised something better, thanks.


I found an interesting quote on the http://webcomponents.org/polyfills/html-imports/ page:

POLYFILL NOTES

In imported documents, href and src attributes in HTML, and url properties in CSS files, are relative to the location of the imported document, not the main document.

why would a polifill use different logic that the native implementation?


Web Components Ideally should encapsulate all their dependencies, but if a web component requires an image, it should know the absolute URL to that image, which does not allow the component to be simply moved around in file structure.

Say, for example I have the following structure:

  • index.html
  • css
    • main.css
  • js
    • main.js
  • web_components
    • cool_web_component
      • cool_web_component.html
      • icon.png

If I change it to the following:

  • index.html
  • css
    • main.css
  • js
    • main.js
  • cool_web_component
    • cool_web_component.html
    • icon.png

I would be required to change the pointer to icon.png somewhere in those files My question is how to avoid it, or solve it in elegant way. Also, why the actual native implementation is in conflict with the polyfills?

Vlas Bashynskyi
  • 1,886
  • 2
  • 16
  • 25
  • can you be more explain ? you mean if have structure root->some_folder->image and your php is root->athor_folder->php_file.php you want add in php_file.php a img tag to refer a image from image folder ? – Laurentiu Nov 03 '15 at 18:20
  • 1
    @Laurentiu, check out my edit, I hope it clarifies your not understanding. – Vlas Bashynskyi Nov 03 '15 at 18:35
  • you could use a image folder like your js, and all time your absolut path is same, this mean in your html you will never need to use relative path and your component is portable. Probabily POLYFILL do some thing like rewrite css in symfony, in symfony too use relative path to your image and after assets symfony convert it in abolute path to your root folder. I'm not expert in POLYFILL but hope this will be helpful. – Laurentiu Nov 03 '15 at 18:45

3 Answers3

18

The webcomponent specification defines that URLs are always relative to the main document. This, of course, breaks web component encapsulation, as you rightly concluded. In other words, the specification is buggy, and lots of people are complaining about it. That's why the polyfill doesn't follow the specification: it's fixing the problem.

The specification will change. However, since this is not trivial to fix, it could still take some time. Please see these links:

https://lists.w3.org/Archives/Public/public-webapps/2014OctDec/0013.html

https://www.w3.org/Bugs/Public/show_bug.cgi?id=20976#c8

For the moment, the solution is for your component to change the URL of its template images, from relative to absolute. You can get the templateBaseUrl as follows:

(function (window, document)
    {
    var proto = Object.create(HTMLElement.prototype);

    var template = document.currentScript.ownerDocument.querySelector("template");
    var templateBaseUrl = template.baseURI;

    proto.createdCallback = function ()
        {
        // Now find all images inside the template and change their src attribute, 
        // using templateBaseUrl. Also you must change CSS background-image: url(...);
        ...
        };

    document.registerElement('test-element', {prototype: proto});
    })(window, document);

Another way of fixing this, in case of small images like icons, is to embed the image data directly into the document, with data URIs. This also saves HTTP Requests (until we have Http/2). For example:

<img src="data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" />
Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
  • The polyfill also makes any XHR inside the loaded js files untouched, which makes the behaviour weird. – Ali Naci Erdem Nov 03 '16 at 08:34
  • 1
    While this solution (retrieving the current script's base URL and building the URL to the rest of WebComponent's resources) is the most correct one (after the native support for relative paths, of course) it is NOT WORKING in case where component's script itself is ES6 module, and then the hardcoded absolute paths are inescapable, portability impossible etc. – GullerYA Mar 16 '18 at 19:00
  • 1
    I just got bitten by this issue. It's now 2022 and apparently it is still not fixed :-/ – ackh Aug 14 '22 at 17:09
  • I got around this by making my components store `baseURL` in the `shadowRoot`. Since they are es6 modules, you can get that value from `import.meta.url`. Also stored that value in a css variable `--baseURL` in `:host` to prefix urls within css. Eventually I made a base element that automates the conversion as you do, using this ideas. – tomyo May 06 '23 at 02:50
1

This behavior seems to be specific to images.
For script and link tags, relative paths from imported document work as expected.

Also I noticed that this is not something specific to polyfills, even for native implementation(chrome) this issue(?) seems to be there.

Seems like only option here is to include a script in your imported html, which will convert these relative paths to their absolute counterparts.
To solve it elegantly you can avoid hardcoding urls in your script and generate it using url of your importing document. You can get that from document.currentScript.ownerDocument(or document._currentScript.ownerDocument in polyfilled scenario).

So to answer your second question, I don't see any difference in native and polyfilled implementation at least in terms of behavior of src and href attributes.
Quote you mentioned from http://webcomponents.org/polyfills/html-imports/ seems to be specific to href/src of script and link tags and they work as written.

Hope it helps.

Abhinav
  • 1,346
  • 12
  • 25
1

Reacting on my own prev comment to MarcG answer - the need for the modules to be aware of their own path has become acknowledged and nowadays may be retrieved by the import.meta.url mean!

So it is much easier to write now some util to fetch component's HTML/CSS.

Furthermore, if CSS is NOT fetched, but a new style sheet link dynamically added to the document - this CSS plays very well with relative paths furthermore.

Having that, today, while waiting for some standard way to specify my component's base url, my ow approach is to provide a component with 2 resources:

  • main/single HTML is fetched via fetch API, turned into template, cached and then cloned into the component each time new instance of it attached to the DOM
  • main CSS is linked by the standard link element
  • names of those 2 are the same as the main JS file providing component's class - making it easier to turn this whole mess into a dozen-of-lines-util
  • additional CSS resources, if needed, added as a regular relative imports in the main CSS
  • images, if possible, added as url backgrounds using relative imports as well
  • no more than singe HTML resource obviously
GullerYA
  • 1,320
  • 14
  • 27