How to share code between TypeScript projects?

后端 未结 5 749
猫巷女王i
猫巷女王i 2020-12-12 19:11

Let\'s say I have two projects with following file structure

/my-projects/

  /project-a/
    lib.ts
    app.ts
    tsconfig.json

  /project-b/
    app.ts           


        
相关标签:
5条回答
  • 2020-12-12 19:27

    This can be achieved with use of 'paths' property of 'CompilerOptions' in tsconfig.json

    {
      "compilerOptions": {
        "paths": {
          "@otherProject/*": [
            "../otherProject/src/*"
          ]
        }
      },
    }
    

    Below is a screenshot of folder structure.

    Below is content of tsconfig.json which references other ts-project

    {
      "compilerOptions": {
        "baseUrl": "./",
        "outDir": "./tsc-out",
        "sourceMap": false,
        "declaration": false,
        "moduleResolution": "node",
        "module": "es6",
        "target": "es5",
        "typeRoots": [
          "node_modules/@types"
        ],
        "lib": [
          "es2017",
          "dom"
        ],
        "paths": {
          "@src/*": [ "src/*" ],
          "@qc/*": [
            "../Pti.Web/ClientApp/src/app/*"
          ]
        }
      },
      "exclude": [
        "node_modules",
        "dist",
        "tsc-out"
      ]
    }
    

    Below is import statement to reference exports from other project.

    import { IntegrationMessageState } from '@qc/shared/states/integration-message.state';
    
    0 讨论(0)
  • 2020-12-12 19:28

    Since Typescript 3.0 this can be done with Project References.

    Typescript docs: https://www.typescriptlang.org/docs/handbook/project-references.html

    I believe you would have to move lib.ts into a small ts project called something like 'lib'

    The lib project should have a tsconfig containing:

    // lib/tsconfig.json
        {
              "compilerOptions": {
                /* Truncated compiler options to list only relevant options */
                "declaration": true, 
                "declarationMap": true,
                "rootDir": ".",   
                "composite": true,     
              },
              "references": []  // * Any project that is referenced must itself have a `references` array (which may be empty).
            }
    

    Then in both project-a and project-b add the reference to this lib project into your tsconfig

    // project-a/ts-config.json
    // project-b/ts-config.json
    {
            "compilerOptions": {
                "target": "es5", 
                "module": "es2015",
                "moduleResolution": "node"
                // ...
            },
            "references": [
                { 
                    "path": "../lib",
                    // add 'prepend' if you want to include the referenced project in your output file
                    "prepend": true, 
                }
            ]
        }
    

    In the lib project. Create a file index.ts which should export all your code you want to share with other projects.

    // lib/index.ts    
    export * from 'lib.ts';
    

    Now, let's say lib/lib.ts looks like this:

    // lib/lib.ts
    export const log = (message: string) => console.log(message);
    

    You can now import the log function from lib/lib.ts in both project-a and project-b

    // project-a/app.ts 
    // project-b/app.ts
    import { log } from '../lib';
    log("This is a message");
    

    Before your intelissense will work, you now need to build both your project-a and project-b using:

    tsc -b 
    

    Which will first build your project references (lib in this case) and then build the current project (project-a or project-b).

    The typescript compiler will not look at the actual typescript files from lib. Instead it will only use the typescript declaration files (*.d.ts) generated when building the lib project.

    That's why your lib/tsconfig.json file must contain:

    "declaration": true,
    

    However, if you navigate to the definition of the log function in project-a/app.ts using F12 key in Visual Studio code, you'll be shown the correct typescript file. At least, if you have correctly setup your lib/tsconfig.json with:

    "declarationMap": true,
    

    I've create a small github repo demonstrating this example of project references with typescript:

    https://github.com/thdk/TS3-projects-references-example

    0 讨论(0)
  • 2020-12-12 19:29

    It seems we've got a few options here:

    (1) Put both projects in a mono repo and use answer given by @ThdK using TS project references

    NOT GOOD if you don't want a mono repo

    (2) Use Lerna - see answer by @metric_caution

    NOT GOOD if you can't be bothered to learn Lerna / don't want to publish your shared files to npm

    (3) Create a shared npm package

    NOT GOOD if you don't want to publish your shared files to npm

    (4) Put shared folders in a "shared" directory in project A and write a script to copy files in the shared folder from project A to project B's shared folder that is executed on a git push OR a script to sync the two folders.

    Here the script can be executed manually when copying / syncing is required. The copying / syncing could also be done prior to a git push using husky and the shared files added to git automatically in the script.

    Since I don't want a mono repo and I can't be bothered to publish an npm package for such a pathetic purpose I'm gonna go with option 4 myself.

    0 讨论(0)
  • 2020-12-12 19:30

    In a similar scenario, where I also wanted to avoid the overhead of having to perform an NPM release, I went for the following structure (after lots of trial and error and failed attempts):

    /my-projects/
    
      /node_modules/
        /my-lib/
          lib.ts
          tsconfig.json
          package.json
    
      /project-a/
        app.ts
        tsconfig.json
    
      /project-b/
        app.ts         
        tsconfig.json
    

    The central idea is to move the shared stuff to a node_modules directory above the individual projects (this exploits NPMs loading mechanism, which would start looking for dependencies in the current directory and then move upwards).

    So, project-a and project-b can now access the lib simply via import { Whatever } from 'my-lib'.

    Notes:

    1. In my case, my-lib is actually only for shared typings (i.e. .d.ts files within a lib subdirectory). This means, I do not explicitly need to build my-lib and my my-lib/package.json looks as follows:

      {
        "name": "my-types",
        "version": "0.0.0",
        "private": true,
        "types": "lib/index"
      }
      
    2. In case my-lib contains runnable code, you’ll obviously need to build my-lib, so that .js files are generated, and add a "main" property to the package.json which exposes the main .js file.

    3. Most important: Despite its name, /my-projects/node_modules only contains custom code, no installed dependencies (they are actually in the individual projects project-a/node_modules and project-b/node_modules). This means, there’s an explicit git ignore setting, which un-ignores the /node_modules directory from being committed.

    Is this a clean solution? Probably not not. Did it solve my issue? Yes. Am I happy to hear about improvement suggestions? Absolutely!

    0 讨论(0)
  • 2020-12-12 19:40

    I think that @qqilihq is on the right track with their response - Although there are the noted potential problems with manually maintaining the contents of a node_modules directory.

    I've had some luck managing this by using lerna (although there are a number of other similar tools out there, for example yarn workspaces seem to be somewhat similar, although I've not used them myself).

    I'll just say upfront that this might be a little heavyweight for what you're talking about, but it does give your project a lot of flexibility to grow in the future.

    With this pattern, your code would end up looking something like:

    /my-projects/
        /common-code/
            lib.ts
            tsconfig.json
            package.json
        /project-a/
            app.ts (Can import common-code)
            tsconfig.json
            package.json (with a dependency on common-code)
        /project-b/
            app.ts (Can import common-code)
            tsconfig.json
            package.json (with a dependency on common-code)
    

    The general theory here is that the tool creates symlinks between your internal libraries and the node_modules directories of their dependant packages.

    The main pitfalls I've encountered doing this are

    • common-code has to have both a main and types property set in its package.json file
    • common-code has to be compiled before any of its dependencies can rely on it
    • common-code has to have declaration set to true in its tsconfig.json

    My general experience with this has been pretty positive, as once you've got the basic idea understood, there's very little 'magic' in it, its simply a set of standard node packages that happen to share a directory.

    0 讨论(0)
提交回复
热议问题