Search code examples
node.jsreactjstypescriptcreate-react-appnpm-publish

How to publish a typescript package for both node and react


I have an authentication library in typescript that I have published to npm and want to be able to use it for both node projects and react projects (that are created using create-react-app). If I'm correct the packages that are published on npm will work in apps that are created using create-react-app since the bundling in create-react-app will do the magic to bundle the stuff correctly, however when I install and import my published library in react one of my dependencies won't load correctly. It is interesting that the other dependencies work well, only this dependency is not being loaded correctly. Everything works in Node projects and the issue seems to only happen for React.

When I debug in the debugger I see the value of the imported dependency that is not loaded correctly

Object.defineProperty(exports, "__esModule", { value: true });
exports.JsSigner = void 0;
const util_1 = require("dependency with issue");

is set to a path "util_1: /static/media/index.325f9bf6594ebb34640d.cjs" while the other dependencies are correctly assigned with an object.

The interesting thing is that when I install/import the dependency package that has issues, directly in the react app it loads correctly.

I'm wondering if there is anything that needs to be set in my package before it is published to make it work for both node and create-react-app projects?! Or the published packages are supposed to work for both type of projects?!


Solution

  • The issue turned out to be due to the upstream dependency having two different published modules for different module systems using "exports" settings in package.json:

    "exports": {
        ".": {
          "require": "./index.cjs",
          "default": "./index.js"
        }
    }
    

    the exports setting is a de facto package.json setting that is used by webpack to decide which exported module should be used.

    my published package was compiled by typescript to publish CommonJs modules. So typescript would compile my code to use "require()" to import dependency modules so based on the specified exports setting in the upstream dependency, webpack (which is used by create-react-app) would package the ./index.cjs file from the upstream dependency in my react-app bundle! That's why I was seeing:

    When I debug in the debugger I see the value of the imported dependency that is not loaded correctly

    Object.defineProperty(exports, "__esModule", { value: true });
    exports.JsSigner = void 0; const util_1 = require("dependency with
    issue"); 
    

    is set to a path "util_1: /static/media/index.325f9bf6594ebb34640d.cjs" while the other dependencies are correctly assigned with an object.

    The solution: I needed to set typescript to build both cjs and ems modules for my lib by adding build scripts for both formats in my package.json

    scripts": {
        "build": "yarn build:ems && yarn build:cjs",
        "build:cjs": "tsc --module commonjs --outDir dist/cjs",
        "build:ems": "tsc --module esnext --outDir dist/ems",
      },
    

    The above settings builds my typescripts in 2 different module formats

    • in commonJs module and saves the output in dist/cjs
    • in esnext module and saves the output in dist/ems

    I also needed to add exports to my package.json to let webpack know which module should be loaded for which module systems:

    "exports": {
        ".": {
          "require": "./dist/cjs/index.js",
          "default": "./dist/ems/index.js"
        }
      }
    

    The above exports setting tells webpack to load ./dist/cjs/index.js when my package is imported using require(), and load ./dist/ems/index.js in other cases.