stencil
stencil copied to clipboard
Issue trying to use a component that ships with Assets
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
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.
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.
What is the intention of getAssetPath
if it doesn't map to the assets
directory that Stencil creates in the build process?
This is quite a pain point.
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.
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
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.
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 :)
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.
Yup I'm having the exact same issue with loading a svg from getAssetsPath. It just seems to be looking at app root
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.
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.
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?
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. 🤷♂
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?
I ended up embedding both images and fonts as base64, some problems may arise with CSP, but it gets the work done.
also having the same issue. I don't get the point of using getAssetPath function
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/"
}]
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
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.
Is there any workaround?
base64 embedding
Is there any workaround?
base64 embedding … Is there any workaround?
Ok, thank you!
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).
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
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.
This is not the case for the 'www' output target though.
I use "Copy" { type: 'www', copy: [ { src: 'assets', dest: 'build/assets' } -> This copy my assets folder in to build ], }
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 Exactly, i think that, this is a Stencil logic error, but we can use "Copy" while they solve it.