Custom Library Components Does not show required props on intellisense
Asked Answered
E

1

6

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.

Extravehicular answered 3/5, 2021 at 21:11 Comment(8)
Can we see your tsconfig.json?Abeu
Sure, let me update my question.Extravehicular
When you look in the build folder that rollup generates, are there any .d.ts files in there?Abeu
@SethLutske Yes, Updating question with the rollup outputExtravehicular
Ok...what's the actual content of the CarLink.d.ts file?Abeu
@SethLutske added the content of the .d.tsExtravehicular
I deleted my answer because the issues is clearly not the lack of .d.ts files being output. But now the question has enough info to fully describe the problem you're having with your specific build environmentAbeu
This is most likely due to a misconfigured package.json for the component lib.Tactic
T
1

The question does not show the most important things:

  • the import specifier of the component
  • the package.json of the component package

These items would tell you the name of the components package and how the 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 the src/index.ts file since the build uses a bundler. An alternative would be to compile with TS and let the consuming package deal with a bundle.

Define the types in the package.json file 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"

Now your editor’s intellisense can provide the type info via resolution of 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/foo"

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

Tactic answered 4/6, 2024 at 5:36 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.