Search code examples
reactjstypescriptwebpackstenciljs

StencilJS error with React output target - You may need an additional loader to handle the result of these loaders


I'm creating some basic elements in Stencil for a custom design system. I created some basic components, which work fine on their own as custom elements, but throw errors when used as React components.

I generated the React components via Stencil by includng the @stencil/react-output-target in stencil.config.ts.

reactOutputTarget({
      componentCorePackage: '@sr-design-system/simple-stencil-demo',
      proxiesFile: './react/src/components/index.ts',
      includeImportCustomElements: true
    }),

I then uploaded all of the components (custom elements & React) to a private npm package and installed them in a seperate project. The custom elements seem to work fine, but with the React elements I get the following error.

ERROR in ./node_modules/@sr-design-system/simple-stencil-demo/react/src/index.ts 6:12
Module parse failed: Unexpected token (6:12)
File was processed with these loaders:
 * ./node_modules/source-map-loader/dist/cjs.js
You may need an additional loader to handle the result of these loaders.
| import { createReactComponent } from './react-component-lib';
|
> import type { JSX } from '@sr-design-system/simple-stencil-demo/';
|
| import { defineCustomElement as defineSrText } from '@sr-design-system/simple-stencil-demo/dist/components/sr-text';
 @ ./src/App.jsx 7:0-73
 @ ./src/index.jsx 7:0-24 12:33-36

webpack 5.65.0 compiled with 1 error and 1 warning in 63 ms

I've been stuck on this issue for days now. Any idea what the solution could be?

===tsconfig.json===

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "allowUnreachableCode": false,
    "declaration": true,
    "experimentalDecorators": true,
    "forceConsistentCasingInFileNames": true,
    "lib": ["dom", "es2017"],
    "module": "es2015",
    "moduleResolution": "node",
    "pretty": true,
    "removeComments": false,
    "strictPropertyInitialization": false,
    "target": "es2017",
    "baseUrl": ".",
    "paths": {
      "@srds/react": ["./react"]
    },
    "jsx": "react",
    "jsxFactory": "h"
  },
  "include": ["src"]
}

===stencil.config.ts===

import { Config } from '@stencil/core';
import { reactOutputTarget } from '@stencil/react-output-target';

export const config: Config = {
  namespace: 'simple-stencil-demo',
  bundles: [{ components: ['sr-text'] }, { components: ['text-demo'] }],
  outputTargets: [
    reactOutputTarget({
      componentCorePackage: '@sr-design-system/simple-stencil-demo',
      proxiesFile: './react/src/components/index.ts',
      includeImportCustomElements: true,
    }),
    {
      type: 'dist',
      esmLoaderPath: './loader',
    },
    {
      type: 'dist-custom-elements',
    },
    {
      type: 'docs-readme',
    },
    {
      type: 'www',
      serviceWorker: null, // disable service workers
    },
  ],
  buildEs5: 'prod',
};

Solution

  • I figured out what the issue. For some reason, the dist folder was not being generated for me every time I ran npm run build.

    Sometimes it was generated, other times it wasn't. I believe it was due to some errors in my component code, which failed silently. So now I check for the dist folder every time I build the library.

    In my final, working attempt I went with the monorepo approach as advised by the Stencil team in their documentation.

    Here are all I took the steps for a basic Stencil library with a React output:

    • Create a monorepo
    • Create a Stencil Library
      • Generate components using npx stencil generate
      • Update name in package.json to MY_LIBRARY
      • npm i @stencil/react-output-target
      • Add the React Wrapper function to stencil.config.ts
      react({
         componentCorePackage: 'MY_LIBRARY',
         proxiesFile: '../MY_REACT_LIBRARY/src/components/stencil-generated/index.ts',
         includeDefineCustomElements: true,
       }),
      
    • Move back to root level of monorepo
    • Create a React library
    • In the Stencil Library
      • Run npm run build
      • Check if dist folder contains all subfolders (cjs, collection, components, esm, types, web-components, index.cjs.js, index.js)
      • Run npm link to generate a global symlink
    • In the React Library
      • Run npm link MY_LIBRARY
      • Run npm i
      • Run npm run build (Not sure if this step is required as it is not documented, but I did it anyway)
      • Run npm link
    • Move back to root level of monorepo
    • Create a React demo
      • npx create-react-app MY_REACT_DEMO --template typescript
      • npm link MY_REACT_LIBRARY
      • Import a component from the library and use it in App.tsx
      • npm run start

    When I confirmed everything worked fine, I added a basic lerna.jsonconfig for npm package management. Using this config, Lerna will automatically handle symver for our packages.

    {
      "version": "independent",
      "npmClient": "npm",
      "command": {
        "publish": {
          "allowBranch": ["master", "react-generation"],
          "ignoreChanges": ["*.md", "build.js", "config.json"],
          "message": "(auto) Lerna publish",
          "registry": "URL_TO_MY_PACKAGE_REGISTRY"
        },
        "bootstrap": {
          "ignore": "component-*",
          "npmClientArgs": ["--no-package-lock"]
        }
      },
      "packages": ["MY_LIBRARY", "MY_REACT_LIBRARY"]
    }
    
    

    After configuring Lerna, I published using the command npx lerna publish, following their publishing wizard.

    When it's published the package can be installed in any React project using npm i MY_REACT_LIBRARY and it should work.