Pnpjs: polyfill-ie11 "Out of stack space"

Created on 9 Jan 2020  路  27Comments  路  Source: pnp/pnpjs

Category

  • [ ] Enhancement
  • [x] Bug
  • [ ] Question
  • [ ] Documentation gap/issue

Version

Please specify what version of the library you are using: [2.0.0]

Please specify what version(s) of SharePoint you are targeting: [SPO 16.0.0.19520]

Expected / Desired Behavior / Question

PnP polyfill package should allow sp and graph packages to function to some degree in IE.

The end goal is to query SPO/Graph using xstate v4. To that end, I've tried building my own polyfill bundle from https://polyfill.io/v3/url-builder. The xstate community have figured out how to make their tool work in IE11 using the aforementioned polyfill builder so it would be useful to find out exactly what PnP needs to function. This is just for context. I'm happy to open other issues and concentrate on the one in the title for now.

Observed Behavior

Simply importing the polyfill package into a pristine 1.9.1 SPFx webpart blows up the stack.

Steps to Reproduce

  1. Get IE11
  2. Clone
  3. Build and deploy to a modern page in SPO
  4. View page in IE11 with F12 open
  5. To see my attempts at polyfilling the library, simply do this:
  6. comment out the PnP polyfill import
  7. enable the dormant code in onInit method
  8. this code contains polyfills for the following:
Array.from
Array.isArray
Array.prototype.@@iterator
Array.prototype.fill
Array.prototype.find
Array.prototype.includes
es5
Intl
Map
matchMedia
Number.isNaN
Object.assign
Object.create
Object.entries
Object.values
Promise
Reflect
Reflect.apply
Reflect.defineProperty
Reflect.get
Reflect.has
Reflect.set
Reflect.setPrototypeOf
Set
Symbol
Symbol.iterator
  • Notice that the unhandled exception has been resolved but the web object is undefined (no idea which polyfills I've missed out) :supersadface:
IE11 tooling fixed bug

Most helpful comment

This is my first attempt at a workaround:

Helper setup function that takes care of IE11 detection

https://gist.github.com/semopz/46ba5c6da6e957b327e9acdbe430c68d
I found that WeakMap, Map and Set must be overwritten and you can choose to leave WeakSet untouched. So pretty much the same findings as in this article from early 2019

Usage

  1. Import the above file at the top of your WebPart.ts. The import must appear before any other imports that use the PnP library otherwise you will either get a Symbol is undefined or Reflect.defineProperty is undefined. So just stick it at the top to be safe.
import pnpSetupHelper from './PnPSetupHelp/PnPSetupHelper';
import * as React from 'react';
// rest of your WebPart.ts file
  1. Override your WebPart's onInit method as follows:
    >Note:
    You don't need to specify the ie11 flag.
    This takes care of the setup for both sp and graph.
protected async onInit() {
    await super.onInit();
    await pnpSetupHelper({
      spfxContext: this.context,
      sp: {
        baseUrl: this.context.pageContext.web.absoluteUrl // or whatever your remote web url might be
      }
    });
  }

I've not used this in anger yet so issue reports and suggestions are welcome.

All 27 comments

Please make sure you also enable IE mode. Otherwise, some parts (e.g. V2 search) which can't be polyfilled will cause a similar effect.

Please make sure you also enable IE mode. Otherwise, some parts (e.g. V2 search) which can't be polyfilled will cause a similar effect.

Haven't even gotten to that part. The error happens simply by importing the polyfills.

In the example web part provided, I have tried to do that when I provide my own polyfills

I am a bit confused - the polyfills we load are from (mostly) the corejs package we just repackage them to make things easier. I doubt there is a stack overflow issue with their code but its possible. I think it more likely looking at your code that it is something to do with all the dynamic imports you are executing and you may have inadvertently created a loop with that.

Yes, it is very weird. It probably doesn't have anything to do core-js per se and it might be a SharePoint issue instead.

The code I added was there just to show the things I've tried (it allows the webpart to run but PnP web object remains empty for some reason).

I've just spun up another webpart which has only two changes - 1) added the polyfills to package.json and the import statement on top of the *webpart.ts file.

Does this work for you without errors?
Just pnp polyfill

I ran into this yesterday using the polyfill approach in the docs:

import "@pnp/polyfill-ie11";

I reverted to using:

import "core-js/es6/array";
import "es6-map/implement";
import "core-js/modules/es6.array.find";

and was all set. I didn't have time to figure out what was going on.

In my case, SPFx 1.9.1, PnPjs 1.3.7.

Hmmm, no idea. I tested it but only in the most simple case. Maybe our polyfills and theirs are interfering somehow? My understanding is that those shouldn't re-patch if a patch already exists so maybe we've gotten stuck in some kind of race condition loop. Sigh.

@sympmarc, could you let me know which version of core-js and es6-map works for you?

Exploring the race condition hypothesis, I tried dynamically importing the polyfills with a sufficient delay to allow SPO to load whatever it needs to load. As soon as the import is done, the stack blows up. So I think SPO polyfills a feature with bad code that where core-js might then be using that feature.

It looks like the loop is happening here

function n(t){if(!this||this.constructor!==n)return new n(t);

at https://spoprod-a.akamaihd.net/files/sp-client/sp-pages-assembly_en-us_4b9a6cb22e75bd2c68f137d9abb58046.js:1:11219

Not sure if there's anyway of figuring out what's happening when all we have to go on is a proprietary uglified bundle.

I think I've isolated the issue to polyfills that SPO implements for WeakMap, Map, Set and WeakSet.
I'll try to write some something that overwrites those features with something from core-js instead.
note: RECURSIVE_CALLER and RECURSIVE_FUNCTION are names I came up with. No idea what they're called in Microsoft's source code.

// snip
function (t) {
  "use strict";
  var e, r = Object.defineProperty,
    n = function (t, e) {
      return t === e || t != t && e != e
    };

  function RECURSIVE_CALLER(t, e) {
    function RECURSIVE_FUNCTION(t) {
      if (!this || this.constructor !== RECURSIVE_FUNCTION) return new RECURSIVE_FUNCTION(t);
      this._keys = [], this._values = [], this._itp = [], this.objectOnly = e, t && function (t) {
        this.add ? t.forEach(this.add, this) : t.forEach(function (t) {
          this.set(t[0], t[1])
        }, this)
      }.call(this, t)
    }
    return e || r(t, "size", {
      get: y
    }), t.constructor = RECURSIVE_FUNCTION, RECURSIVE_FUNCTION.prototype = t, RECURSIVE_FUNCTION
  }

  function i(t) {
    return this.has(t) && (this._keys.splice(e, 1), this._values.splice(e, 1), this._itp.forEach(function (t) {
      e < t[0] && t[0]--
    })), -1 < e
  }

  function s(t) {
    return this.has(t) ? this._values[e] : void 0
  }

  function a(t, r) {
    if (this.objectOnly && r !== Object(r)) throw new TypeError("Invalid value used as weak collection key");
    if (r != r || 0 === r)
      for (e = t.length; e-- && !n(t[e], r););
    else e = t.indexOf(r);
    return -1 < e
  }

  function u(t) {
    return a.call(this, this._values, t)
  }

  function c(t) {
    return a.call(this, this._keys, t)
  }

  function h(t, r) {
    return this.has(t) ? this._values[e] = r : this._values[this._keys.push(t) - 1] = r, this
  }

  function f(t) {
    return this.has(t) || this._values.push(t), this
  }

  function l() {
    (this._keys || 0).length = this._values.length = 0
  }

  function p() {
    return d(this._itp, this._values)
  }

  function d(t, e, r) {
    var n = [0],
      o = !1;
    return t.push(n), {
      next: function () {
        var i, s = n[0];
        return !o && s < e.length ? (i = r ? [e[s], r[s]] : e[s], n[0]++) : (o = !0, t.splice(t.indexOf(n), 1)), {
          done: o,
          value: i
        }
      }
    }
  }

  function y() {
    return this._values.length
  }

  function m(t, e) {
    for (var r = this.entries(); ;) {
      var n = r.next();
      if (n.done) break;
      t.call(e, n.value[1], n.value[0], this)
    }
  }
  "undefined" == typeof WeakMap && (t.WeakMap = RECURSIVE_CALLER({
    delete: i,
    clear: l,
    get: s,
    has: c,
    set: h
  }, !0)), "undefined" != typeof Map && "function" == typeof (new Map).values && (new Map).values().next || (t.Map = RECURSIVE_CALLER({
    delete: i,
    has: c,
    get: s,
    set: h,
    keys: function () {
      return d(this._itp, this._keys)
    },
    values: p,
    entries: function () {
      return d(this._itp, this._keys, this._values)
    },
    forEach: m,
    clear: l
  })), "undefined" != typeof Set && "function" == typeof (new Set).values && (new Set).values().next || (t.Set = RECURSIVE_CALLER({
    has: u,
    add: f,
    delete: i,
    clear: l,
    keys: p,
    values: p,
    entries: function () {
      return d(this._itp, this._values, this._values)
    },
    forEach: m
  })), "undefined" == typeof WeakSet && (t.WeakSet = RECURSIVE_CALLER({
    delete: i,
    add: f,
    clear: l,
    has: u
  }, !0))
}

// snip

Nice digging! I am absolutely swamped but will try and have a look at this. Not sure what we can do, but maybe updated docs if nothing else.

@sympmarc, could you let me know which version of core-js and es6-map works for you?

To be honest, I'm not sure how to check!

@sympmarc, no worries, I think I've got it now. Just for reference, you can find the version of your external libraries from package.json. If not listed there, you can Ctrl+click (in VS Code) on the library path in your import statement line which should take you to the library's definition folder (or just find it in node_modules). Traverse up the folder tree until you find the library's package.json which will list its version (or its dependencies' versions). General rule of thumb is not to use any libraries that aren't listed in your own package.json anyway.

This is my first attempt at a workaround:

Helper setup function that takes care of IE11 detection

https://gist.github.com/semopz/46ba5c6da6e957b327e9acdbe430c68d
I found that WeakMap, Map and Set must be overwritten and you can choose to leave WeakSet untouched. So pretty much the same findings as in this article from early 2019

Usage

  1. Import the above file at the top of your WebPart.ts. The import must appear before any other imports that use the PnP library otherwise you will either get a Symbol is undefined or Reflect.defineProperty is undefined. So just stick it at the top to be safe.
import pnpSetupHelper from './PnPSetupHelp/PnPSetupHelper';
import * as React from 'react';
// rest of your WebPart.ts file
  1. Override your WebPart's onInit method as follows:
    >Note:
    You don't need to specify the ie11 flag.
    This takes care of the setup for both sp and graph.
protected async onInit() {
    await super.onInit();
    await pnpSetupHelper({
      spfxContext: this.context,
      sp: {
        baseUrl: this.context.pageContext.web.absoluteUrl // or whatever your remote web url might be
      }
    });
  }

I've not used this in anger yet so issue reports and suggestions are welcome.

I'm also getting the "out of stack space" error in IE11 on SPFx 1.9.1, PnPjs 1.3.7.

Polyfilling using
es-map v0.1.5
core-js v2.6.10

worked for my functionality

what version of the polyfill are you using? There is a 1.* version and a 2.* version now. I haven't tested the 1.* lib with the 2.* polyfill - maybe that is the hangup here?? I can add a peer dep so there is an warning produced.

Finally able to have a look and I can indeed reproduce this bug now. We don't use the Weak* varieties of Map so I am going to try restricting the polyfills further and see if that helps.

I've updated my gist from before https://github.com/pnp/pnpjs/issues/1002#issuecomment-572975881

I've added a check before clobbering the offending global objects. I found that without this check, flipping the modern page modes (to and from Edit) causes the whole page (sans chrome) to melt. This is because onInit is run again but the polyfills are no longer required (SharePoint page router re-renders the contents but doesn't reload the window).

