Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sharing code between TypeScript projects (with React)?

This question: "Cannot find module" error when using TypeScript 3 Project References was somewhat helpful.

So was this one: Project references in TypeScript 3 with separate `outDir`

But I am still having trouble getting this working efficiently with VSCode and React.

Project structure:

  • client/src
  • common
  • server/src

common tsconfig:

{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "rootDir": ".",
    "composite": true,
    "outDir": "build"
  },
  "references": []
}

client tsconfig:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react",
  },
  "include": [
    "src",
  ],
  "references": [
    {
      "path": "../common"
    }
  ]
}

server tsconfig

{
    "compilerOptions": {
        "target": "es6",
        "lib": [
            "dom",
            "esnext"
        ],
        "module": "commonjs",
        "sourceMap": true,
        "outDir": "build",
        "allowJs": true,
        "moduleResolution": "node",
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noImplicitReturns": true,
        "noUnusedLocals": true,
        "baseUrl": ".",
    },
    "include": [
        "src/**/*"
    ],
    "references": [
        {
            "path": "../common"
        }
    ]
}

I am using create-react-app in client. This has caused compilation issues. I believe I have solved this with create-app-rewired and customize-cra using this for config-overrides.js

const { override, removeModuleScopePlugin, getBabelLoader } = require("customize-cra");
const path = require("path");

const addCommon = (config) => {
  const loader = getBabelLoader(config, false);
  const commonPath = path.normalize(path.join(process.cwd(), "../common")).replace(/\\/g, "\\");
  loader.include = [loader.include, commonPath];
  return config;
};
module.exports = override(
  addCommon,
  removeModuleScopePlugin(),
);

In common, I have created a file index.ts which exports code from every file.

export * from "./messages/toServer/AuthMessage";

for example.

On the front-end and back-end, I can import code (to use) with a handwritten import statement:

import { AuthMessage } from "../../../common/build/messages/toServer/AuthMessage";

VSCode doesn't give any suggestions, but it accepts the import statement assuming the code is built.

I run tsc -b -w in common. This seems to work with manual imports.

It is possible to have:

  • IntelliSense / auto-import When writing types such as AuthMessage
  • Usable code that is imported. I have seen examples where types are supported, but code is not generated from common. Untested, but I imagine this works well for interfaces in a common directory
  • Usability with React. Some relevant jabber: https://github.com/facebook/create-react-app/issues/6799. ts-loader supports project references, but I am unsure the status of babel-loader, which to the best of my knowledge, is the loader used by CRA. I got a partial solution with the code I provided above.

Guidance here: https://www.typescriptlang.org/docs/handbook/project-references.html#guidance is very confusing. Any help with an explanation here is appreciated.

Other solutions?

  • I don't want to publish and update a private NPM module.
  • Using a program such as dsynchronize: http://dimio.altervista.org/eng/ might be easier where code is duplicated across two projects, but included in the src of both projects. It's not clean, and I can see some disasters with refactoring and an IDE attempting to re-negotiate import paths if the program is running...

Any help is appreciated.

like image 701
Dakota Avatar asked Jan 01 '20 20:01

Dakota


1 Answers

It appears to be working. The steps I took...

Create a base tsconfig in the project directory. (This is documented)

{
  "references": [
    {
      "path": "./common"
    }
  ],
  "files": []
}

Add "extends": "../tsconfig" to both sub project tsconfigs.

export everything from all the files in common/src. (using /common/index.ts) Update import paths to the common directory import { ClassName } from "../../common" (not individual build folders)

Run tsc -b in non-react projects for compilation. After very simple initial testing, it looks like my rewired react config just picks up changes to common. The config-overrides.js is a necessity.

This does appear to work in VSCode, however, after testing, I agree auto-import is superior in WebStorm.

When creating new files in common, they must be exported in /common/index.ts WebStorm auto import will give you a clue, VSCode is a little less obvious.

like image 75
Dakota Avatar answered Nov 05 '22 14:11

Dakota