Compile-time OK, run-time error while using class from namespace in typescript with webpack

942 views Asked by At

I use swagger-codegen with the -l typescript-angular option to generate a library of REST consumer services. The generated code looks like this (DefaultApi.ts):

namespace API.Client {
    'use strict';

    export class DefaultApi {
        protected basePath = 'http://localhost:7331/v1';
        public defaultHeaders : any = {};

        static $inject: string[] = ['$http', '$httpParamSerializer', 'basePath'];

        constructor(protected $http: ng.IHttpService, protected $httpParamSerializer?: (d: any) => any, basePath?: string) {
            if (basePath !== undefined) {
                this.basePath = basePath;
            }
        }

        private extendObj<T1,T2>(objA: T1, objB: T2) {
            for(let key in objB){
                if(objB.hasOwnProperty(key)){
                    objA[key] = objB[key];
                }
            }
            return <T1&T2>objA;
        }

        /**
         * Delete a person.
         * Deletes a specified individual and all of that person&#39;s connections. 
         * @param id The id of the person to delete
         */
        public deletePersonById (id: number, extraHttpRequestParams?: any ) : ng.IHttpPromise<{}> {/*...*/}

        /* etc... */
    }
}

As you can see, there are concrete classes that need to be used but are declared inside of a namespace, i.e. not importable. My editor (VSCode) doesn't complain when I reference API.Client.DefaultApi despite the lack of an import because it picks up the definition as part of a declared namespace I suppose. But at run-time the browser complains that API is not defined.

I am using webpack to bundle my code. I see a few other questions on SO that are kind of like this one, but had no luck with the answers there.

EDIT:

As requested, here are my configuration files for ts and webpack:

webpack config file:

const webpack = require('webpack');
const conf = require('./gulp.conf');
const path = require('path');

const HtmlWebpackPlugin = require('html-webpack-plugin');
const autoprefixer = require('autoprefixer');

module.exports = {
  module: {
    preLoaders: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        loader: 'tslint'
      }
    ],

    loaders: [
      {
        test: /.json$/,
        loaders: [
          'json'
        ]
      },
      {
        test: /\.(css|less)$/,
        loaders: [
          'style',
          'css',
          'less',
          'postcss'
        ]
      },
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        loaders: [
          'ng-annotate',
          'ts'
        ]
      },
      {
        test: /.html$/,
        loaders: [
          'html'
        ]
      }
    ]
  },
  plugins: [
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.NoErrorsPlugin(),
    new HtmlWebpackPlugin({
      template: conf.path.src('index.html')
    })
  ],
  postcss: () => [autoprefixer],
  debug: true,
  devtool: 'source-map',
  output: {
    path: path.join(process.cwd(), conf.paths.tmp),
    filename: 'index.js'
  },
  resolve: {
    modules: [
      path.resolve(__dirname, '../src/app'),
      path.resolve(__dirname, '../node_modules')
    ],
    extensions: [
      '',
      '.webpack.js',
      '.web.js',
      '.js',
      '.ts'
    ]
  },
  entry: `./${conf.path.src('index')}`,
  ts: {
    configFileName: '../tsconfig.json'
  },
  tslint: {
    configuration: require('../tslint.json')
  }
};

tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "src/app",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false,
    "module": "commonjs"
  },
  "compileOnSave": false,
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "!typings/**",
    "!node_modules/**"
  ]
}
2

There are 2 answers

0
Abraão Alves On

You have two options to fix this, an ease and another complex:

  1. change generated ts file.

Add the following code at the end of generated code:

export = API.Client;

Now, you can use import in your modules without problems, eg:

import {DefaultApi} from './generated-code';
  1. But if change generated file is not an option, appeal to the Salsa compiler!

The idea:

Split the modular code and not modular code with differents tsconfig. Mix modular code and not modular with Salsa, webpack resolve alias and javascript support.

Tutorial:

TL;DR Here a GitHub repository applying this solution.

  1. Create a new tsconfig.generate.json to generated code handle , just generatred code, eg:
{
   "compilerOptions": {
      "outFile": "module-generated-code.js"
   },
   "files": ["generated-code.ts"]
}
  1. in your initial tsconfig.json, you must exclude generated ts files. This will ensure that there is no unnecessary code, eg:
{
   "compilerOptions": {

   },
   "exclude": ["generated-code.ts"]
}
  1. Now, the ace in the hole!You will add a api.js file and reference it in your tsconfig.generate.json. Yes, it is a js file, it is here that Salsa gets into action. And to do this you must enable allowJs feature in tsconfig
{ 
   "compilerOptions": {
      "outFile": "module-generate-code.js", "allowJs": true
   },
   "files": ["generated-code.ts", "api.js"]
}

These files are basically to export via commonjs your generated code without touch it.

/// <reference path="./namespacing-code.ts" />
// typescript compiler don't warning this because is Salsa!
module.exports = API.Client;

Now, note in tsconfig.generate.json and your outFile property. If you test the compiler (tsc -p tsconfig.generate.json), you see all your generated files concatenated in module-generate-code.js, and the last line must be like in this:

module.exports = API.Client;

Nearly Done!

Now, you can use the module-generate-code.js in your own code with import! But how js files do not have the best definitions, then you have config a resolve.alias in webpack.config and tsconfig.json

{ //webpack.config
    resolve: {
      extensions: ['', '.webpack.js', '.web.js', '.ts', '.js'],
      alias:{ 'api':'./module-generated-code.js'
    }
}
{ //tsconfig.json
    "compilerOptions": {
        "allowJs": true, //remember of enabling Salsa here too
        "baseUrl": ".",
        "paths": {
            "api":["api.js"] //it is just get type definitions from generated files
        }
     },

Now you can use your generate code without touch it: import api from 'api';

Any doubt, here a GitHub repo using this approach. I hope I´ve helped

3
Martin On

The current version of swagger-codegen TypeScript Angular generator does not wrap the DefaultApi in a namespace.

Update and regenerate. Let me know if you have any issues.