Custom Library Components Does not show required props on intellisense

Custom Library Components Does not show required props on intellisense

  

I developed a custom React component library to be consume on a private npm. All my components are Typescript React Class Components and in many I used interfaces to declare which props are optional or required. Example:

export interface ICardLinkProps {

    Title: string;
    Description: string;
    ActionText: string;
    DestinationUrl?: string;
    ImageUrl?: string;
    Icon?: string;
    Theme: Theme;
}

export class CardLink extends React.Component<ICardLinkProps> {
   /// component Code.
}

All the components work as expected but when my coworkers install de package the intellisense does not show the required props. Example:

No intellinse

In contrast if I consume a component from Material-UI the intellisense shows all required and optional props.

yes Intellinse

Does anyone have an idea on Why I am not getting the intellisense for my components? I'm using rollup to export build the package, this is my configuration:

import typescript from "rollup-plugin-typescript2";
import commonjs from "rollup-plugin-commonjs";
import external from "rollup-plugin-peer-deps-external";
import resolve from "rollup-plugin-node-resolve";
import url from "rollup-plugin-url";
import PeerDepsExternalPlugin from "rollup-plugin-peer-deps-external";

import pkg from "./package.json";


export default {
  input: "src/index.ts",
  output: [
    {
      file: pkg.main,
      format: "cjs",
      exports: "named",
      sourcemap: true
    },
    {
      file: pkg.module,
      format: "es",
      exports: "named",
      sourcemap: true
    }
  ],
  plugins: [

    url({
      include: ['**/*.ttf', '**/*.png'],
      limit: Infinity
    }),
    PeerDepsExternalPlugin(),
    external(),
    resolve(),
    typescript({
      rollupCommonJSResolveHack: true,
      exclude: "**/__tests__/**",
      clean: true
    }),
    commonjs({
      include: ["node_modules/**"],
      namedExports: {
        "node_modules/react/react.js": [
          "Children",
          "Component",
          "PropTypes",
          "createElement"
        ],
        "node_modules/react-dom/index.js": ["render"],
        'node_modules/react-is/index.js': [
          'isElement',
          'isValidElementType',
          'ForwardRef',
          'Memo',
          'isFragment'
        ],
        'node_modules/prop-types/index.js': [
          'elementType'
        ]
      }
    })
  ]
};

Here is my tsconfig.json

{
  "compilerOptions": {
    "outDir": "dist",
    "module": "esnext",
    "target": "es5",
    "lib": [
      "es6",
      "dom",
      "es2016",
      "es2017"
    ],
    "sourceMap": true,
    "allowJs": false,
    "jsx": "react",
    "declaration": true,
    "moduleResolution": "node",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "typeRoots": [
      "src/types"
    ]
  },
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "src/**/*.stories.tsx",
    "src/**/*.test.tsx"
  ]
}

Here is the dist folder after runing rollup -c:

componentsDts

rootfolder

This is the contents of the CardLink.d.ts

import React from "react";
import { ICardLinkProps } from "./ICardLinkProps";
/**
 * CardLink Component
 * @extends {Component<ICardLinkProps,ICardLinkState>}
 */
export declare const CardLink: React.ComponentType<Pick<Pick<ICardLinkProps, "theme" | "classes"> & Partial<Pick<ICardLinkProps, "Title" | "Description" | "ActionText" | "DestinationUrl" | "ImageUrl" | "Icon" | "Secondary">> & Partial<Pick<{
    Title: string;
    Description: string;
    ActionText: string;
    DestinationUrl: string;
    ImageUrl: string;
    Icon: string;
    Secondary: boolean;
}, never>>, "Title" | "Description" | "ActionText" | "DestinationUrl" | "ImageUrl" | "Icon" | "Secondary"> & import("@material-ui/core").StyledComponentProps<"centeredIcon" | "icon" | "bellowMargin" | "paper" | "paperSecondary" | "iconSecondary" | "container" | "actionsArea">>;

Thanks in advance.

Answer

The question does not show the most important things:

  • the import of the component
  • the package.json of the component package
  • the bundle out dir (assume dist)

The first two items would tell you the name of the components package and how that paths are exported as a node module. Specifically the name and exports field from package.json.

I’m assuming the components package uses a barrel file to export them all from some index file since the build uses a bundler. An alternative would be to simply compile with TS and let the consuming package deal with a bundle.

For a bundled components package with output as given from the images you define the types so they can be found during module resolution. You should probably use NodeNext for moduleResolution.

package.json

"name": "components",
"main": "dist/index.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"exports": {
  ".": {
    "types": "./dist/index.d.ts",
    "import": "./dist/index.es.js",
    "require": "./dist/index.js",
    "default": "./dist/index.js"
  },
  "./package.json": "./package.json"
}

This would allow an import like

import { CardLink } from "components"

Where your editor can handle the types resolution via the types field from the package’s corresponding package.json file.

A better approach would be to not bundle but only compile while also producing a ES module and CommonJS build. Ideally you use different file extensions to at least distinguish the CJS types. Also, avoid a barrel file and use subpath exports. This allows for easier tree shaking by consuming packages and the tool ecosystem.

.
├── components/
│   ├── Foo/
│   │   └── foo.tsx
│   └── Bar/
│       └── bar.tsx
├── package.json
└── tsconfig.json

Now create a dual build somehow without bundling. It’s easy with @knighted/duel and other options.

Now use wildcards in the subpath exports. Same as before but change the exports:

"exports": {
  "./*": {
    "import": {
      "types": "./dist/*.d.ts",
      "default": "./dist/*.js"
    },
    "require": {
      "types": "./dist/*.d.cts",
      "default": "./dist/*.cjs"
    }
  }
}

Now you can import from a subpath instead of a barrel file.

import { Foo } from "components/Foo"

The types for Foo will be available to your editor through the types field.

© 2024 Dagalaxy. All rights reserved.