How to let users import from subfolders of my NPM package
Asked Answered
S

3

11

I want to let users import from the subfolders of my TypeScript NPM package. For example, if the directory structure of the TypeScript code is as follows,

- lib
- src
  - server
  - react

users of my package should be able to import from subfolders as package-name/react, package-name/server etc.

My tsconfig.json is as follows.

{
    "compilerOptions": {
      "target": "es5",
      "module": "commonjs",
      "declaration": true,
      "outDir": "./lib",
      "strict": true,
      "jsx": "react",
      "esModuleInterop": true
    },
    "include": ["src"],
    "exclude": ["node_modules", "**/__tests__/*"]
  }

I could do this when "outDir" was set to the project root, but then the file structure is messy. Can someone point me a way out to do this (importing from submodules) while preserving "outDir": "./lib"? Helpful answers are highly appreciated. Thanks in advance.

Sniggle answered 14/4, 2021 at 19:13 Comment(2)
can you share error you are getting while importing module.Bowhead
when I try to import components as import {component} from 'package-name/server', I get error 'package-name/server' is not found, but when I import as import {component} from 'package-name/lib/server, I do not get such an error and everything work well. However, I want to get rid of that /lib part and users to directly import from server subfolder.Sniggle
J
10

That is possible with an entry in the package.json file called exports:

"exports": {
  "./server": "./lib/server",
  "./react": "./lib/react"
},

You can read more about it here: https://nodejs.org/api/packages.html#packages_package_entry_points

But be careful:

Warning: Introducing the "exports" field prevents consumers of a package from using any entry points that are not defined, including the package.json (e.g. require('your-package/package.json'). This will likely be a breaking change.


EDIT: It seems like TypeScript ignores the field, maybe it helps when you add types like this:

// server.d.ts
export * from "./lib/server";

// react.d.ts
export * from "./lib/react";

What I did

I copied your project setup and changed something:

src/
  server.ts
  react.ts
lib/
  (generated output)
  server.d.ts
  server.js
  react.d.ts
  react.js
package.json
tsconfig.json
react.d.ts
server.d.ts

Add this to package.json:

"exports": {
  "./server": "./lib/server",
  "./react": "./lib/react"
},

react.d.ts

export * from "./lib/react";

server.d.ts

export * from "./lib/server";

That worked for me. Now I could import "mypackage/server" for example somewhere else. Maybe you didn't split up the definition files to multiple files, but try to apply these changes and see if it works.

Jackqueline answered 17/4, 2021 at 5:46 Comment(1)
Did you split it up to different files? It worked for me.Jackqueline
B
2

Add below exports entry in package.json

"exports": {
  "./": "./lib/"
},

After the entry you can import from within lib subfolder or file within lib folder but for subfolder within lib folder there need to be index.js file which would be imported.

Structure after typescript compilation that would work:

- lib
- src
  - server.js
  - react.js
- lib
- src
  - server
    - index.js
  - react
    - index.js

Note: slash(/) at the end of the entry are important for subpath export.

Example Steps:

mkdir -p mod1/src/api mod2/src
cd mod1
npm init -y

mod1/tsconfig.json

{
    "compilerOptions": {
      "target": "es5",
      "module": "commonjs",
      "declaration": true,
      "outDir": "./lib",
      "strict": true,
      "jsx": "react",
      "esModuleInterop": true
    },
    "include": ["src"],
    "exclude": ["node_modules", "**/__tests__/*"]
}

mod1/package.json

{
  "name": "mod1",
  "version": "1.0.0",
  "description": "",
  "exports": {
    "./": "./lib/"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "MIT"
}

mod1/src/api/index.ts

export const api = function() : void {
  console.log('api');
}

mod1/src/react.ts

export const react = function() : void {
  console.log('react');
}

mod1/src/server.ts

export const server = function() : void {
  console.log('server');
}

change to mod2 directory

cd ../mod2
npm init -y
npm install ../mod1

mod2/src/index.js

const lib = require('mod1/server');
const { react } = require('mod1/react');
const { api } = require('mod1/api');
lib.server();
react();
api();

Note: I used node version 14.4.0 and tsc version 4.2.4

Bowhead answered 22/4, 2021 at 14:58 Comment(0)
B
1

You can copy your package.json file into your dist/lib folder as part of the publish process to avoid a messy repository structure. See here for an example. As a downside, yarn link might be more complicated and this approach might not work for yarn workspaces.

Alternatively, you can use the exports key in package.json once TypeScript implements support for it.

Brisance answered 21/4, 2021 at 16:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.