How to import external NPM module with Typescript

2019-05-11 12:09发布

问题:

  1. 'test-module' is external module written in Typescript with index.d.ts definition file. it has properties for tsd/tsc in package.json:

    "typings": "dist/index.d.ts", "typescript": { "definition": "dist/index.d.ts" }

  2. 'test-module' is installed with JSPM in jspm_packages/npm/test-module for dynamic loading using SystemJS

  3. 'app' is Typescript application that imports 'test-module' like this:

    import {Component} from 'test-module';

The problem is that 'test-module' module HAS TO BE in both locations:

  1. in node_modules for Typescript compiler (otherwise it does not find 'test-module' and errors it during compile import from 'test-module')

  2. in jspm_packages for SystemJS to load it during runtime

So, i need to insert it in package.json 'dependencies' AND 'jspm/dependencies'

Is there a hack to :

A) force JSPM/SystemJS to use ONLY standard node_modules folder? (I know I can use raw SystemJS and map node_modules but it means that I have to map it for every single dependency and dependencies of dependency which is a lot of manual work)

OR

B) force Typescript to search modules using some kind of path mapping (I guess version 1.8 will have this feature)

Any ideas?

回答1:

This is a answer I don't like but is a work around. In fact I come across your question while searching for a answer to my case, they are similar.

In my case I create a external library too, that library is compiled with typescript and the module is generated to be consumed in external jspm projects.

What works for me is to import the library with the same syntax of javascript es6. Suppose the library is named myLib and have a config.js map of myLib=blabla and you have a file called notify.js (generated from typescript). The import is

import Notify from 'myLib/notify'

At runtime it works, but the problem is that the compiler for the project cannot locate 'myLib/notify'. The definition generated by typescript (file jspm_package/github/myLib/myLib@version/notify.d.ts for the library is something like:

export declare enum NotifyStatus {
    INFO = 0,
    SUCCESS = 1,
    WARNING = 2,
    DANGER = 3,
}
export declare enum NotifyPosition {
    TOP_CENTER = 0,
    TOP_LEFT = 1,
    TOP_RIGHT = 2,
    BOTTOM_CENTER = 3,
    BOTTOM_LEFT = 4,
    BOTTOM_RIGHT = 5,
}
/**
 * Class for notifications
 */
export declare class Notify {
    /**
     * Show a default Notification in the page
     * @param  {string}            message      The message
     * @param  {NotifyStatus   =            NotifyStatus.INFO} status   Status to style the message
     * @param  {[type]}            timeout=5000 Timeout
     * @param  {NotifyPosition =                                NotifyPosition.TOP_CENTER} pos        Position to display the message
     * @return {NotifyMessage}                  A object representing the message on the DOM
     */
    static show(message: string, status?: NotifyStatus, timeout?: number, pos?: NotifyPosition): NotifyMessage;
    private static getStatusString(status);
    private static getPositionString(position);
}
export interface DialogModal {
    show(): any;
    hide(): any;
}

To work around I manually modify the definition wrapping in a declaration of a library:

declare module 'myLib/notify' {
export declare enum NotifyStatus {
    INFO = 0,
    SUCCESS = 1,
    WARNING = 2,
    DANGER = 3,
}
export declare enum NotifyPosition {
    TOP_CENTER = 0,
    TOP_LEFT = 1,
    TOP_RIGHT = 2,
    BOTTOM_CENTER = 3,
    BOTTOM_LEFT = 4,
    BOTTOM_RIGHT = 5,
}
/**
 * Class for notifications
 */
export declare class Notify {
    /**
     * Show a default Notification in the page
     * @param  {string}            message      The message
     * @param  {NotifyStatus   =            NotifyStatus.INFO} status   Status to style the message
     * @param  {[type]}            timeout=5000 Timeout
     * @param  {NotifyPosition =                                NotifyPosition.TOP_CENTER} pos        Position to display the message
     * @return {NotifyMessage}                  A object representing the message on the DOM
     */
    static show(message: string, status?: NotifyStatus, timeout?: number, pos?: NotifyPosition): NotifyMessage;
    private static getStatusString(status);
    private static getPositionString(position);
}
export interface DialogModal {
    show(): any;
    hide(): any;
}
}

My tsconfig.json file is configured to look in jspm_packages for definitions

And then the compile stops warning and Atom editor provides intellisense.

There are two problems with this approach:

  1. The wraps is made manually (this is annoying)
  2. myLib is handwritten in the definition and can't be changed for other location, we must use import 'myLib/bla' and in reality 'myLib' can be overridden by SystemJS and jspm to other reference.

What I'm searching is a way to auto generate the typescript definition with the module declaration.

I came across this https://www.npmjs.com/package/autodts but I do not fully understand how it works, but it seams that can be use to automatically generate the definition file in this way.

Edit:

I create a gulp task to edit typescript definition files and add automatic add declare module. Here is how:

var gulp = require('gulp');
var runSequence = require('run-sequence');
var ts = require('gulp-typescript');
var paths = require('../paths');
var through2 = require('through2');
var concat = require('gulp-concat');
var insert = require('gulp-insert');
var rename = require('gulp-rename');
var merge = require('merge2');
var modify = require('gulp-modify');

var tsProjectAmd = ts.createProject('tsconfig.json', {module: 'amd'});
var tsProjectES6 = ts.createProject('tsconfig.json', {module: 'es6'});
var tsProjectCOMMONJS = ts.createProject('tsconfig.json', {module: 'commonjs'});
var tsProjectSystem = ts.createProject('tsconfig.json', {module: 'system'});

 var makeBuild = function(project, format, path, output){
  return function(){
    var tsResult =  gulp.src(path)
      .pipe(ts(project));
    return merge([
      tsResult.js.pipe(gulp.dest(output + format)),
      tsResult.dts.pipe(modify({
        fileModifier: function(file, content){
          // Split at /dist/ caracter
          var regex = new RegExp("/" + paths.dist);
          var split = file.path.split(regex);
          // Remove ".d.ts";
          var fileNameAndPath = split[1].slice(0, split[1].length - 5);
          if(fileNameAndPath != paths.packageName){
            return  'declare module "' + paths.packageName + "/" + fileNameAndPath + '" {\n' + content + '\n}';
          }else {
            return content;
          }

        }
      })).pipe(gulp.dest(output + format))
    ]);
  }
}


gulp.task('build-amd', makeBuild(tsProjectAmd, '', paths.source, paths.output));

paths.js

var path = require('path');
var fs = require('fs');

var appRoot = 'src/';
var outputRoot = 'dist/src/';
var pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));

module.exports = {
  root: appRoot,
  source: [appRoot + '**/*.ts','typings/**/*.ts'],
  html: appRoot + '**/*.html',
  style: outputRoot + 'css/**/*.css',
  styles: outputRoot + 'css/',
  less: ['less/theme/**/*.less','!less/theme/_variables.less'],
  fonts: 'less/uikit/src/fonts/*',
  images: 'images/**/*',
  output: outputRoot,
  doc:'./docs',
  apiDoc: './api-doc',
  dist: 'dist/',
  testsFixtures: 'test/fixtures/*',
  specsSrc: 'test/**/*.ts',
  specsOutput: 'dist/',
  packageName: pkg.name
};