Search code examples
typescriptwebpacktsconfig

Build and reference a TypeScript module from a subfolder using Webpack


There are like three different senses of the word "module" in this question so please ask for clarification if it's not clear.

I have a TypeScript project with a heavy dependency on a node_module that I now want to make changes to. I've cloned the source for this dependency into a Git submodule. This dependency is also built with TypeScript.

My directory structure is as follows:

root/
  - source/
    - app.tsx (my entrypoint)
    - ... 
  - vendor/
    - dependency/ (git submodule)
      - node_modules/
        - (dependency's dependencies)
      - source/
        - index.ts
        - ... 
      - package.json
      - tsconfig.json
  - webpack.config.js
  - tsconfig.json
  - package.json

The file root/source/vendor/dependency/source/index.ts contains the dependency's exports.

I want to configure my project so that

  1. I can reference my dependency in my .ts files as if it were still a node_module dependency (i.e. import { class1, class2 } from "dependency".
  2. Make webpack build my dependency into the same bundle as my project

I'm having major difficulty with point 1. I can't figure out how to make tsc understand the module name "dependency".

I suspect the problem is a missing config setting in my tsconfig.json - but I can't figure it out. I've tried setting baseUrl and paths to what I thought should work but the compiler still complains that it can't find the module.


My current tsconfig.json

{
    "compileOnSave": false,
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "isolatedModules": false,
        "jsx": "react",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "declaration": false,
        "noImplicitAny": false,
        "sourceMap": true,
        "removeComments": true,
        "noLib": false,
        "preserveConstEnums": false,
        "suppressImplicitAnyIndexErrors": true,
        "outDir": "./lib",
        "types": [
            "es6-promise",
            "google.analytics"
        ],
        "baseUrl": ".",
        "paths": {
            "dependency": "vendor/dependency/source/index.ts"
        }
    },
    "exclude": [
        "**/node_modules",
        "vendor/dependency/node_modules/"
    ]
}

My current webpack.config.js

var path = require("path");
var tsConfigPath = path.resolve("tsconfig.json");

module.exports = {
  devtool: "source-map",
  entry: {    
    web: path.resolve(path.join("source", "app.tsx"))
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".scss"],
    modules: [
      "node_modules",
      "vendor"
    ]
  },
  output: {
    path: path.resolve(path.join("proxy", "static")),
    filename: "[name]/[name].js",
    publicPath: "/"
  },
  module: {
    rules: [
        {
          test: /\.tsx?$/,
          loader: "awesome-typescript-loader"
        },
        {
          test: /\.scss$/,
          loaders: ["style-loader", "css-loader", "sass-loader"]
        },
        { // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
          enforce: "pre",
          test: /\.js$/,
          loader: "source-map-loader"
        }
    ]
  },
  plugins: [
    require("webpack-fail-plugin")
  ],
  externals: {
    "react": "React",
    "react-dom": "ReactDOM"
  }
};

Please let me know if I'm going about this all wrong... all I want to be able to do is to modify my dependency locally without having to publish it to npm.


Solution

  • This is a little bit tricky, but you can create node_modules folder in your project root (if it is not already there). Inside of it you create a file dependency.ts in which you do:

    export * from './../vendor/dependency/source/index.ts'
    

    After that when you write import { class1, class2 } from 'dependency' from anywhere in your app, it will look for root/node_modules/dependency.ts.

    Read this article from official TypeScrypt docs for details.