jaredpalmer / tsdx

Zero-config CLI for TypeScript package development
https://tsdx.io
MIT License
11.22k stars 507 forks source link

Unable to compile Object.fromEntries -- polyfill required #968

Closed abhimanyuPathania closed 3 years ago

abhimanyuPathania commented 3 years ago

Current Behavior

I am building an NPM package for Node using the 'basic' template. I tried using Object.fromEntries in my code but get the error:

TypeError: Object.fromEntries is not a function

Upon inspecting the .cjs.development.js output it leaves Object.fromEntries untransformed which I guess are not supported in Node 10. I tried using build --target node which does not resolve it. Changing target on tsconfig also does not fix this.

Expected behavior

Typescript or Babel should be able to compile Object.fromEntries

Suggested solution(s)

Maybe a Babel plugin or polyfill is missing.

Additional context

Following is code snippet and transformed output Code:

export default class Platform {
  id: string;
  name: string;
  channelLabelVsIdMap: Record<string, number>;
  features: Record<string, Feature | undefined>;

  constructor(platform: TPlatformMetadata, metadata: TMetadataResponse) {
    const { features = [] } = metadata;
    this.id = platform.id;
    this.name = platform.name;
    this.channelLabelVsIdMap = platform.channelLabelVsIdMap;

    this.features = Object.fromEntries(
      features
        .filter((feature) => feature.platformId === platform.id)
        .map((feature) => [feature.id, new Feature(feature, metadata)]),
    );
  }
}

Transformed output tsdx build

var Platform = function Platform(platform, metadata) {
  var _metadata$features = metadata.features,
      features = _metadata$features === void 0 ? [] : _metadata$features;
  this.id = platform.id;
  this.name = platform.name;
  this.channelLabelVsIdMap = platform.channelLabelVsIdMap;
  this.features = Object.fromEntries(features.filter(function (feature) {
    return feature.platformId === platform.id;
  }).map(function (feature) {
    return [feature.id, new Feature(feature, metadata)];
  }));
};

Your environment

  System:
    OS: macOS 10.15.3
    CPU: (8) x64 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
    Memory: 792.30 MB / 16.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 10.22.0 - ~/.nvm/versions/node/v10.22.0/bin/node
    Yarn: 1.22.4 - /usr/local/bin/yarn
    npm: 6.14.6 - ~/.nvm/versions/node/v10.22.0/bin/npm
  Browsers:
    Chrome: 88.0.4324.96
    Firefox: 84.0.2
    Safari: 13.0.5
  npmPackages:
    tsdx: ^0.14.1 => 0.14.1
    typescript: ^4.1.3 => 4.1.3
agilgur5 commented 3 years ago

Problem Summary

So I looked this up and https://github.com/microsoft/TypeScript/issues/32803 and https://github.com/vercel/next.js/issues/16715#issuecomment-770875269 summarize this well.

Upon inspecting the .cjs.development.js output it leaves Object.fromEntries untransformed which I guess are not supported in Node 10. I tried using build --target node which does not resolve it.

Indeed, MDN reports this is as only compatible with Node 12+. I thought it was strange the --target node option didn't work for you because it will set @babel/preset-env to target Node 10 (and one or two other settings), but as those issues suggest, that's because Object.fromEntries has no transform, it must be polyfilled. TSDX does not set preset-env's useBuiltIns options as it is impure (pollutes global scope) and designed for apps not libraries.

Suggested Solution

TSDX currently leaves how to polyfill up to the library author as there are different preferences in the library ecosystem. But, what I'd suggest based on future state is to use babel-polyfills, specifically babel-plugin-polyfill-corejs3 with usage-pure set. Then I would suggest making core-js a dependency. This should act similarly to useBuiltIns, but be pure and designed for libraries. TSDX recently switched to using a similar method to polyfill regenerator in #795 .

Future State

I would like to move to add the core-js Babel plugin polyfill by default in the future, but per my comments in that PR, there's some legwork to make sure TSDX can detect when you need a polyfill and make sure you have the respective polyfill listed as a dep (since TSDX is a devDep, a peerDep on it would not suffice, and not all users need to polyfill either). We currently bundle in regenerator-runtime if necessary which is suboptimal to using a dep (potential for duplicates), so it would apply for that polyfill and likely similar for @babel/runtime as well.

Changing target on tsconfig also does not fix this.

See https://github.com/formium/tsdx/issues/951#issuecomment-757527184 . This is unsupported since we override to ESNext and transpile the rest with preset-env. Future state also includes having a warning for this 😅 (it's a somewhat common occurrence, along with downlevelIteration, importHelpers, etc).