Skip to content

Commit dbc7cc3

Browse files
committed
add polyfills
1 parent c9e3602 commit dbc7cc3

16 files changed

+429
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## Build Polyfills
2+
3+
This is a collection of syntax and import/export polyfills either copied directly from or heavily inspired by those used by [Rollup](https://github.com/rollup/rollup) and [Sucrase](https://github.com/alangpierce/sucrase). When either tool uses one of these polyfills during a build, it injects the function source code into each file needing the function, which can lead to a great deal of duplication. For our builds, we have therefore implemented something similar to [`tsc`'s `importHelpers` behavior](https://www.typescriptlang.org/tsconfig#importHelpers): Instead of leaving the polyfills injected in multiple places, we instead replace each injected function with an `import` or `require` statement, pulling from the CJS or ESM builds as appropriate. (In other words, the injected `import` statements import from `@sentry/utils/esm/buildPolyfills` and the injected `require` statements pull from `@sentry/utils/cjs/buildPolyfills/`. Because these functions should never be part of the public API, they're not exported from the package directly.)
4+
5+
Note that not all polyfills are currently used by the SDK, but all are included here for future compatitibility, should they ever be needed. Also, since we're never going to be calling these directly from within another TS file, their types are fairly generic. In some cases testing required more specific types, which can be found in the test files.
6+
7+
--------
8+
9+
_Code from both Rollup and Sucrase is used under the MIT license, copyright 2017 and 2012-2018, respectively._
10+
11+
_Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:_
12+
13+
_The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software._
14+
15+
_THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE._
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// adapted from Sucrase (https://github.com/alangpierce/sucrase)
2+
3+
import { _nullishCoalesce } from './_nullishCoalesce';
4+
5+
/**
6+
* Polyfill for the nullish coalescing operator (`??`), when used in situations where at least one of the values is the
7+
* result of an async operation.
8+
*
9+
* Note that the RHS is wrapped in a function so that if it's a computed value, that evaluation won't happen unless the
10+
* LHS evaluates to a nullish value, to mimic the operator's short-circuiting behavior.
11+
*
12+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase)
13+
*
14+
* @param lhs The value of the expression to the left of the `??`
15+
* @param rhsFn A function returning the value of the expression to the right of the `??`
16+
* @returns The LHS value, unless it's `null` or `undefined`, in which case, the RHS value
17+
*/
18+
// eslint-disable-next-line @sentry-internal/sdk/no-async-await
19+
export async function _asyncNullishCoalesce(lhs: unknown, rhsFn: () => unknown): Promise<unknown> {
20+
return _nullishCoalesce(lhs, rhsFn);
21+
}
22+
23+
// Sucrase version:
24+
// async function _asyncNullishCoalesce(lhs, rhsFn) {
25+
// if (lhs != null) {
26+
// return lhs;
27+
// } else {
28+
// return await rhsFn();
29+
// }
30+
// }
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { GenericFunction } from './types';
2+
3+
/**
4+
* Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values,
5+
* descriptors, and functions, for situations in which at least one part of the expression is async.
6+
*
7+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase) See
8+
* https://github.com/alangpierce/sucrase/blob/265887868966917f3b924ce38dfad01fbab1329f/src/transformers/OptionalChainingNullishTransformer.ts#L15
9+
*
10+
* @param ops Array result of expression conversion
11+
* @returns The value of the expression
12+
*/
13+
// eslint-disable-next-line @sentry-internal/sdk/no-async-await
14+
export async function _asyncOptionalChain(ops: unknown[]): Promise<unknown> {
15+
let lastAccessLHS: unknown = undefined;
16+
let value = ops[0];
17+
let i = 1;
18+
while (i < ops.length) {
19+
const op = ops[i] as string;
20+
const fn = ops[i + 1] as (intermediateValue: unknown) => Promise<unknown>;
21+
i += 2;
22+
// by checking for loose equality to `null`, we catch both `null` and `undefined`
23+
if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
24+
// really we're meaning to return `undefined` as an actual value here, but it saves bytes not to write it
25+
return;
26+
}
27+
if (op === 'access' || op === 'optionalAccess') {
28+
lastAccessLHS = value;
29+
value = await fn(value);
30+
} else if (op === 'call' || op === 'optionalCall') {
31+
value = await fn((...args: unknown[]) => (value as GenericFunction).call(lastAccessLHS, ...args));
32+
lastAccessLHS = undefined;
33+
}
34+
}
35+
return value;
36+
}
37+
38+
// Sucrase version:
39+
// async function _asyncOptionalChain(ops) {
40+
// let lastAccessLHS = undefined;
41+
// let value = ops[0];
42+
// let i = 1;
43+
// while (i < ops.length) {
44+
// const op = ops[i];
45+
// const fn = ops[i + 1];
46+
// i += 2;
47+
// if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
48+
// return undefined;
49+
// }
50+
// if (op === 'access' || op === 'optionalAccess') {
51+
// lastAccessLHS = value;
52+
// value = await fn(value);
53+
// } else if (op === 'call' || op === 'optionalCall') {
54+
// value = await fn((...args) => value.call(lastAccessLHS, ...args));
55+
// lastAccessLHS = undefined;
56+
// }
57+
// }
58+
// return value;
59+
// }
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { _asyncOptionalChain } from './_asyncOptionalChain';
2+
3+
/**
4+
* Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values,
5+
* descriptors, and functions, in cases where the value of the expression is to be deleted.
6+
*
7+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase) See
8+
* https://github.com/alangpierce/sucrase/blob/265887868966917f3b924ce38dfad01fbab1329f/src/transformers/OptionalChainingNullishTransformer.ts#L15
9+
*
10+
* @param ops Array result of expression conversion
11+
* @returns The return value of the `delete` operator: `true`, unless the deletion target is an own, non-configurable
12+
* property (one which can't be deleted or turned into an accessor, and whose enumerability can't be changed), in which
13+
* case `false`.
14+
*/
15+
// eslint-disable-next-line @sentry-internal/sdk/no-async-await
16+
export async function _asyncOptionalChainDelete(ops: unknown[]): Promise<boolean> {
17+
const result = (await _asyncOptionalChain(ops)) as Promise<boolean | null>;
18+
// If `result` is `null`, it means we didn't get to the end of the chain and so nothing was deleted (in which case,
19+
// return `true` since that's what `delete` does when it no-ops). If it's non-null, we know the delete happened, in
20+
// which case we return whatever the `delete` returned, which will be a boolean.
21+
return result == null ? true : (result as Promise<boolean>);
22+
}
23+
24+
// Sucrase version:
25+
// async function asyncOptionalChainDelete(ops) {
26+
// const result = await ASYNC_OPTIONAL_CHAIN_NAME(ops);
27+
// return result == null ? true : result;
28+
// }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { GenericObject } from './types';
2+
3+
declare const exports: GenericObject;
4+
5+
/**
6+
* Copy a property from the given object into `exports`, under the given name.
7+
*
8+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase)
9+
*
10+
* @param obj The object containing the property to copy.
11+
* @param localName The name under which to export the property
12+
* @param importedName The name under which the property lives in `obj`
13+
*/
14+
export function _createNamedExportFrom(obj: GenericObject, localName: string, importedName: string): void {
15+
exports[localName] = obj[importedName];
16+
}
17+
18+
// Sucrase version:
19+
// function _createNamedExportFrom(obj, localName, importedName) {
20+
// Object.defineProperty(exports, localName, {enumerable: true, get: () => obj[importedName]});
21+
// }
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { GenericObject } from './types';
2+
3+
declare const exports: GenericObject;
4+
5+
/**
6+
* Copy properties from an object into `exports`.
7+
*
8+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase)
9+
*
10+
* @param obj The object containing the properties to copy.
11+
*/
12+
export function _createStarExport(obj: GenericObject): void {
13+
Object.keys(obj)
14+
.filter(key => key !== 'default' && key !== '__esModule' && !(key in exports))
15+
.forEach(key => (exports[key] = obj[key]));
16+
}
17+
18+
// Sucrase version:
19+
// function _createStarExport(obj) {
20+
// Object.keys(obj)
21+
// .filter(key => key !== 'default' && key !== '__esModule')
22+
// .forEach(key => {
23+
// if (exports.hasOwnProperty(key)) {
24+
// return;
25+
// }
26+
// Object.defineProperty(exports, key, { enumerable: true, get: () => obj[key] });
27+
// });
28+
// }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { RequireResult } from './types';
2+
3+
/**
4+
* Unwraps a module if it has been wrapped in an object under the key `default`.
5+
*
6+
* Adapted from Rollup (https://github.com/rollup/rollup)
7+
*
8+
* @param requireResult The result of calling `require` on a module
9+
* @returns The full module, unwrapped if necessary.
10+
*/
11+
export function _interopDefault(requireResult: RequireResult): RequireResult {
12+
return requireResult.__esModule ? (requireResult.default as RequireResult) : requireResult;
13+
}
14+
15+
// Rollup version:
16+
// function _interopDefault(e) {
17+
// return e && e.__esModule ? e['default'] : e;
18+
// }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { RequireResult } from './types';
2+
3+
/**
4+
* Adds a self-referential `default` property to CJS modules which aren't the result of transpilation from ESM modules.
5+
*
6+
* Adapted from Rollup (https://github.com/rollup/rollup)
7+
*
8+
* @param requireResult The result of calling `require` on a module
9+
* @returns Either `requireResult` or a copy of `requireResult` with an added self-referential `default` property
10+
*/
11+
export function _interopNamespace(requireResult: RequireResult): RequireResult {
12+
return requireResult.__esModule ? requireResult : { ...requireResult, default: requireResult };
13+
}
14+
15+
// Rollup version (with `output.externalLiveBindings` and `output.freeze` both set to false)
16+
// function _interopNamespace(e) {
17+
// if (e && e.__esModule) return e;
18+
// var n = Object.create(null);
19+
// if (e) {
20+
// for (var k in e) {
21+
// n[k] = e[k];
22+
// }
23+
// }
24+
// n["default"] = e;
25+
// return n;
26+
// }
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { RequireResult } from './types';
2+
3+
/**
4+
* Wrap a module in an object, as the value under the key `default`.
5+
*
6+
* Adapted from Rollup (https://github.com/rollup/rollup)
7+
*
8+
* @param requireResult The result of calling `require` on a module
9+
* @returns An object containing the key-value pair (`default`, `requireResult`)
10+
*/
11+
export function _interopNamespaceDefaultOnly(requireResult: RequireResult): RequireResult {
12+
return {
13+
__proto__: null,
14+
default: requireResult,
15+
};
16+
}
17+
18+
// Rollup version
19+
// function _interopNamespaceDefaultOnly(e) {
20+
// return {
21+
// __proto__: null,
22+
// 'default': e
23+
// };
24+
// }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { RequireResult } from './types';
2+
3+
/**
4+
* Wraps modules which aren't the result of transpiling an ESM module in an object under the key `default`
5+
*
6+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase)
7+
*
8+
* @param requireResult The result of calling `require` on a module
9+
* @returns `requireResult` or `requireResult` wrapped in an object, keyed as `default`
10+
*/
11+
export function _interopRequireDefault(requireResult: RequireResult): RequireResult {
12+
return requireResult.__esModule ? requireResult : { default: requireResult };
13+
}
14+
15+
// Sucrase version
16+
// function _interopRequireDefault(obj) {
17+
// return obj && obj.__esModule ? obj : { default: obj };
18+
// }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { RequireResult } from './types';
2+
3+
/**
4+
* Adds a `default` property to CJS modules which aren't the result of transpilation from ESM modules.
5+
*
6+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase)
7+
*
8+
* @param requireResult The result of calling `require` on a module
9+
* @returns Either `requireResult` or a copy of `requireResult` with an added self-referential `default` property
10+
*/
11+
export function _interopRequireWildcard(requireResult: RequireResult): RequireResult {
12+
return requireResult.__esModule ? requireResult : { ...requireResult, default: requireResult };
13+
}
14+
15+
// Sucrase version
16+
// function _interopRequireWildcard(obj) {
17+
// if (obj && obj.__esModule) {
18+
// return obj;
19+
// } else {
20+
// var newObj = {};
21+
// if (obj != null) {
22+
// for (var key in obj) {
23+
// if (Object.prototype.hasOwnProperty.call(obj, key)) {
24+
// newObj[key] = obj[key];
25+
// }
26+
// }
27+
// }
28+
// newObj.default = obj;
29+
// return newObj;
30+
// }
31+
// }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Polyfill for the nullish coalescing operator (`??`).
3+
*
4+
* Note that the RHS is wrapped in a function so that if it's a computed value, that evaluation won't happen unless the
5+
* LHS evaluates to a nullish value, to mimic the operator's short-circuiting behavior.
6+
*
7+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase)
8+
*
9+
* @param lhs The value of the expression to the left of the `??`
10+
* @param rhsFn A function returning the value of the expression to the right of the `??`
11+
* @returns The LHS value, unless it's `null` or `undefined`, in which case, the RHS value
12+
*/
13+
export function _nullishCoalesce(lhs: unknown, rhsFn: () => unknown): unknown {
14+
// by checking for loose equality to `null`, we catch both `null` and `undefined`
15+
return lhs != null ? lhs : rhsFn();
16+
}
17+
18+
// Sucrase version:
19+
// function _nullishCoalesce(lhs, rhsFn) {
20+
// if (lhs != null) {
21+
// return lhs;
22+
// } else {
23+
// return rhsFn();
24+
// }
25+
// }
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { GenericFunction } from './types';
2+
3+
/**
4+
* Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values,
5+
* descriptors, and functions.
6+
*
7+
* Adapted from Sucrase (https://github.com/alangpierce/sucrase)
8+
* See https://github.com/alangpierce/sucrase/blob/265887868966917f3b924ce38dfad01fbab1329f/src/transformers/OptionalChainingNullishTransformer.ts#L15
9+
*
10+
* @param ops Array result of expression conversion
11+
* @returns The value of the expression
12+
*/
13+
export function _optionalChain(ops: unknown[]): unknown {
14+
let lastAccessLHS: unknown = undefined;
15+
let value = ops[0];
16+
let i = 1;
17+
while (i < ops.length) {
18+
const op = ops[i] as string;
19+
const fn = ops[i + 1] as (intermediateValue: unknown) => unknown;
20+
i += 2;
21+
// by checking for loose equality to `null`, we catch both `null` and `undefined`
22+
if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
23+
// really we're meaning to return `undefined` as an actual value here, but it saves bytes not to write it
24+
return;
25+
}
26+
if (op === 'access' || op === 'optionalAccess') {
27+
lastAccessLHS = value;
28+
value = fn(value);
29+
} else if (op === 'call' || op === 'optionalCall') {
30+
value = fn((...args: unknown[]) => (value as GenericFunction).call(lastAccessLHS, ...args));
31+
lastAccessLHS = undefined;
32+
}
33+
}
34+
return value;
35+
}
36+
37+
// Sucrase version
38+
// function _optionalChain(ops) {
39+
// let lastAccessLHS = undefined;
40+
// let value = ops[0];
41+
// let i = 1;
42+
// while (i < ops.length) {
43+
// const op = ops[i];
44+
// const fn = ops[i + 1];
45+
// i += 2;
46+
// if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
47+
// return undefined;
48+
// }
49+
// if (op === 'access' || op === 'optionalAccess') {
50+
// lastAccessLHS = value;
51+
// value = fn(value);
52+
// } else if (op === 'call' || op === 'optionalCall') {
53+
// value = fn((...args) => value.call(lastAccessLHS, ...args));
54+
// lastAccessLHS = undefined;
55+
// }
56+
// }
57+
// return value;
58+
// }

0 commit comments

Comments
 (0)