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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With