stencil icon indicating copy to clipboard operation
stencil copied to clipboard

Issue trying to use a component that ships with Assets

Open diondree opened this issue 5 years ago • 48 comments

Stencil version:

@stencil/[email protected]

I'm submitting a: [x] bug report [ ] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/

Current behavior: I've created a component that essentially spits out an svg when used. So this component works well when using the test server but when the component has been built and published, it is unable to find the assets.

Expected behavior: The expected behaviour is that the component should be able to output the icon the same way it does locally

Steps to reproduce: Import library within Angular and try to utilize web component.

<test-icon icon="arrow-down"></test-icon>

Related code: Component

import {
  Component,
  Host,
  h,
  State,
  Prop,
  Watch,
  getAssetPath,
} from '@stencil/core';
import { getSvgContent } from './util';

@Component({
  tag: 'test-icon',
  styleUrl: 'icon.css',
  assetsDir: 'svg',
  shadow: true,
})
export class SmartIcons {
  @Prop() icon: string;
  @State() private svgContent?: string;

  connectedCallback() {
    this.loadIcon();
  }

  @Watch('icon')
  loadIcon() {
    const svg = `svg/test-${this.icon}.svg`;
    const url = getAssetPath(svg);
    if (this.icon) {
      getSvgContent(url).then((content) => {
        this.svgContent = content;
      });
    }
  }

  render() {
    return (
      <Host role="img">
        {this.svgContent ? (
          <div innerHTML={this.svgContent}></div>
        ) : (
          <div></div>
        )}
      </Host>
    );
  }
}

Other information:

zone-evergreen.js:1042 GET http://localhost:4200/svg/smart-arrow-down.svg 404 (Not Found)
(anonymous) @ zone-evergreen.js:1042
getSvgContent @ smart-button_2.entry.js:18
loadIcon @ smart-button_2.entry.js:42
connectedCallback @ smart-button_2.entry.js:36
safeCall @ core-7cea914b.js:853
fireConnectedCallback @ core-7cea914b.js:1018
initializeComponent @ core-7cea914b.js:999
async function (async)
initializeComponent @ core-7cea914b.js:965
(anonymous) @ core-7cea914b.js:1044
invoke @ zone-evergreen.js:359
run @ zone-evergreen.js:124
(anonymous) @ zone-evergreen.js:855
invokeTask @ zone-evergreen.js:391
runTask @ zone-evergreen.js:168
drainMicroTaskQueue @ zone-evergreen.js:559
Promise.then (async)
scheduleMicroTask @ zone-evergreen.js:542
scheduleTask @ zone-evergreen.js:381
scheduleTask @ zone-evergreen.js:211
scheduleMicroTask @ zone-evergreen.js:231
scheduleResolveOrReject @ zone-evergreen.js:845
then @ zone-evergreen.js:955
bootstrapModule @ core.js:40599
./src/main.ts @ main.ts:14
__webpack_require__ @ bootstrap:84
0 @ main.ts:16
__webpack_require__ @ bootstrap:84
checkDeferredModules @ bootstrap:45
webpackJsonpCallback @ bootstrap:32
(anonymous) @ main.js:1

diondree avatar Sep 16 '19 15:09 diondree

Well, this is due to you can't actually know where the file will be hosted when on production during development. We have the same problem because our assets have cache busting hashes applied to their names, so they differ from development. And also the dev environment doesn't need to be equal to the production one.

So you either slot the images or pass the image path as a property. You can also adopt your server and serve those image request correctly. (Your component is looking for the file on root/svg..) Easiest way is to host assets on an unique and consistent path, like a CDN, and use that.

drdreo avatar Sep 24 '19 08:09 drdreo

Hmm I think I understand. I guess this is why it works when you include the library via a script tag vs as an npm module.

diondree avatar Sep 24 '19 13:09 diondree

What is the intention of getAssetPath if it doesn't map to the assets directory that Stencil creates in the build process?

eshtadc avatar Oct 17 '19 16:10 eshtadc

This is quite a pain point.

justjoeyuk avatar Oct 30 '19 15:10 justjoeyuk

