Asset path in React DS output

Hi there,

I currently have a project using Stencil and the reactOutputTarget to create an icon component, similar to the way the Ionicons icon component works - loading in svg content at runtime from a static public/assets folder

I’m trying to import the generated React component in another project, which uses Next.js, but I’m finding that the getAssetPath() call to retrieve icon content keeps returning the current page’s path - e.g. localhost:3000/section/page returns its assetPath as /section/page, and the component attempts to fetch localhost:3000/section/page/assets/icon.svg, which is not going to work. It should be localhost:3000/assets/icon.svg.

I didn’t hit this problem in any local testing or setting the components up in Storybook, presumably because all those were loaded from the web server root. I can’t for the life of me work out how to use setAssetPath() - it’s not exported by any of my bundles (I’m not currently building dist-custom-elements-bundle which is the only place it gets mentioned in the docs). I’m importing my components-react package in the Next.js project, which exports only the component proxies as far as I can tell.

Where am I going wrong? How can I get assetPath to return '/', or (ideally) be configurable to the app consuming the react components library, in case the app’s public path needs to be different from mine?

stencil.config.js (edited)

export const config: Config = {
  namespace: 'my-components',
  taskQueue: 'async',
  outputTargets: [
    reactOutputTarget({
      componentCorePackage: 'my-components',
      proxiesFile: '../components-react/src/components.ts',
      includeDefineCustomElements: true,
    }),
    {
      type: 'dist',
      esmLoaderPath: '../loader',
    },
    {
      type: 'docs-readme',
      strict: true,
    },
    {
      type: 'www',
      serviceWorker: null, // disable service workers
    },
  ],
  plugins: [sass({})],
  globalStyle: './src/global/variables.scss',
};

icon-utils.tsx (edited)

import { getAssetPath } from '@stencil/core';

const iconCache = {};
const requestCache = {};

export async function fetchIcon({ icon }): Promise<IconData> {
  if (iconCache[icon]) {
    return iconCache[icon];
  }
  if (!requestCache[icon]) {
    requestCache[icon] = fetch(getAssetPath(`./assets/${icon}.json`))
      .then((resp) => resp.json())
      .catch(() => {
        console.error(`"${icon}" is not a valid icon name`);
        return {};
      });
  }

  const iconData = await requestCache[icon];
  iconCache[icon] = iconData;

  return iconData;
}

page.js (edited)

import { MyIcon } from 'my-components-react'; // is there a better way to import? this seems to pull in the whole component bundle

render() {
  return <MyIcon name="icon-name" />;
}

versions:
├─ @stencil/core@2.4.0
├─ @stencil/react-output-target@0.0.9
├─ @stencil/sass@1.4.1

UPDATE & SOLUTION

It looks like the assetPath defaults to the current url where no base path is set. This explains why my tests and storybook had worked (where an explicit base path was set). Next.js won’t allow you to set a redundant ‘/’ as your config’s basePath as that’s the default value, but instead I stuck the following inside pages/_document.js:

import Document, { Html, Head, Main, NextScript } from 'next/document';

class PageDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <base href="/" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default PageDocument;

getAssetPath() now returns the domain root, irrespective of the current page’s path. Might be worth documenting this somewhere in the asset handling section!