I really don't like these workarounds - just another thing to go wrong in future updates (by which time, the workarounds might not even be required)

what version of the polyfill are you using? There is a 1.* version and a 2.* version now. I haven't tested the 1.* lib with the 2.* polyfill - maybe that is the hangup here?? I can add a peer dep so there is an warning produced.

Sorry for the late reply, I was using v2 of the pnp/polyfill-ie11

Also found this issue that seems to be the same thing. It feels like an issue with SPFx vs what we are doing since we just include the corejs stuff. I hate to add some hacky stuff to the polyfill package but I am seeing what we can do on our end because I have zero hope that the SPFx stuff will be updated to address this.

I agree with you. @waldekmastykarz marked that ticket as a question (not a bug) and @andrewconnell accepted a workaround as the solution and closed the ticket. I guess we're on our own with this one

Ok, fixed this by falling back to the es6-map polyfill we used in v1 of the polyfill lib as for whatever reason it doesn't cause this conflict. Just published a new polyfill package 2.0.1 - give that a try and things should now work.

Just caught this issue, using the workaround from @sympmarc (https://github.com/pnp/pnpjs/issues/1002#issuecomment-572601599) fixed it for me.

@garrytrinder - can you try the new polyfill lib and see if that solves it for you? Would rather folks didn't have to do some weird workaround at all.

I just tested in the solution where I had the issue above.

First, I ran:

npm install --save @pnp/polyfill-ie11@latest

which gave me:

+ @pnp/[email protected]

then replaced:

// import "core-js/es6/array";
// import "es6-map/implement";
// import "core-js/modules/es6.array.find";
import "@pnp/polyfill-ie11";

At least in debug mode, things ran fine. I think you've got it!

Thanks @sympmarc! That's all you should need to do :)

@patrick-rodgers will do, now that @sympmarc has confirmed all is well 馃槃

Going to close this as resolved with the new version of the polyfill library. If someone has further issues please open a new issue and ref this one with the new details. Thanks!

Was this page helpful?
0 / 5 - 0 ratings