Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 433
Justin Grant Avatar asked Sep 24 '18 20:09

Justin Grant


People also ask

Does TypeScript use Webpack?

Webpack allows TypeScript, Babel, and ESLint to work together, allowing us to develop a modern project. The ForkTsCheckerWebpackPlugin Webpack plugin allows code to be type-checked during the bundling process.

Does Webpack convert TypeScript to JavaScript?

Like Babel, Webpack depends on TSC to transpile TypeScript to JavaScript but as TSC doesn't have a clue about Webpack, hence Webpack needs a loader to talk to TSC. This is where the ts-loader comes into play. Webpack compiles a TypeScript file using ts-loader package which asks TSC to do the compilation.


1 Answers

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.

like image 125
Justin Grant Avatar answered Sep 21 '22 21:09

Justin Grant