Yea, stencil could be killed because of this problem. Because the whole point of using stencil is to decouple components to applications, if the components' assets still need applications to provide, I could say that the component isn't fully wrapped.

AnonymousArthur avatar Nov 01 '19 09:11 AnonymousArthur

I wonder why if this is the real thing (you can't serve publicly from inside node_modules, nor you can't copy on runtime to a public folder), it should be clearly explicited here https://stenciljs.com/docs/local-assets

bison92 avatar Dec 05 '19 11:12 bison92

I have this exact issue. I'm packaging my assets with my components using a copy task into the correct directory, but I wanted an easy way to reference it from the component since the component library will be included from the app using a script tag.

There is a workaround: add data-resource-url attribute with the value you want your resources to point to (same path as to where you have your javascript, minus the javascript part) onto the script tag(s) that points to your component library javascript to set the base url for the assets. Example:

<script type="module" src="https://some.cdn.com/some-web-components.esm.js" data-resource-url="https://some.cdn.com/" defer=""></script>
<script src="https://some.cdn.com/some-web-components.js" data-resource-url="https://some.cdn.com/" defer=""></script>

What I don't understand is why stencil doesn't make this the default behavior? This should be how all component libraries work except for some key exceptions where they can specify otherwise. Furthermore, Stencil is already doing all of the hard work in the patchBrowser function when the library starts but just doesn't set resourceUrl unless it's specified in the script attribute. This is super weird to me.

This should be the rule, not the exception.

mboudreau avatar Dec 12 '19 02:12 mboudreau

Also, to add to this, I didn't like the idea that every. single. addition. of our component library needs to make sure that our developers knew to set the data-resource-url on the script tags. So, I made a convenience method within the library to get around this. Example is below.

I hope that the Stencil actually fixes this eventually so that I can remove this code once and for all.

export const namespace = 'your-component-namespace'; // this should be imported into your stencil.config.ts file and used as the namespace for your project

let currentScriptUrl: string;

export function getCurrentScriptUrl():string {
    if (!currentScriptUrl) {
        const script = Array.from(document.querySelectorAll('script'))
            .find(s => new RegExp(`${namespace}(\.esm)?\.js$`).test(s.src));
        currentScriptUrl = script && script.src || document.baseURI;
    }
    return currentScriptUrl;
}

export function getURL(path: string): string {
    return new URL(path, getCurrentScriptUrl()).href;
}

