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

Any roadmap on supporting Object.assign? #3429

Closed
unional opened this issue Jun 8, 2015 · 47 comments
Closed

Any roadmap on supporting Object.assign? #3429

unional opened this issue Jun 8, 2015 · 47 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@unional
Copy link
Contributor

unional commented Jun 8, 2015

in 1.6 or 2.0?

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Jun 8, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Jun 8, 2015

not sure what you are looking for. if you mean the typings, it is already defined in lib.es6.d.ts as

    /**
      * Copy the values of all of the enumerable own properties from one or more source objects to a 
      * target object. Returns the target object.
      * @param target The target object to copy to.
      * @param sources One or more source objects to copy properties from.
      */
    assign(target: any, ...sources: any[]): any;

i you mean the implementation, TypeScript does not include pollyfills, you can always include yours, for instance Mozilla has a pollyfill for it.

If you are asking about the the mixin pattern typingings, it is something that we always had on the roadmap for 2.0.

@mhegazy mhegazy closed this as completed Jun 8, 2015
@unional
Copy link
Contributor Author

unional commented Jun 9, 2015

Thanks. I mean writing Object.assign(...) in TypeScript which transpile to es3/es5/es6. So it is on the roadmap for 2.0.

👍

@Izhaki
Copy link

Izhaki commented Jan 20, 2016

@unional I may have misunderstood something. But I don't think your conclusion is correct.

ES6 anyway supports Object.assign, so if your target is ES6, you should be able to write it in TypeScript anyhow.

But if you transpile to es5 and below, you'd still need a polyfill, because es5 doesn't have Object.assign.

As a quick workaround consider this polyfill and typing:

interface ObjectConstructor {
    assign(target: any, ...sources: any[]): any;
}

if (typeof Object.assign != 'function') {
  (function () {
    Object.assign = function (target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var output = Object(target);
      for (var index = 1; index < arguments.length; index++) {
        var source = arguments[index];
        if (source !== undefined && source !== null) {
          for (var nextKey in source) {
            if (source.hasOwnProperty(nextKey)) {
              output[nextKey] = source[nextKey];
            }
          }
        }
      }
      return output;
    };
  })();
}

@unional
Copy link
Contributor Author

unional commented Jan 20, 2016

Thanks for digging up this issue. It was quite a while ago. :) Yes, I now understand that it simply requires a polyfill for es5.

Thanks again!

@c9s
Copy link

c9s commented May 26, 2016

Will we compile this polyfill when target is set to es5? I mean, since the compilation target is set to es5, then this should be able to work under es5 environment.

@basarat
Copy link
Contributor

basarat commented May 26, 2016

Will we compile this polyfill when target is set to es5

Yes, as a ponyfill 🌹 🐴

@prashaantt
Copy link

I'm sorry I don't understand. ES6 supports Object.assign so Babel transpiles it to a polyfill for ES5 targets. I'm confused why TypeScript doesn't do the same, just as it does for other ES6 features.

@devoto13
Copy link

devoto13 commented Jul 5, 2016

@prashaantt TypeScript doesn't provide any polyfills and it's by design. If you want to use Object.assign or any other primitive/method added by newer standard you should either:

  • use polyfill library (like core-js) with corresponding typings
  • target the standard when the feature was added (e.g. "target": "es6" in your tsconfig.json)

@prashaantt
Copy link

prashaantt commented Jul 9, 2016

@devoto13 Thanks, core-js works well.

I just had a silly question. After npm installing and typings installing core-js I begin to get IntelliSense for the polyfilled methods. But I will still need to actually import those methods in every module where I'm using them or the compiled code wouldn't include the polyfills, right?

@jesseschalken
Copy link
Contributor

@prashaantt Once core-js/shim is required by anything, Object.assign will be available globally from that point onwards. I recommend putting import 'core-js/shim'; at the top of your main module/entry point.

@prashaantt
Copy link

Thanks @jesseschalken. As a follow up, isn't importing the whole shim going to bloat up my bundle? Or will tsc or ts-loader be smart enough to only pick up the things that actually get used in my code?

@jesseschalken
Copy link
Contributor

jesseschalken commented Jul 9, 2016

