Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I share a Typescript module by different projects without duplicating the shared module?

Tags:

typescript

I have two folders: shared and project1 in a folder node. I am developing several independent Node projects located in the node folder.

In shared, I have an index.ts and it contains a default export. The outdir parameter in tsconfig.json is ./dist. When built, the directory is like:

node/shared/
           src/
              index.ts
           dest/
               index.js
           tsconfig.json
           ...

In project1, I have index.ts, and under "normal" circumstances, when built, the directory is like:

node/project1/
             src/
                index.ts
             dist/
                 index.js

My intention is to use shared to contain shared utilities that can be used by different projects in the node folder.

When I have a statement import shared from "../../shared/src/index.js" in project1/src/index.ts, the compiled output in the dest folder goes crazy:

node/project1/
             src/
                index.js
             dest/
                 shared/
                       src/
                          index.js  <- copied from the shared project
                 project1/
                         src/
                            index.js

But it is still not workable, because the node modules installed in the shared project are not present in project1.

My intention is to have in the run-time environment something simple, with no duplication of common shared code, like:

node/
    shared/
          index.js
    project1/
            index.js   <- importing stuff from shared/index
    project2/
            index.js   <- importing stuff from shared/index

Is this possible? Or is converting the shared module into an NPM package the only way to go?

like image 245
Old Geezer Avatar asked Sep 18 '25 06:09

Old Geezer


1 Answers

You can use project references.

node/
  project1/
    dist/
      index.js
    src/
      index.ts
    tsconfig.json
  shared/
    dist/
      index.js
      index.d.ts
    src/
      index.ts
    package.json
    tsconfig.json

Shared

This is set up like an ordinary project, except that the composite setting must be enabled.

shared/tsconfig.json

{
  "compilerOptions": {
    // Referenced projects must have the composite flag turned on
    "composite": true,
    "rootDir": "./src",
    "outDir": "./dist",
    "module": "commonjs"
  }
}

shared/package.json

{
  "name": "shared",
  "version": "0.0.1",
  "main": "./dist",
  "scripts": {
    "build": "tsc"
  },
  "devDependencies": {
    "typescript": "^4.0.3"
  }
}

shared/src/index.ts

export default 'foo'

The output shared/dist/index.js isn't too exciting:

"use strict";
exports.__esModule = true;
exports["default"] = 'foo';

Because of the composite compiler flag, type definitions are also generated:

shared/dist/index.d.ts

declare const _default: "foo";
export default _default;

Project

project1/tsconfig.json

Here, you specify that you are referencing the shared module.

{
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist"
  },
  "references": [
    {"path": "../shared"}
  ]
}

project1/package.json

Instead of simply tsc, you can use tsc --build (tsc -b for short). This build mode automatically rebuilds referenced projects if they are out of date. However, note that there are some specific build-only flags and you cannot override compiler options with command-line arguments.

{
  "name": "project1",
  "version": "0.0.1",
  "main": "./dist",
  "scripts": {
    "build": "tsc -b"
  },
  "devDependencies": {
    "typescript": "^4.0.3"
  }
}

project1/src/index.ts

Use the shared module like you normally would.

import foo from '../../shared'

console.log(foo)

project1/dist/index.js

The source compiles down to this:

"use strict";
exports.__esModule = true;
var shared_1 = require("../../shared");
console.log(shared_1["default"]);

As you can see, it correctly imports the shared module from the node/shared directory, instead of copying the files into dist.

You do not need to specify the full path of the file (../../shared/dist/index.js) because the main field of the shared project's package.json is set to ./dist, which resolves to ./dist/index.js. TypeScript knows that Node.js will resolve the require to the appropriate file and therefore knows the types of the shared module.

If you do not provide a main entry in package.json, you'll need to use import foo from '../../shared/dist'.

like image 151
cherryblossom Avatar answered Sep 19 '25 21:09

cherryblossom