Then anywhere in your app, you can do something like <img src={getUrl('./path-to-icons/icon1.svg}/> which should resolve to wherever you're deploying your javascript to :)

mboudreau avatar Dec 12 '19 02:12 mboudreau

I followed this tutorial https://stenciljs.com/docs/local-assets and instead of looking in the www folder, it is looking in the build folder for some reason. http://localhost:3333/build/assets/img/angry.png and I get a 404.

kevinclarkadstech avatar Jan 05 '20 23:01 kevinclarkadstech

Yup I'm having the exact same issue with loading a svg from getAssetsPath. It just seems to be looking at app root

mbulfair avatar Jan 24 '20 21:01 mbulfair

It seems this function doesn't do what it's described to do, You can't relatively import a file in a component, and use it as a NPM module on an application. Really wish someone from stencil would help figure out this issue.

mbulfair avatar Jan 24 '20 23:01 mbulfair

So, strangely, it works if you have a leading slash only:

img src="/assets/img/profile.png"

No issues whatsoever for me now. But I don't remember it being very clear in the docs.

kevinclarkadstech avatar Jan 25 '20 02:01 kevinclarkadstech

So, strangely, it works if you have a leading slash only:

img src="/assets/img/profile.png"

No issues whatsoever for me now. But I don't remember it being very clear in the docs.

Are you saying if you use a / in the getAssetsPath it will work when referenced from a npm module?

mbulfair avatar Jan 25 '20 02:01 mbulfair

So, strangely, it works if you have a leading slash only: img src="/assets/img/profile.png" No issues whatsoever for me now. But I don't remember it being very clear in the docs.

Are you saying if you use a / in the getAssetsPath it will work when referenced from a npm module?

Hmm, I am not sure. I just meant that I was having weird behavior with setting the src of images within my Stencil app. If a component was in a deeper folder, then src="./assets/img/profile.png" would not work, even if to me that makes sense as assets is at the root. But if I used "../assets/img/profile.png" in this component in a subfolder then the image would show. But once I changed all of them to "/assets/img/{whatever}" then they all worked, even without the getAssetsPath. 🤷‍♂

kevinclarkadstech avatar Jan 25 '20 05:01 kevinclarkadstech

I guess my question is just, if I have a component that includes an asset, and it's in the dist. Someone who installs the component, and used it, that reference would be the node_module? or what? How can I safely include assets and package them up?

mbulfair avatar Jan 25 '20 19:01 mbulfair

I ended up embedding both images and fonts as base64, some problems may arise with CSP, but it gets the work done.

bison92 avatar Jan 31 '20 09:01 bison92

also having the same issue. I don't get the point of using getAssetPath function

rrdlpl avatar Feb 03 '20 12:02 rrdlpl

I found this way to get the assets of my component into the app where I used just by adding this config in my angular.json file. It might be similar to other frameworks:

...
assets: [ {
"glob": "**/*", 
"input": "./node_modules/my-component/path/to/assets/",
"output": "/assets/"
}]

rrdlpl avatar Feb 03 '20 12:02 rrdlpl

I found this way to get the assets of my component into the app where I used just by adding this config in my angular.json file. It might be similar to other frameworks:

...
assets: [ {
"glob": "**/*", 
"input": "./node_modules/my-component/path/to/assets/",
"output": "/assets/"
}]

So while this worked and was my solution as well for a library you're trying to distribute this just copies from node modules to their local app. You'd have to do this solution for react and other frameworks too. I wish stencil had a lazy loading dynamic import

mbulfair avatar Feb 03 '20 12:02 mbulfair

There is no update on this from the Ionic/Stencil team?

Is it posible to encode the image to base64 on compile time? So that what the base64 string is included and wrapped inside the web component and doesn't rely on a URL.

It would be very useful for a small static asset like a logo.

mergin avatar May 04 '20 16:05 mergin

Is there any workaround?

rzylber avatar May 06 '20 16:05 rzylber

base64 embedding

Is there any workaround?

bison92 avatar May 08 '20 19:05 bison92

base64 embedding Is there any workaround?

Ok, thank you!

rzylber avatar May 09 '20 13:05 rzylber

I followed the documentation on https://stenciljs.com/docs/local-assets as well and was having issues. Finally I decide to try and restart the dev server with the code unchanged from how the documentation specifies it to be, and the asset loaded to my component. The assets directory show in both my output targets 'dist' and 'www'(build directory).

nearz avatar May 13 '20 17:05 nearz

Yes the assets directory is included in www and dist, but are you able to get it to work if you include the component in an external application? The problem for me is that getAssetPath is then not returning the correct path, which I described in https://github.com/ionic-team/stencil/issues/2269

bjolletz avatar May 14 '20 09:05 bjolletz

Yes it is working. If I copy the dist folder to another location, and reference the ./dist/"namespace"/"namespace".esm.js file in a script tag in the head of my html. I have no issues using the component along with the svg asset that I have in the component.

nearz avatar May 16 '20 14:05 nearz

This is not the case for the 'www' output target though.

nearz avatar May 16 '20 14:05 nearz

I use "Copy" { type: 'www', copy: [ { src: 'assets', dest: 'build/assets' } -> This copy my assets folder in to build ], }

SebasBaezCode avatar Jun 04 '20 22:06 SebasBaezCode

Ok. But as far as I understand, the point is that you should not have to hardcode the path to the assets (which is also hard, since it seems to vary between www and dist builds), and you should also not have to do any copy actions of your own. Instead, assets are supposed to be copied automatically and then you should be able to use getAssetPath to get the path of wherever assets were copied.

bjolletz avatar Jun 05 '20 08:06 bjolletz

@bjolletz Exactly, i think that, this is a Stencil logic error, but we can use "Copy" while they solve it.

SebasBaezCode avatar Jun 05 '20 18:06 SebasBaezCode