Topsy
Topsy

Reputation: 1162

Import of local typescript package not resolved

Here is what I want to achieve.

I want to make a package that I will use to share typescript interface, or common config that will be shared between my front-end (react) and my back-end (nestjs)

I have created project called "shared" and made a link in my package.json.

Like so : "shared": "file:../shared",

It works great my React, where can use my interface or anything from "shared" without any error !

I did the same in my nestjs project, there is not error in the editor and I can see the shared package in the node_modules. But when I compile the project, it fails with :

Error: Cannot find module 'shared/interfaces/user'

So I guess the problem comes from something in my nestjs conf or webpack... But I don't know what.

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "moduleResolution": "node",
  },
}

webpack-hmr.config.js

const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');

module.exports = function (options) {

    return {
        ...options,
        entry: ['webpack/hot/poll?500', options.entry],
        watch: true,
        externals: [
            nodeExternals({
                allowlist: ['webpack/hot/poll?500'],
            }),
        ],
        plugins: [
            ...options.plugins,
            new webpack.HotModuleReplacementPlugin(),
            new RunScriptWebpackPlugin({ name: options.output.filename }),
            new webpack.WatchIgnorePlugin( {paths: [/\.js$/, /\.d\.ts$/] }),

        ],
        
    };
};

If you have any idea :) Thanks guys !

Upvotes: 6

Views: 5542

Answers (2)

Attila Večerek
Attila Večerek

Reputation: 800

What I usually do in my multi-component backend applications, e.g. app server, Kafka consumers, k8s cron jobs, etc., is that I use a so-called monorepo structure. I assume you could do the same for your frontend + backend project. I would have the following directory structure in my projects:

- apps
  - server
    - package.json
    - tsconfig.json
  - internal-kafka-events-consumer
    - package.json
    - tsconfig.json
  - sqs-consumer
    - package.json
    - tsconfig.json
  - resource-deleter-job
    - package.json
    - tsconfig.json
- packages
  - resource-repo
    - package.json
    - tsconfig.json
  - resource-validator
    - package.json
    - tsconfig.json
- package.json
- tsconfig.json
- tsconfig-base.json

Apps are private npm packages representing the components and packages are also private npm package representing some re-usable libraries across the apps. In your case, both the frontend and backend pieces would be separate components in the apps directory. To make this repository a monorepo, you can use yarn workspaces. I will describe the important bits in the tsconfig.json and package.json files.

// package.json
{
  "private": true,
  "workspaces": {
    "packages": [
      "packages/*",
      "apps/*"
    ]
  }
}

The package.json file in your root project is the one that tells yarn that you're going to work with a workspace and which directories should be considered workspaces.

// tsconfig.json
{
  "files": [],
  "include": [],
  "references": [
    { "path": "./apps/server" },
    { "path": "./apps/internal-kafka-events-consumer" },
    { "path": "./apps/sqs-consumer" },
    { "path": "./apps/resource-deleter-job" },
    { "path": "./packages/resource-repo" },
    { "path": "./packages/resource-validator" }
  ]
}

The tsconfig.json file in your root holds references to all the typescript projects in your repository. This so that if you run tsc --build from the root, it can build all your subprojects at once, and that it can import modules from the referenced projects properly.

I also like to use a tsconfig-base.json file to hold the shared configuration, i.e. all the TS configuration that is the same for all the apps and packages:

// tsconfig-base.json
{
  "compilerOptions": {
    ... // compiler options shared by TS sub-projects
  }
}

Assume that the server project depends on both resource-repo and resource-validator packages. The resource-repo's package.json file would look the following way:

// packages/resource-repo/package.json
{
  "name": "@my-project/resource-repo",
  "private": true,
  "version": "1.0.0"
}

The tsconfig.json file for the package would look this way:

// packages/resource-repo/tsconfig.json
{
  "extends": "../../tsconfig-base.json",
  "compilerOptions": {
    ... // project-specific compiler options
  }
}

The apps/server/package.json would look the following way:

// apps/server/package.json
{
  "private": true,
  "dependencies": {
    "@my-project/reource-repo": "1.0.0",
    "@my-project/resource-validator": "1.0.0"
  }
}

And its tsconfig.json file would look this way:

// apps/server/tsconfig.json
{
  "extends": "../../tsconfig-base.json",
  "compilerOptions": {
    ... // project-specific compiler options
  },
  "references": [
    { "path": "../../packages/resource-repo" },
    { "path": "../../packages/resource-validator" }
  ]
}

I don't have much experience with TypeScript front-end projects but I think that given this monorepo structure with the proper yarn workspace and TS setup adding bundling via Webpack or similar should not be a big issue. A simple Google search for "webpack typescript monorepo" gave me this article.

Upvotes: 4

Flavien Volken
Flavien Volken

Reputation: 21309

You should display the code importing your module as well. The issue might be due to the import where the path should be relative to you file and not an absolute path. Same as: https://stackoverflow.com/a/54049216/532695

If you really want to have a shorter link, like import {myInterface} from @shared/myModule you can setup path alias in your tsconfig.json file.

{
...
  "compilerOptions": {
    "paths": {
      "@shared/*": ["path/to/shared/folder/*"],
    }
  },
...
}

But this is only understood by ts or possibly ts-node. If you compile this using webpack, you might need to create an alias in its configuration as well:

...
resolve: {
  alias: {
    shared: path.resolve(__dirname,'path/to/shared/folder/')
  }
}
...

Here is an article about how to setup aliases for webpack

Upvotes: 1

Related Questions