@prashaantt It depends on the browsers you're targeting. You already know Object.assign is not supported by a browser you're targeting, who knows what else isn't? You need the whole shim in your bundle if you want the widest browser support.

If you only want the polyfill for Object.assign, you can import 'core-js/modules/es6.object.assign';, and add more things as you discover you need them (see shim.js in core-js for a list, also the docs). Webpack will follow the require graph and only include the modules necessary.

@jesseschalken
Copy link
Contributor

If you're already using Babel, I recommend using import 'babel-polyfill'; instead of using core-js directly. It includes core-js/shim but also regenerator-runtime for generators/async-await.

@prashaantt
Copy link

Thanks for the tips, though we should have full generators support any moment now — literally the last hurdle to 2.0!

@aluanhaddad
Copy link
Contributor

I'm sorry I don't understand. ES6 supports Object.assign so Babel transpiles it to a polyfill for ES5 targets. I'm confused why TypeScript doesn't do the same, just as it does for other ES6 features.

@prashaantt What do you mean when you say that Babel transpiles Object.assign? It is just a function. You can add the polyfill, ponyfill, or write it yourself, and you can use it any environment - ES 3, 5.1, 6, etc.

@avegancafe
Copy link

avegancafe commented Dec 14, 2016

@aluanhaddad My understanding of what babel does is if you specify es5 as your target and use Object.assign, it automatically includes a polyfill for Object.assign, and doesn't if you don't use it. It would be nice if typescript did the same thing, as its claimed that it's a "superset of es2015", which isn't really true if it doesn't provide functionality to transpile to older targets. (I could be wrong though)

@avegancafe
Copy link

@devoto13 if your target is es5, then you should at the very least throw a warning that Object.assign isn't supported in es5. It makes zero sense to have it be completely valid and not telling the programmer that you need some random polyfill.

@RyanCavanaugh
Copy link
Member

@kyleholzinger what you've described (targeting ES5 means Object.assign isn't available) is already the behavior.

@devoto13
Copy link

devoto13 commented Dec 14, 2016

@kyleholzinger It indeed does throw error. If you create folder with two following files:

// test.ts
let t = Object.assign({}, {});
// tsconfig.json
{ "target": "es5" }

And then run tsc on it. You'll get following error:

$ tsc
test.ts(1,16): error TS2339: Property 'assign' does not exist on type 'ObjectConstructor'.

In your project you probably include typings for some polyfill, but don't include polyfill implementation, that's why it fails.

@avegancafe
Copy link

I understand that it's the current behavior haha. My point is that if you specify the target as es5, it'd be nice if typescript gave you a meaningful error beyond "it's not on the constructor".

@prashaantt
Copy link

prashaantt commented Dec 14, 2016

@kyleholzinger FWIW, TypeScript 2.1 now supports the ES6 (ES7?) object rest/spread so I personally find lesser reason to bother about Object.assign anymore. That with native generators means I don't need any polyfills in most of my projects.

@avegancafe
Copy link

That's true. It'd just be nice to not leave out language features. It'd be awesome if Object.assign wasn't browser dependent and typescript warned you if you weren't using a polyfill.

@prashaantt
Copy link

If your tsconfig is set right, TypeScript warns you about assign not being available for < ES6 by not giving you the option to autocomplete that function name on Object in the first place. If you persist in painstakingly writing it out by hand, you'll see the red squiggles. If you ignore that, tsc will give you the above error. But if you willfully ignore that too, you rightfully deserve your doom. ;)

@avegancafe
Copy link

right, my only point is in the es2015 spec, so it should be in typescript ;)

@johnbendi
Copy link

johnbendi commented Dec 24, 2016

How do I use Object.assign on node while targeting es5 ? That is, code is to run on server and not in the browser. Do I use polyfills too and how?

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Dec 24, 2016

@johnbendi
It depends on the version of node, which is to say that it depends on the runtime as is the case with all polyfillable functionality.

Here is how you can do test if your runtime supports Object.assign

$ Node 
> Object.assign({ to: 'world' }, { message: 'Hello there!' })
{ to: 'world', message: 'Hello there!' }

If the above works all you need to do is include "es2017.object" in the "compilerOptions"."lib" property of your tsconfig.json.

