Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue trying to use a component that ships with Assets #1868

Closed
diondree opened this issue Sep 16, 2019 · 53 comments
Closed

Issue trying to use a component that ships with Assets #1868

diondree opened this issue Sep 16, 2019 · 53 comments
Labels
ionitron: needs reproduction This PR or Issue does not have a reproduction case URL ionitron: stale issue This issue has not seen any activity for a long period of time

Comments

@diondree
Copy link

Stencil version:

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
@ionitron-bot ionitron-bot bot added the triage label Sep 16, 2019
@drdreo
Copy link

drdreo commented Sep 24, 2019

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.

@diondree
Copy link
Author

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.

@eshtadc
Copy link
Contributor

eshtadc commented Oct 17, 2019

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

@justjoeyuk
Copy link

This is quite a pain point.

@AnonymousArthur
Copy link

AnonymousArthur commented Nov 1, 2019

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.

@bison92
Copy link

bison92 commented Dec 5, 2019

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

@mboudreau
Copy link

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
Copy link

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 :)

@kevinclarkadstech
Copy link

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.

@mbulfair
Copy link

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

@mbulfair
Copy link

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.

@kevinclarkadstech
Copy link

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.

@mbulfair
Copy link

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?

@kevinclarkadstech
Copy link

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. 🤷‍♂

@mbulfair
Copy link

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?

@bison92
Copy link

bison92 commented Jan 31, 2020

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

@rrdlpl
Copy link

rrdlpl commented Feb 3, 2020

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

@rrdlpl
Copy link

rrdlpl commented Feb 3, 2020

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/"
}]

@mbulfair
Copy link

mbulfair commented Feb 3, 2020

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

@mergin
Copy link

mergin commented May 4, 2020

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.

@rzylber
Copy link

rzylber commented May 6, 2020

Is there any workaround?

@bison92
Copy link

bison92 commented May 8, 2020 via email

@rzylber
Copy link

rzylber commented May 9, 2020

base64 embedding

Is there any workaround?

Ok, thank you!

@nearz
Copy link

nearz commented May 13, 2020

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).

@bjolletz
Copy link

bjolletz commented May 14, 2020

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 #2269

@nearz
Copy link

nearz commented May 16, 2020

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
Copy link

nearz commented May 16, 2020

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

@SebasBaezCode
Copy link

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

@ionitron-bot ionitron-bot bot closed this as completed Feb 21, 2022
@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Feb 21, 2022
@rwaskiewicz rwaskiewicz removed the ionitron: stale issue This issue has not seen any activity for a long period of time label Feb 21, 2022
@ionic-team ionic-team unlocked this conversation Feb 21, 2022
@rwaskiewicz rwaskiewicz reopened this Feb 21, 2022
@rwaskiewicz
Copy link
Contributor

Hey ionitron-bot, we shouldn't be closing these issues. Bad bot! ❌ 🤖

Sorry about that folks, that shouldn't have happened.

@dgateles
Copy link

Any news about this issue?

@rwaskiewicz rwaskiewicz added Resolution: Needs Investigation This PR or Issue should be investigated from the Stencil team and removed Feature: Bundling labels Mar 25, 2022
@jonapgartwohat
Copy link

jonapgartwohat commented Jun 29, 2022

setAssetPath is exported from @my-org/my-lib/dist/components but it doesn't actually set the internal $resourceUrl$ for the actual bundle being loaded. Calling the exported function has no effect on the asset path in the loader.

@Loic57
Copy link

Loic57 commented Aug 31, 2022

Anyone has a fix ?

This is my component (simplified)

import { Component, Prop, h, Host, Element, getAssetPath } from '@stencil/core';

@Component({
  tag: 'xxx-icon',
  styleUrl: 'icon.scss',
  shadow: false,
  assetsDirs: ['icons']
})
export class XXXIcon {

  render() {

    const fileUrl = `./icons/icons.svg`;
    const iconUrl = `${getAssetPath(fileUrl)}#icon-${this.icon}`;

    return (
      <Host>
        <svg>
          <use href={iconUrl} xlinkHref={iconUrl} />
        </svg>
      </Host>
    );
  }
}

Stencil generates this path : /dist/xxx-components/icons/icons.svg#icon-chevron-right and this is great!

But when I'm in vuejs or react this is the path generated : /icons/icons.svg#icon-chevron-right and it doesn't work... I have to copy my dist/xxx-components/icons/icons.svg inside the public folder of vue or react to make it work.

I don't like the idea to go inside my node_modules folder to get assets and manually bring them inside react. Of course I could use a webpack copy plugin but I would prefer to have stencil linking the icons no matter the environment

@juanr941
Copy link

Is there a workaround for this?

@rwaskiewicz
Copy link
Contributor

Hey folks,

There's a lot of information here, but some of it is really hard to make actionable for myself and the team! I hear and understand the frustrations with the API - we've rewritten the entirety of our assets documentation already, but to move forward with fixing this API we need more information to understand the issue(s) present.

Specifically, we need whole, minimal reproductions that we can pull down and review. The reason being is the output target(s) being used and how the Stencil library is being using plays a large factor in this API's behavior.

Can anyone who is willing and able please create a new issue with a minimal reproduction case and add a link back to this issue? If you're creating a new issue, can you please look through this issue first to see if a new issue has been created with your same scenario?

Thanks!

@hccampos
Copy link

I'd just like to point out one more related problem that we're facing. It has to do with using multiple Stencil-based libraries in a single application, each with their own assets. The app uses the dist-custom-elements target of each of the component libraries and it uses a bundler (esbuild in our case) to produce a final app bundle. The problem we're seeing is that calling the setAssetPath function exported by any of the component libraries ends up setting the same global path internally, because the bundler deduplicates @stencil/core/internal/client.

To get around this, we're forced to have all the assets from all the component libraries in a single location and then, in the main app, set a single asset path which points to that common assets folder. This is brittle and risks name collisions, for example. I think Stencil should provide a way of specifying an isolated asset path for each component library built with the dist-custom-elements target.

import { setAssetPath as setAssetPathForLibA } from "component-lib-a";
import { setAssetPath as setAssetPathForLibB } from "component-lib-b";

setAssetPathForLibA("../path/to/assets/for/libA");
setAssetPathForLibA("../path/to/assets/for/libB");

@ismaelb87
Copy link

4 years later and the same problem still occurs

@rwaskiewicz
Copy link
Contributor

I'm going to be relabeling this issue, as it hasn't seen much activity in almost a year.

I'd like to reiterate my previous call-to-action:

Hey folks,

There's a lot of information here, but some of it is really hard to make actionable for myself and the team! I hear and understand the frustrations with the API - we've rewritten the entirety of our assets documentation already, but to move forward with fixing this API we need more information to understand the issue(s) present.

Specifically, we need whole, minimal reproductions that we can pull down and review. The reason being is the output target(s) being used and how the Stencil library is being using plays a large factor in this API's behavior.

Can anyone who is willing and able please create a new issue with a minimal reproduction case and add a link back to this issue? If you're creating a new issue, can you please look through this issue first to see if a new issue has been created with your same scenario?

Thanks!

@rwaskiewicz rwaskiewicz added ionitron: needs reproduction This PR or Issue does not have a reproduction case URL and removed Request For Comments Seeking commentary on an issue or PR from the community Resolution: Needs Investigation This PR or Issue should be investigated from the Stencil team labels Aug 22, 2023
@ionitron-bot
Copy link

ionitron-bot bot commented Aug 22, 2023

Thanks for the issue! This issue has been labeled as needs reproduction. This label is added to issues that need a code reproduction.

Please reproduce this issue in an Stencil starter component library and provide a way for us to access it (GitHub repo, StackBlitz, etc). Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed.

If you have already provided a code snippet and are seeing this message, it is likely that the code snippet was not enough for our team to reproduce the issue.

For a guide on how to create a good reproduction, see our Contributing Guide.

@ionitron-bot ionitron-bot bot added the ionitron: stale issue This issue has not seen any activity for a long period of time label Sep 21, 2023
@ionitron-bot
Copy link

ionitron-bot bot commented Sep 21, 2023

Thanks for the issue! This issue is being closed due to inactivity. If this is still an issue with the latest version of Stencil, please create a new issue and ensure the template is fully filled out.

Thank you for using Stencil!

@ionitron-bot ionitron-bot bot closed this as completed Sep 21, 2023
@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Sep 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
ionitron: needs reproduction This PR or Issue does not have a reproduction case URL ionitron: stale issue This issue has not seen any activity for a long period of time
Projects
None yet
Development

No branches or pull requests