7

I'm creating a library that exports a lot of React Components and also some TypeScript utilities used for multiple projects within my company. However, I can’t seem to do anything other than everything exporting from the root of the library.

I have the following folder structure. The .tsx and utils.ts files only export named properties and the root index.ts file contains export * from './Button';, etc. Then src/index.ts is export * from './ui', etc...

src/
  ui/
    Button/
      Button.tsx
    Input/
      Input.tsx
    index.ts
  utils/
    myUtilFunc1.ts
    myUtilFunc2.ts
    index.ts

Using the below Webpack configuration,

const path = require("path");

module.exports = {
  entry: "./src/index.ts",
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
    library: "@company/library",
    libraryTarget: "umd",
    umdNamedDefine: true,
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  externals: {
    react: "react",
    "react-dom": "react-dom",
  },
};

And the following package.json properties:

"main": "dist/main.js",
"module": "dist/main.js",
"types": "dist/types/index.d.ts",

When I consume this library in another project, I can import as I need like so: import { Button, myUtilFunc2 } from "@company/library"

But I would like to import like this:

import { Button } from "@company/library/ui" and import {myUtilFunc2 } from "@company/library/util"

I know this should be possible, as MUI and other libraries have this. I am using Webpack as I tried this will rollup and even set up a monolithic repository with Lerna (which works well, but I just feel like I shouldn’t need to build two npm packages for this and they can both be contained in one).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
rorypicko
  • 4,194
  • 3
  • 26
  • 43

3 Answers3

3

One possible way to achieve your goal is to use the exports field in your package.json file, which allows you to declare which module should be used when using module requests like import "package" or import "package/sub/path". This feature is supported by webpack 5 and newer versions.

For example, you could have something like this in your package.json:

{
  "name": "@company/library",
  "main": "dist/main.js",
  "module": "dist/main.js",
  "types": "dist/types/index.d.ts",
  "exports": {
    ".": "./dist/main.js",
    "./ui": "./dist/ui/index.js",
    "./utils": "./dist/utils/index.js"
  }
}

This way, you can import your submodules like this:

import { Button } from "@company/library/ui";
import { myUtilFunc2 } from "@company/library/utils";

According to webpack documentation:

When the exports field is specified, only these module requests are available. Any other requests will lead to a ModuleNotFound Error.

So if you have too many project modules, this might be tedious or impractical.

In that case, you could try to use some of the syntax features that allow you to specify multiple or dynamic module requests, such as:

  • Using an array of results to provide alternatives for the same module request.
  • Using a wildcard (*) to match any value and replace it in the result.
  • Using a slash (/) at the end of the property to forward a request with this prefix to the old file system lookup algorithm.

To learn more, please see Webpack 5 General Syntax, Alternatives.

But it has some compatibility issue.

  • Not all module systems or tools support the exports field. For example, Node.js only supports it from version 12+, and only for ES modules. (You can get this information here.)

You can learn more about the exports field and its syntax from this resource.

Pluto
  • 4,177
  • 1
  • 3
  • 25
1

You can archive this by using Webpack alias.

Configure your Webpack to resolve the @company alias to the appropriate path where your components are located. This can be done by adding the following to your Webpack configuration:

const path = require('path');

module.exports = {
  // ...
  resolve: {
    alias: {
      '@company': path.resolve(__dirname, 'path/to/company/library/ui'),
    },
  },
  // ...
};

But you need to make sure to follow the same structure:

    - company
      - library
        - ui
          - Button.js (Button comp)

Then this should work:

import React from 'react';
import { Button } from '@company/library/ui';
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
victorbalssa
  • 2,762
  • 2
  • 13
  • 25
-3

To achieve the desired folder structure when importing components and utilities from your library, you need to make some adjustments to your project's configuration and file structure. You can use the resolve.alias property in your Webpack config to create aliases for specific directories, so they can be imported directly.

Here's how you can modify your Webpack configuration to achieve the desired import syntax:

Update your Webpack config:

    const path = require("path");

module.exports = {
  entry: "./src/index.ts",
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
    alias: {
      "@company/library": path.resolve(__dirname, "src"),
    },
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
    library: "@company/library",
    libraryTarget: "umd",
    umdNamedDefine: true,
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  externals: {
    react: "react",
    "react-dom": "react-dom",
  },
};

Modify the way you export from your src/index.ts:

    // src/index.ts
export * from "./ui";
export * from "./utils";

Update your file structure:

src/
  ui/
    Button/
      Button.tsx
      index.ts // Add index.ts to re-export Button.tsx
    Input/
      Input.tsx
      index.ts // Add index.ts to re-export Input.tsx
    index.ts   // Add index.ts to re-export components
  utils/
    myUtilFunc1.ts
    myUtilFunc2.ts
    index.ts   // Add index.ts to re-export utilities
  index.ts     // Re-export from ui and utils

With these changes, you should be able to import components and utilities as desired:

// Importing components
import { Button } from "@company/library/ui";
import { Input } from "@company/library/ui";

// Importing utilities
import { myUtilFunc1, myUtilFunc2 } from "@company/library/utils";

This will provide you with the folder structure you want while keeping everything within a single npm package. The alias in the Webpack config allows you to use the @company/library import path, which points to the root src directory of your library. The index.ts files in each directory are used to re-export the components and utilities, making them easily accessible from their respective folders.

Muhammad Idrees
  • 570
  • 4
  • 10