If it the above fails, add a polyfill like this one, which is written in TypeScript.

// polyfill-object-assign.ts

if (typeof Object.assign !== 'function') {
  Object.assign = function (target, ...args) {

    if (!target) {
      throw TypeError('Cannot convert undefined or null to object');
    }
    for (const source of args) {
      if (source) {
        Object.keys(source).forEach(key => target[key] = source[key]);
      }
    }
    return target;
  };
}

And import it with

import './polyfill-object-assign';

And also make the same changes to your tsconfig.json as for the runtime supported case.

I hope that helps

@johnbendi
Copy link

@aluanhaddad thanks a lot for the insights. My node does support Object.assign based on the experiment you asked me to run. But even after adding the "compilerOptions": { "lib": ["es2017.object"] } I still get the squiggles. Should I just ignore it or is there something I can do to make it go away.

@johnbendi
Copy link

@aluanhaddad never mind. It works well now.

@johnbendi
Copy link

johnbendi commented Dec 24, 2016

@aluanhaddad I previously had "compilerOptions": { "lib": ["es2017.object", "es6"] } when I was getting the squiggles. Removing es6 seemed to resolve but rerunning my gulp script again suddenly produces a new set of errors.
My tsconfig.json :

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "node",
        "lib": ["es2017.object"],
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "sourceMap": true,
        "inlineSources": true,
        //"noImplicitAny": true,
        "declaration": true,
        "noFallthroughCasesInSwitch": true,
        // "noImplicitReturns": true,
        "removeComments": true,
        "stripInternal": true,
        "outDir": "dist"
    },
    "files": ["src/main.ts"],
    "include": [
        "src/**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

Sample of my new errors:

error TS2318: Cannot find global type 'Array'.
error TS2318: Cannot find global type 'Boolean'.
error TS2318: Cannot find global type 'Function'.
error TS2318: Cannot find global type 'IArguments'.
error TS2318: Cannot find global type 'Number'.
error TS2318: Cannot find global type 'RegExp'.
error TS2318: Cannot find global type 'String'.
error TS2339: Property 'bind' does not exist on type '(message?: any, ...optionalParams: {}) => void'.
error TS2339: Property 'bind' does not exist on type '(message?: any, ...optionalParams: {}) => void'.
error TS2322: Type '{}' is not assignable to type
error TS2304: Cannot find name 'Promise'.

So is there a way to use your recommendation and still get typescript to compile correctly?

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Dec 25, 2016

@johnbendi yes, certainly.

Use I was simply suggesting adding the specific entry "es2017.object" because of the specificity of your request.
I believe "lib": ["es6"] is no longer correct and that it should be "lib": ["es2015"].
Try "lib": ["es2015", es2017.object"] or just "lib": ["es2017"].

@himdel
Copy link

himdel commented Jun 23, 2017

I think what's missing is a clean way to say "I have an es6 polyfill and it's not your bussiness typescript, just assume I do" :).

Because, setting target: "es6" does that, but presumably can also generate code using non-polyfillable es6 features.

Requiring core-js explicitly essentially forces you to have the shim, you can't have shimmy and non-shimmy builds because TS will complain.

Adding node_modules/typescript/lib/lib.es6.d.ts to files in tsconfig.json does that, but.. does not look so clean... (or am I missing an obvious way of achieving that?)

@aluanhaddad
Copy link
Contributor

@himdel just use

{
  "compilerOptions": {
    "lib": [
      "es2015"
    ]
  }
}

It works perfectly - I've been doing it for months.

@kitsonk
Copy link
Contributor

kitsonk commented Jun 24, 2017

Or any subset you wish:

{
  "compilerOptions": {
    "lib": [
      "es2015.core",
      "es2016.array.include"
    ]
  }
}

@danez
Copy link

danez commented Apr 13, 2018

I still cannot figure out how to do this. When i have target: "es5" tsc will always transform Object.assign into some call to a polyfill. Adding libs does not change anything at all for me.

In my case I want arrow functions to be converted to normal functions, but leave static calls like Object.assign and Array.includes untouched.

tsc should have a flag to tell it to only transpile syntax features and not polyfill features like static methods Object, Array etc.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Apr 13, 2018

When i have target: "es5" tsc will always transform Object.assign into some call to a polyfill.

@danez TypeScript won't modify a call to Object.assign. It sounds like you might be running your code through Babel?

@danez
Copy link

danez commented Apr 20, 2018

@RyanCavanaugh It does. Babel is not involved in the process.
What it does is basically transforming this:

var a = Object.assign({}, {});

into this

var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
var a = _assign({}, {});

@RyanCavanaugh
Copy link
Member

@danez Can you post an actual repro? The compiler did not emit that code

@danez
Copy link

danez commented Apr 20, 2018

I don't have a repo, but this is the tsconfig:

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "module": "ES2015",
    "target": "es5",
    "outDir": "js/lib/es"
  },
  "include": [
    "js/**/*.ts",
  ],
  "exclude": [
    "**/__tests__/**/*.ts"
  ]
}

