Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing __importStart and __importDefault calls when synthetic is required #113

Closed
alejandroclaro opened this issue Sep 2, 2020 · 2 comments

Comments

@alejandroclaro
Copy link

  • Version: 1.3.3
  • Rollup Version: 2.26.5
  • Does it work with tsc (if applicable): Yes

Some CommonJS modules are developed exporting function or other objects as export.default (for example 'bindings'). Typescript synthesize a 'default' property to make it compliant with es6 module specification: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#support-for-import-d-from-cjs-from-commonjs-modules-with---esmoduleinterop

For example:

export async function importFoo(path: string): Promise<Addon> {
  const bindings = await import('bindings');
  const addon = bindings.default('addon') as Addon;

  return addon;
}

generates this using 'tsc':

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const util_1 = tslib_1.__importDefault(require("util"));
async function importAddon(path) {
    const bindings = await Promise.resolve().then(() => tslib_1.__importStar(require('bindings')));
    const addon = bindings.default('addon');
    return addon;
}

However, this rollup plugin is generating this:

'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var util = require('util');

async function importAddon(path) {
    const bindings = await Promise.resolve().then(function () { return require('bindings'); });
    const addon = bindings.default('addon');

    return addon(path);
}

Expected Behavior

The esModuleInterop functions (__importStar and __importDefault) are preserved and default import are possible without workaround or direct invocation to 'require' function.

Actual Behavior

This produces a runtime error due to the fact that 'default' is not defined. It's defined in the code generated by 'tsc' in the call to tslib_1.__importStar.

@wessberg
Copy link
Owner

wessberg commented Sep 2, 2020

Hi there.

I think a little explainer is needed first:

You might already know this, but internally, Rollup builds up a module graph of ES modules and requires everything to be modules. Plugins like @rollup/plugin-commonjs works by trying to rewrite a CommonJS module to ESM such that Rollup can understand it.

And, given that under these circumstances, TypeScript is a client of Rollup, it is Rollup - not TypeScript - that is the decider of which module format(s) to target based on your Rollup config.

Because of this, no matter what module format you've decided to target in your tsconfig, rollup-plugin-ts will always target ES modules. It is then Rollup that as one of the final steps in the compilation pipeline emits to whatever module system(s) you decided in your rollup config.

That is why you are experiencing differences in behavior between tsc and this plugin. The esModuleInterop flag to TypeScript won't actually make a difference here, since TypeScript isn't emitting CommonJS modules. What you are seeing here instead is Rollup rewriting await import('...') to await Promise.resolve().then(function () { return require('...'); });.

That said, I'm actually somewhat surprised to learn that you're using Rollup v2.26.5, because since v2.24.0 Rollup has made interoperability with CommonJS a little more flexible and arguably better, and here's the output I get from Rollup v2.26.5 with no customizations to the interop option:

'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopNamespace(e) {
        if (e && e.__esModule) { return e; } else {
                var n = Object.create(null);
                if (e) {
                        Object.keys(e).forEach(function (k) {
                                if (k !== 'default') {
                                        var d = Object.getOwnPropertyDescriptor(e, k);
                                        Object.defineProperty(n, k, d.get ? d : {
                                                enumerable: true,
                                                get: function () {
                                                        return e[k];
                                                }
                                        });
                                }
                        });
                }
                n['default'] = e;
                return Object.freeze(n);
        }
}

async function importFoo(path) {
    const bindings = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('bindings')); });
    const addon = bindings.default('addon');
    return addon;
}

I think this should be documented, so I've added an explainer for this in the README. I hope it makes sense

@alejandroclaro
Copy link
Author

Thank you @wessberg ,

The explanation is very clear. I got it now, I thought that was maybe the plugin was changing the result and the behavior of typescript when esModuleInterop is specified.

After your explanation, I found the issue. By mistake in some packages, the building scripts were setting the output.interop variable to 'false'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants