bjyoungblood / es6-error

Easily-extendable error for use with ES6 classes

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to import using TypeScript on nodejs

gilbert opened this issue · comments

In Typescript, this line:

import ExtendableError from 'es6-error'

translates to this line:

var ExtendableError = require('es6-error').default

However, .default is undefined due to the incorrect way these two lines are written. A correct version of those two lines would read like this:

module.exports = ExtendableError;
module.exports.default = ExtendableError;

Try doing

import * as ExtendableError from 'es6-error'

@styfle That works dynamically, but then typescript doesn't recognize the typing and throws errors

Maybe it broke in TypeScript 2.1? See Breaking Changes

I'm pretty sure it's behaving as intended. When you type import * as ExtendableError, semantically that means you're collecting all the imports in a single object. That's different than importing the default, which is why you get different types between the two.

I'm not sure, but I'm wondering if this could be related to #28. I don't think exports being undefined is the problem, since node defines it automatically.

I'm not really familiar enough with TypeScript to debug further.

You don't need to know typescript; I've included the JavaScript that typescript compiles to in the issue description (just edited it for more clarity).

lib/index.js is generated by Babel, so there's not a whole lot I can do about the output.

I think the issue is being caused by the output generated by babel-plugin-add-module-exports, but if I remove that, it will break for a pretty large number of users.

To try to address #28, I added an additional build at lib/index.ts.js which doesn't use that plugin. Not a great fix, but I'm not sure what else to do. Is there a way in package.json to have TypeScript use a different entry point (similar to how rollup uses jsnext:main)?

You probably can't, since it's just a plain require call.

I think a workaround would be copying the index.d.ts file to index.ts.d.ts so typescript users could at least write import ExtendableError from 'es6-error/lib/index.ts'

I suggest to change typedefinitions to

export = Error

It would allows to import * as ExtendableError from "es6-error", and import ExtendableError from "es6-error" with --allowSyntheticdefaultImports

How about just not exporting as default, so that the import looks like this:

import { ExtendableError } from 'es6-error';

Default exporting isn't completely working in Typescript yet anyway. For example, a default exported class can't have nested interfaces or classes, but a non-default one can.

Also, I'm hearing from others that refactoring tools don't work well with default exports.

Oh never mind. That is the proposal -- to export it both ways.

My Typescript works with v4.0.2 when I locally link to es6-error, but not when I install v4.0.2 from NPM. What's the deal? Sorry, I'm trying to get up to speed on Typescript and such.

I just posted a PR that fixes the problem, but it's just a temporary fix until Babel does the right transpilation. All we had to do was add this line:

ExtendableError.default = ExtendableError; // for Typescript

This allows Typescript to import default with no problem.

Meanwhile, Typescript can successfully import ExtendableError as follows:

const ExtendableError: (typeof Error) = require('es6-error');

PR #35 enables the following to also work in Typescript:

import ExtendableError from 'es6-error';

The above interim workaround (without PR #35) won't allow you to generate type declarations for exported subclasses of ExtendableError because the declaration is local to the module.

To be able to compile Typescript with es6-error@4.0.2 and still generate declarations, you'll need to do the following:

export class ExtendableError extends (<typeof Error>require('es6-error')) {

    constructor(...args: any[]) {
        super(...args);
    }
}

Upgrading to node v8 seems to obviate the need for es6-error, but these mods still help older modules that use es6-error be compatible with Typescript.

This should be fixed by #36. Please reopen if that is not the case.

I think babel-plugin-add-module-exports actually does something wrong. The final lines of the output look like this:

exports.default = ExtendableError;
module.exports = exports['default'];

So babel is building the exports object to contain everything it should and then the plugin replaces it with ExtendableError. In node-land module.exports overrules exports, so bad things happen:

  • in a bundler import ExtendableError from "es6-error"will likely hit the jsnext:main, which correctly defines a default export
  • in plain node.js require('es6-error') will hit the generated main, which overrides module.exports

By itself this sounds fine but this breaks down in 3rd party code using es6-error in a jsnext project. The 3rd party code uses require (which would normally work fine with the module.exports but babel will happily load the jsnext version (which doesn't provide a node-style export).

This actually caused us some trouble in arangojs:

  • node.js would work fine
  • create-react-app would work fine
  • angular-cli would break

In the end we had to create a wrapper module that sanitises the inconsistent exports:

https://github.com/arangodb/arangojs/blob/v6.1.1/src/util/error.ts:

let ExtendableError = require("es6-error");
ExtendableError = ExtendableError.default || ExtendableError;
export default ExtendableError as typeof Error;

The module itself was necessary to make sure the dependency would always be an importable module (because TypeScript imports look for a default property). But to avoid accidentally exporting the jsnext module as an object (i.e. {"default": ExtendableError}) in angular-cli, the second line had to be added to make getting the wrong exports via require wouldn't mess up the module export.

This issue actually caused us to remove esmodule/jsnext support from arangojs after introducing it in 6.0.0.

EDIT: The workaround in #29 (comment) would solve this problem by ensuring default is always exported, even when module.exports is set to the error type itself. Although this would of course add a noisy extra property to ExtendableError.