and then i simply call tsc

@RyanCavanaugh
Copy link
Member

@danez I'm afraid you'll have to post an actual repro.

C:\Throwaway\oat>type a.ts
var a = Object.assign({}, {});

C:\Throwaway\oat>type tsconfig.json
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "module": "ES2015",
    "target": "es5",
    "outDir": "js/lib/es"
  },
  "include": [
    "*.ts",
  ]
}
C:\Throwaway\oat>tsc
a.ts(1,16): error TS2339: Property 'assign' does not exist on type 'ObjectConstructor'.

C:\Throwaway\oat>type js\lib\es\a.js
var a = Object.assign({}, {});

@unional
Copy link
Contributor Author

unional commented Apr 20, 2018

Maybe related: #12901

@kitsonk
Copy link
Contributor

kitsonk commented Apr 29, 2018

@unional in that it is working properly. The compiler has a helper for providing syntactical support, but it does not ever rewrite functionality on globals. @danez that helper is not ever accessible by TypeScript code.

const foo = { foo: 'bar' };
const bar = { ...foo };

will emit:

var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
var foo = { foo: 'bar' };
var bar = __assign({}, foo);

I don't think you can provide an example where Object.assign() gets rewritten to __assign, it only gets used in object spread syntactical support.

@danez
Copy link

danez commented Apr 30, 2018

@kitsonk Yes I was using object spread, you are right. So would be nice if object-spread would just be simply transformed to Object.assign

@kitsonk
Copy link
Contributor

kitsonk commented Apr 30, 2018

@danez it is when you target something that supports Object.assign() (e.g. target is es2015+).

For example, this:

const foo = { foo: 'bar' };
const bar = { ...foo };

Will output:

const foo = { foo: 'bar' };
const bar = Object.assign({}, foo);

@danez
Copy link

danez commented Apr 30, 2018

@kitsonk Yes I know, but I'm targeting es5 syntax with all ES2017+ globals being polyfilled by core-js. So what I'm referring to what would be nice is a mode that outputs es5 syntax but assumes all builtins are available. Similar to what babel is doing with the useBuiltins option: https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx#usebuiltins

@RyanCavanaugh
Copy link
Member

In general we don't go out of our way to give special support to "mixed" runtime targets, but there are other options available.

You can inject __assign into the global scope (along with any other helpers, e.g. __extends) and run with --noEmitHelpers.

@avegancafe
Copy link

Yes I know, but I'm targeting es5 syntax

I feel like this is the main problem with not shimming features like Object.assign. Gotta take a stance one way or another-- you can't replace its usage with __assign when using a spread but not when you call it directly, it's confusing as hell

@kitsonk
Copy link
Contributor

kitsonk commented Apr 30, 2018

you can't replace its usage with __assign when using a spread but not when you call it directly, it's confusing as hell

I can understand you feel it is confusing, but it isn't if you keep the mantra, TypeScript provides syntatical rewrites not functional polyfills. TypeScript is full consistent in how it behaves, and it is opinionated about what it does, and 99% of the time it has no impact on the end user.

As @RyanCavanaugh says, it is possible to leverage a set of polyfills, plus tslib, with --noEmitHelpers plus a global script that says:

__assign = Object.assign;

But that is really "bonus round" TypeScript and arguable that it would provide any real world measurable performance improvement.

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests