weiran-zsd / dts-cli

Zero-config CLI for TypeScript package development
MIT License
440 stars 23 forks source link

Error "Element type is invalid" in target project when referencing some external library #209

Closed RockyStrongo closed 7 months ago

RockyStrongo commented 7 months ago

Current Behavior

For some components, I get the error below when using my library built with dts-cli from a react project (react 18.2.0, NextJS 13.5.5)

Error Message

Unhandled Runtime Error
"Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

Check the render method of `Select`."

I suspect the issue is occuring when the component is using packages that are not ES modules.

For example, a component using react-select will generate this error, while a component using @headlessui/react works fine.

I noticed that @headlessui/react includes "type"="module" in its package.json while react-select does not.

When copying the component code directly in the target project or testing it from storybook, it works as expected. So I would guess the issue is related to bundling - packaging.

Example of component not working

import React from 'react';
import ReactSelect, { SingleValue } from 'react-select'
import { InputLabel } from './InputLabel'
import { ValidationError } from './ValidationError';

//using the library React Select https://react-select.com/home

type MyProps = {
  label?: string,
  id: string,
  data: Array<{ value: string, label: string }>,
  value: { value: string, label: string },
  required?: boolean
  errorVisible?: boolean,
  errorText?: string,
  placeholder?: string,
  onChange?: (newValue: SingleValue<{
    value: string;
    label: string;
  }>) => void,
}

export function Select({ id, label, data, required = false, errorVisible = false, errorText, placeholder = "Sélectionner", onChange, value }: MyProps) {

  return (
    <div className='mb-5'>
      <InputLabel labelFor={id} required={required} label={label} ></InputLabel>
      <div className="sm:w-72 w-full">

        <ReactSelect
          aria-label={label}
          placeholder={placeholder}
          id={id}
          name="foo"//needed for required prop to work !
          required={required}
          unstyled
          options={data}
          value={value}
          classNames={{
            control: (state) =>
              `${state.menuIsOpen ? 'rounded-b-none ' : ''
              } ${state.isFocused ? 'border-primary outline-none ring-2 ring-white ring-opacity-75 ring-offset-2 ring-offset-primary' : ''
              } h-10 font-content relative sm:w-72 w-full cursor-default rounded-lg bg-white py-2 pl-3 pr-3 text-left shadow-md sm:text-sm`,

            menu: () =>
              "font-content relative sm:w-72 w-full cursor-default rounded-b-lg bg-white text-left shadow-md focus:outline-none focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:text-sm",

            option: (state) =>
              `${state.isFocused ? 'bg-primary text-secondary ' : ''
              } py-2 pl-3 pr-3`,

            dropdownIndicator: () => "text-primary"

          }}
          defaultValue={null}
          onChange={onChange}

        ></ReactSelect>
        <ValidationError visible={errorVisible} text={errorText}></ValidationError>

      </div>
    </div>

  )
}

Expected behavior

The component is usable from a react project when imported from the library built with dts.

Suggested solution(s)

Additional context

Library built and published via gitlabCI.

I tried adding a .babelrc file as below, it did not solve the issue :

{
 "presets": ["@babel/preset-env", "@babel/preset-react"]
}

the component is exported as below in the entry point file index.tsx :

export * from './components/Select';

Your environment

dts-cli library :

    OS: Linux 5.15 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish)
    CPU: (12) x64 12th Gen Intel(R) Core(TM) i7-1255U
    Memory: 4.23 GB / 7.61 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 20.9.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /mnt/c/Users/simon/AppData/Roaming/npm/yarn
    npm: 10.1.0 - /usr/local/bin/npm
    pnpm: 8.12.1 - ~/.local/share/pnpm/pnpm
  Browsers:
    Chrome: 114.0.5735.133
  npmPackages:
    dts-cli: ^2.0.4 => 2.0.4 
    typescript: ^5.0.4 => 5.3.3

target project :

  System:
    OS: Linux 5.15 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish)
    CPU: (12) x64 12th Gen Intel(R) Core(TM) i7-1255U
    Memory: 4.20 GB / 7.61 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 20.9.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /mnt/c/Users/simon/AppData/Roaming/npm/yarn
    npm: 10.1.0 - /usr/local/bin/npm
    pnpm: 8.12.1 - ~/.local/share/pnpm/pnpm
  Browsers:
    Chrome: 114.0.5735.133
  npmPackages:
    typescript: 5.1.6 => 5.1.6 
aladdin-add commented 7 months ago

the error usually happens when encountered a mismatched import vs. export. In your case:

export function Select (){...}

the import should be like


import {Select} from "mylib";// not!: import Select from ...
RockyStrongo commented 7 months ago

the error usually happens when encountered a mismatched import vs. export. In your case:

export function Select (){...}

the import should be like

import {Select} from "mylib";// not!: import Select from ...

I proprely imported the component in the target project, as below : import { Select } from '@mylibrary/components'

aladdin-add commented 7 months ago

can you provide a repro, thanks! 🙏

RockyStrongo commented 7 months ago

can you provide a repro, thanks! 🙏

Working on it 🙂

RockyStrongo commented 7 months ago

@aladdin-add please find below the reproduction:

when running the target project, you get the error :

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

Check the render method of `Select`.
aladdin-add commented 7 months ago

react-select:

Object.defineProperty(exports, '__esModule', { value: true });
...
exports["default"]=xxx;

the problem is:

var ReactSelect = require('react-select'); // ❌should be: require('react-select').default;

I think one of the causes is the next bundler does not know pkg.module.

  1. module field in package.json:

The module field in package.json is used to specify the entry point for a module when it is imported using the ES6 module syntax (import and export). This field allows you to define a different file that will be used when the module is imported with the import statement. For example:

{
  "name": "my-module",
  "main": "main.js",
  "module": "main-esm.js"
}
  1. "exports" feature in Node.js:

The "exports" feature was introduced in Node.js to provide a more flexible way to specify entry points for modules. Instead of relying solely on the main and module fields, the "exports" field allows you to define different entry points based on conditions like the Node.js version or the requested file extension.

For example:

{
  "name": "my-module",
  "exports": {
    ".": {"require": "./main.js", "import": "./main.mjs"}
  }
}

I suggest adding the exports in your lib. I think we can also update dts templates to support bundlers like next.js. 🤔

RockyStrongo commented 7 months ago

Thanks ! It fixed my issue.

In case it can help somebody, the reproduction repo is here : https://github.com/RockyStrongo/dts-issue-reproduction/tree/resolution (issue reproduction in main, solution in branch solution)