skip to Main Content

I have a Node.js library package (let’s call it "OasisLib") which has a file named TypeGenerator.ts.

The exact logic of what happens in the file is not relevant. But this needs to access files in filesystem while building the project. So, we used let fs = require('fs') to write some additional files to file system.

This OasisLib needs to be used in two different Node.js modules:

  1. OasisUI – This is a React application
  2. OasisLibExtension – This is another Node.js library that another team has to be able to implement, and in here we need to use TypeGenerator that is defined in OasisLib to generate additional files during build.

So, the OasisLib is used in both a UI package and a non-UI package and the TypeGenerator in base library package is only needed in extension library during build time. We have exported TypeGenerator in library package by adding it as an export in index.ts.

However, when I’m running the UI package, we are getting following error on homepage of UI:

ERROR in ../OasisLib/dist/src/gen/TypeGenerator.js 48:15-28
Module not found: Error: Can't resolve 'fs' in '/Volumes/Code/OasisLib/src/OasisLib/dist/src/gen'

So, the JS file is being compiled even though I’m not using that file in UI package.

Here is what I’m trying to figure out. Is there any way to export TypeGenerator from OasisLib module, so that I can use it in LibExtensions package, but not cause issues in UI package?

Alternatively can I exclude a specific file when starting UI React application?

I tried adding following to OasisUI but this didn’t solve the issue:

"exports": {
    "../OasisLib/dist/src/gen/TypeGenerator.js": {
      "ignore": true
    }
  },
  "browser": {
    "fs": false
  }

Package.json of OasisLib.

{
  "name": "@myorg/oasis-lib",
  "version": "1.0.0",
  "description": "",
  "author": "",
  "license": "UNLICENSED",
  "main": "dist/index.js",
  "types": "dist/types/index.d.ts",
  "scripts": {
    "clean": "rm -rf dist && rm -rf node_modules",
    "generate-models": "node ./dist/src/gen/GenerateTypes.js && tsc",
    "build": "tsc && npm run generate-models",
    "watch": "tsc -w",
    "prepublishOnly": "npm run build && npm run test",
    "test:watch": "vitest",
    "test": "vitest run --coverage --passWithNoTests",
    "start": "env-cmd -f .env.staging react-scripts start"
  },
  "files": [
    "dist/src/*",
    "dist/types/*"
  ],
  "npm-pretty-much": {
    "runTest": "never"
  },
  "dependencies": {
    "ion-js": "^4.3.0"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.0.1",
    "@testing-library/react": "^14.1.2",
    "@types/jest": "*",
    "@types/node": "^16.18.25",
    "@vitejs/plugin-react": "^4.0.3",
    "@vitest/browser": "^0.34.6",
    "@vitest/coverage-v8": "^0.34.6",
    "env-cmd": "^10.1.0",
    "prettier": "2.8.4",
    "react-scripts": "^5.0.1",
    "typescript": "^4.9.5",
    "vite": "^4.4.5",
    "vitest": "^0.34.6"
  },
  "peerDependencies": {
    "@types/react": ">=18",
    "@types/react-dom": ">=18",
    "react": ">=18",
    "react-dom": ">=18"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

2

Answers


  1. Chosen as BEST ANSWER

    Here is what I came up with before I noticed the other answer, which I still recommend instead of this answer. But documenting here as an alternative solution.

    I changed the TypeGenerator.ts to avoid any references to fs by moving the code that uses fs to another class. I only use writeFile method of fs so created a dummy object named writer with dummy writeFile method.

    Previous code in TypeGenerator.ts

    export function generateTypes(types: ConfigTypeDef[] = [], fs_module: any) {
      writer = require("fs");
      generate(types, writer);
    }
    

    New code without any references to fs.

    let writer: { writeFile: (arg0: string, arg1: string, arg2: { (err: Error): void; (err: Error): void; (err: Error): void; }) => void; };
    
    export function generateTypes(types: ConfigTypeDef[] = [], fs_module: any) {
      writer = fs_module;
      generate(types, writer);
    }
    

    The TypeGenerator is still exported as usual in OasisLib. When OasisLibExtension is calling this method, it passes the fs module. OasisUI package does not have the issue of fs anymore.


  2. IIUC, you have 3 projects:

    • (A) OasisLib having both runtime objects (A1) and TypeGenerator (A2) for its own build, all exposed through a single index.ts
    • (B) UI React app importing (A1)
    • (C) Node lib extension using (A2) for its own build

    Because (A2) is part of the same index.ts file as (A1), if (B) tries to import (A1) through this entry point, and it does not perform tree shaking, then indeed, it will include (A2) as well.

    But you can expose (A1) and (A2) through separate entry points. The exact configuration depends on the build engine in your (A) project and its build steps.

    That way, (A1) is unrelated to (A2) TypeGenerator (even though the latter may still import (A1) if needed), and (B) UI app can import (A1) separately from (A2).

    While (C) can also import (A2) through the separate entry point.


    If you use directly tsc to build the (A) library, and your tsconfig.json is not too fancy, it should already transpile each .ts file into their own .js and .d.ts files. Therefore, you can directly import them, as if each file is an entry point:

    // In (C) build script
    // Instead of:
    import { TypeGenerator } from "@myorg/oasis-lib"; // Resolves to package.json > main, i.e. dist/index.js
    
    // ...directly import the desired file:
    import { TypeGenerator } from "@myorg/oasis-lib/dist/src/gen/TypeGenerator";
    

    Now you can remove the mention of TypeGenerator from your index.ts, and (B) is no longer polluted by (A2).


    You can even use the "exports" field in package.json to further tune your exposed API / entry points:

    // (A) package.json
    {
      "name": "@myorg/oasis-lib",
      "exports": {
        ".": {
          "types": "./dist/types/index.d.ts",
          "default": "./dist/index.js"
        },
        "type-gen": {
          "types": "./dist/src/gen/TypeGenerator.d.ts",
          "default": "./dist/src/gen/TypeGenerator.js"
        }
      }
    }
    
    // (B) UI app component file
    import { RuntimeObject } from "@myorg/oasis-lib"; // Resolves to exports > . > types + default
    
    // (C) lib extension build script file
    import { TypeGenerator } from "@myorg/oasis-lib/type-gen"; // Resolves to exports > type-gen > types + default
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search