Search code examples
typescriptwebpackawesome-typescript-loader

Sharing code between projects using TypeScript and webpack


I want to share code between two TypeScript projects. I don't want to publish shared code to NPM-- just want to put shared code in one project and use it in another project. I'm using Webpack and awesome-ts-loader. Current folder structure (simplified) is like this:

/devroot/
  mainProject/
    tsconfig.json
    src/
      shared/
        SomeSharedTypes.ts
  apiProject/
    tsconfig.json
    webpack.config.js
    src/
      UseSomeSharedType.ts

In UseSomeSharedType.ts, I want to be able to import types from SomeSharedTypes.ts.

I tried an obvious solution like this:

import {SharedType} from '../../mainProject/src/shared/SomeSharedTypes'

But the TS compiler gave me this error:

TS6059: File '/devroot/mainProject/src/shared/SomeSharedTypes.ts' is not under 'rootDir' '/devroot/apiProject'. 'rootDir' is expected to contain all source files.


Solution

  • The first idea I got from this Medium article, which was to use TypeScript's non-relative module imports feature. This would allow me to write my imports like this:

    import {SharedType} from '@foo/SomeSharedTypes'
    

    Using the techniques described in the article, I added a paths configuration to my tsconfig.json:

    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@foo/*": ["../mainProject/src/shared/*"],
        },
        "rootDir": "./",
        ...
      }
    }
    

    Then, again as the article recommends, for users of awesome-typescript-loader, I had to modify my webpack.config.js to add a resolution plugin:

    const { TsConfigPathsPlugin } = require('awesome-typescript-loader');
    . . .
      resolve: {
        extensions: ['.js', '.json', '.ts'],
        plugins: [
          new TsConfigPathsPlugin(),
        ],
      }
    

    Important: this plugin needs to go into the resolve/plugins section of the file, not the root-level "plugins" section! If you put the resolver plugin in the wrong place, you'll get this error:

    resolver.ensureHook is not a function

    The steps above got me further along in the process-- TypeScript was now able to find my files!--but I still got the same error later in webpack's execution: 'rootDir' is expected to contain all source files.

    After a lot more Googling, I found a solution in this StackOverflow answer: instead of a single rootDir configuration, TS has a rootDirs setting that allows multiple roots. I removed my rootDir setting from tsconfig.json and added a rootDirs setting:

    "rootDirs": [
      "./",
      "../mainProject",
    ],
    

    Next, I ran into a webpack error on the other-project TypeScript file I was including:

    Module parse failed: Unexpected token (3:15)

    You may need an appropriate loader to handle this file type.

    After another hour of troubleshooting, I figured out that I need to tell the webpack loader about my new shared folder. Like this:

    const path = require('path');
    . . .
      rules: [
        {
          test: /\.[jt]sx?$/,
          loader: "awesome-typescript-loader",
          include: [
            __dirname,
            path.resolve(__dirname, "../mainProject/src/shared/")
          ],
          exclude: /node_modules/
        },
    

    That worked! The nice part about this solution is that I can refactor my folder structure without changing source code. All I'd need to change is tsconfig.json and webpack.config.js.

    I'm admittedly new to using webpack and TypeScript, so there may be a better solution than the one above... but the one above worked for me!

    Sharing the solution here to make it easier for the next developer to find it.