Skip to content

fix(build): Add JS polyfills to utils tarball #5029

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

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ scratch/
scenarios/*/dist/
# transpiled transformers
jest/transformers/*.js
# node tarballs
packages/*/sentry-*.tgz

# logs
yarn-error.log
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@
"@types/mocha": "^5.2.0",
"@types/node": "~10.17.0",
"@types/sinon": "^7.0.11",
"acorn": "^8.7.0",
"chai": "^4.1.2",
"codecov": "^3.6.5",
"deepmerge": "^4.2.2",
"eslint": "7.32.0",
"fs-extra": "^10.1.0",
"jest": "^27.5.1",
"jest-environment-node": "^27.5.1",
"jsdom": "^19.0.0",
Expand All @@ -81,6 +83,7 @@
"mocha": "^6.1.4",
"npm-run-all": "^4.1.5",
"prettier": "2.5.1",
"recast": "^0.20.5",
"replace-in-file": "^4.0.0",
"rimraf": "^3.0.2",
"rollup": "^2.67.1",
Expand Down
1 change: 1 addition & 0 deletions packages/gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@sentry/react": "7.0.0-beta.0",
"@sentry/tracing": "7.0.0-beta.0",
"@sentry/utils": "7.0.0-beta.0",
"@sentry/webpack-plugin": "1.18.9"
},
"peerDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions packages/utils/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
module.exports = {
extends: ['../../.eslintrc.js'],
overrides: [
{
files: ['scripts/**/*.ts'],
parserOptions: {
project: ['../../tsconfig.dev.json'],
},
},
],
ignorePatterns: ['jsPolyfills/**'],
};
1 change: 1 addition & 0 deletions packages/utils/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jsPolyfills/
3 changes: 3 additions & 0 deletions packages/utils/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
!/build/cjs/**/*
!/build/esm/**/*
!/build/types/**/*

# polyfills for language features and import helpers
!jsPolyfills/**/*
4 changes: 2 additions & 2 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"build:dev": "run-s build",
"build:es5": "yarn build:cjs # *** backwards compatibility - remove in v7 ***",
"build:esm": "tsc -p tsconfig.esm.json",
"build:rollup": "rollup -c rollup.npm.config.js",
"build:rollup": "rollup -c rollup.npm.config.js && rollup -c rollup.polyfills.config.js && cp ../../rollup/jsPolyfills/README.md jsPolyfills",
"build:types": "tsc -p tsconfig.types.json",
"build:watch": "run-p build:cjs:watch build:esm:watch build:types:watch",
"build:cjs:watch": "tsc -p tsconfig.cjs.json --watch",
Expand All @@ -39,7 +39,7 @@
"build:types:watch": "tsc -p tsconfig.types.json --watch",
"build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build",
"circularDepCheck": "madge --circular src/index.ts",
"clean": "rimraf build coverage",
"clean": "rimraf build coverage jsPolyfills",
"fix": "run-s fix:eslint fix:prettier",
"fix:eslint": "eslint . --format stylish --fix",
"fix:prettier": "prettier --write \"{src,test,scripts}/**/*.ts\"",
Expand Down
28 changes: 28 additions & 0 deletions packages/utils/rollup.polyfills.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// TODO: Swich out the sucrase hack for the real, currently-commented-out code once we switch sucrase builds on.

// export default ['esm', 'cjs'].map(format => ({
export default ['esm', 'cjs'].map(format => {
const config = {
input: '../../rollup/jsPolyfills/index.js',
output: {
// preserveModules: true,
dir: `jsPolyfills/${format}`,
format,
strict: false,
},
};
// }));

// temporary hack for testing sucrase bundles before we switch over
if (!process.version.startsWith('v8')) {
// eslint-disable-next-line no-console
console.log('Doing normal preserveModules in polyfill config');
config.output.preserveModules = true;
} else {
// eslint-disable-next-line no-console
console.log('Doing node 8 preserveModules in polyfill config');
config.preserveModules = true;
}

return config;
});
28 changes: 28 additions & 0 deletions packages/utils/scripts/prepack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable no-console */

// DO NOT RUN this script yourself!
// This is invoked from the main `prepack.ts` script in `sentry-javascript/scripts/prepack.ts`.

import * as fs from 'fs';
import * as fse from 'fs-extra';
import * as path from 'path';

export function prepack(buildDir: string): boolean {
// copy package-specific assets to build dir
const assetPath = path.resolve('jsPolyfills');
const destinationPath = path.resolve(buildDir, 'jsPolyfills');
try {
if (!fs.existsSync(assetPath)) {
console.error(
"\nERROR: Missing 'packages/utils/jsPolyfills' directory. Please run `yarn build` in the `utils` package before running this script again.",
);
return false;
}
console.log(`Copying jsPolyfills to ${path.relative('../..', destinationPath)}.`);
fse.copySync(assetPath, destinationPath);
} catch (error) {
console.error(`\nERROR: Error while copying jsPolyfills to ${path.relative('../..', destinationPath)}:\n${error}`);
return false;
}
return true;
}
1 change: 1 addition & 0 deletions packages/wasm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dependencies": {
"@sentry/browser": "7.0.0-beta.0",
"@sentry/types": "7.0.0-beta.0",
"@sentry/utils": "7.0.0-beta.0",
"tslib": "^1.9.3"
},
"devDependencies": {
Expand Down
17 changes: 17 additions & 0 deletions rollup/jsPolyfills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Javascript Polyfills

This is a collection of syntax 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. This directory is the location from which we import. For simplicity (and greater treeshaking ability when using tools which only work on a file-by-file level), each polyfill lives in its own file.

During build, this directory is copied over to the root level of `@sentry/utils`, and the polyfills are compiled into esm and cjs versions. When the injected implementations are replaced by either `import` or `require` by our rollup plugin, each `import`/`require` pulls from the correct module format's folder. (In other words, the injected `import` statements import from `@sentry/utils/jsPolyfills/esm` and the injected `require` statements pull from `@sentry/utils/jsPolyfills/cjs`.)

Note that not all polyfills are currently used by the SDK, but all are included here for future compatitibility, should they ever be needed.

--------

_Code from both Rollup and Sucrase is used under the MIT license, copyright 2017 and 2012-2018, respectively._

_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:_

_The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software._

_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._
15 changes: 15 additions & 0 deletions rollup/jsPolyfills/_asyncNullishCoalesce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// adapted from Sucrase (https://github.com/alangpierce/sucrase)

export async function _asyncNullishCoalesce(lhs, rhsFn) {
// by checking for loose equality to `null`, we catch both `null` and `undefined`
return lhs != null ? lhs : rhsFn();
}

// Sucrase version:
// async function _asyncNullishCoalesce(lhs, rhsFn) {
// if (lhs != null) {
// return lhs;
// } else {
// return await rhsFn();
// }
// }
48 changes: 48 additions & 0 deletions rollup/jsPolyfills/_asyncOptionalChain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// adapted from Sucrase (https://github.com/alangpierce/sucrase)

export async function _asyncOptionalChain(ops) {
let lastAccessLHS = undefined;
let value = ops[0];
let i = 1;
while (i < ops.length) {
const op = ops[i];
const fn = ops[i + 1];
i += 2;
// by checking for loose equality to `null`, we catch both `null` and `undefined`
if (op in ['optionalAccess', 'optionalCall'] && value == null) {
// really we're meaning to return `undefined` as an actual value here, but it saves bytes not to write it
return;
}
if (op in ['access', 'optionalAccess']) {
lastAccessLHS = value;
value = await fn(value);
} else if (op in ['call', 'optionalCall']) {
value = await fn((...args) => value.call(lastAccessLHS, ...args));
lastAccessLHS = undefined;
}
}
return value;
}

// Sucrase version:
// async function _asyncOptionalChain(ops) {
// let lastAccessLHS = undefined;
// let value = ops[0];
// let i = 1;
// while (i < ops.length) {
// const op = ops[i];
// const fn = ops[i + 1];
// i += 2;
// if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
// return undefined;
// }
// if (op === 'access' || op === 'optionalAccess') {
// lastAccessLHS = value;
// value = await fn(value);
// } else if (op === 'call' || op === 'optionalCall') {
// value = await fn((...args) => value.call(lastAccessLHS, ...args));
// lastAccessLHS = undefined;
// }
// }
// return value;
// }
9 changes: 9 additions & 0 deletions rollup/jsPolyfills/_asyncOptionalChainDelete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// originally from Sucrase (https://github.com/alangpierce/sucrase)

import { _asyncOptionalChain } from './_asyncOptionalChain';

export async function _asyncOptionalChainDelete(ops) {
const result = await _asyncOptionalChain(ops);
// by checking for loose equality to `null`, we catch both `null` and `undefined`
return result == null ? true : result;
}
10 changes: 10 additions & 0 deletions rollup/jsPolyfills/_createNamedExportFrom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// adapted from Sucrase (https://github.com/alangpierce/sucrase)

export function _createNamedExportFrom(obj, localName, importedName) {
exports[localName] = obj[importedName];
}

// Sucrase version:
// function _createNamedExportFrom(obj, localName, importedName) {
// Object.defineProperty(exports, localName, {enumerable: true, get: () => obj[importedName]});
// }
19 changes: 19 additions & 0 deletions rollup/jsPolyfills/_createStarExport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// adapted from Sucrase (https://github.com/alangpierce/sucrase)

export function _createStarExport(obj) {
Object.keys(obj)
.filter(key => key !== 'default' && key !== '__esModule' && !(key in exports))
.forEach(key => (exports[key] = obj[key]));
}

// Sucrase version:
// function _createStarExport(obj) {
// Object.keys(obj)
// .filter(key => key !== 'default' && key !== '__esModule')
// .forEach(key => {
// if (exports.hasOwnProperty(key)) {
// return;
// }
// Object.defineProperty(exports, key, { enumerable: true, get: () => obj[key] });
// });
// }
10 changes: 10 additions & 0 deletions rollup/jsPolyfills/_interopDefault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// adapted from Rollup (https://github.com/rollup/rollup)

export function _interopDefault(importTarget) {
return importTarget.__esModule ? importTarget.default : importTarget;
}

// Rollup version:
// function _interopDefault(e) {
// return e && e.__esModule ? e['default'] : e;
// }
18 changes: 18 additions & 0 deletions rollup/jsPolyfills/_interopNamespace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// adapted from Rollup (https://github.com/rollup/rollup)

export function _interopNamespace(importTarget) {
return importTarget.__esModule ? importTarget : { ...importTarget, default: importTarget };
}

// Rollup version (with `output.externalLiveBindings` and `output.freeze` both set to false)
// function _interopNamespace(e) {
// if (e && e.__esModule) return e;
// var n = Object.create(null);
// if (e) {
// for (var k in e) {
// n[k] = e[k];
// }
// }
// n["default"] = e;
// return n;
// }
10 changes: 10 additions & 0 deletions rollup/jsPolyfills/_interopRequireDefault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// adapted from Sucrase (https://github.com/alangpierce/sucrase)

export function _interopRequireDefault(importTarget) {
return importTarget.__esModule ? importTarget : { default: importTarget };
}

// Sucrase version
// function _interopRequireDefault(obj) {
// return obj && obj.__esModule ? obj : { default: obj };
// }
23 changes: 23 additions & 0 deletions rollup/jsPolyfills/_interopRequireWildcard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// adapted from Sucrase (https://github.com/alangpierce/sucrase)

export function _interopRequireWildcard(importTarget) {
return importTarget.__esModule ? importTarget : { ...importTarget, default: importTarget };
}

// Sucrase version
// function _interopRequireWildcard(obj) {
// if (obj && obj.__esModule) {
// return obj;
// } else {
// var newObj = {};
// if (obj != null) {
// for (var key in obj) {
// if (Object.prototype.hasOwnProperty.call(obj, key)) {
// newObj[key] = obj[key];
// }
// }
// }
// newObj.default = obj;
// return newObj;
// }
// }
15 changes: 15 additions & 0 deletions rollup/jsPolyfills/_nullishCoalesce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// adapted from Sucrase (https://github.com/alangpierce/sucrase)

export function _nullishCoalesce(lhs, rhsFn) {
// by checking for loose equality to `null`, we catch both `null` and `undefined`
return lhs != null ? lhs : rhsFn();
}

// Sucrase version
// function _nullishCoalesce(lhs, rhsFn) {
// if (lhs != null) {
// return lhs;
// } else {
// return rhsFn();
// }
// }
48 changes: 48 additions & 0 deletions rollup/jsPolyfills/_optionalChain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// adapted from Sucrase (https://github.com/alangpierce/sucrase)

export function _optionalChain(ops) {
let lastAccessLHS = undefined;
let value = ops[0];
let i = 1;
while (i < ops.length) {
const op = ops[i];
const fn = ops[i + 1];
i += 2;
// by checking for loose equality to `null`, we catch both `null` and `undefined`
if (op in ['optionalAccess', 'optionalCall'] && value == null) {
// really we're meaning to return `undefined` as an actual value here, but it saves bytes not to write it
return;
}
if (op in ['access', 'optionalAccess']) {
lastAccessLHS = value;
value = fn(value);
} else if (op in ['call', 'optionalCall']) {
value = fn((...args) => value.call(lastAccessLHS, ...args));
lastAccessLHS = undefined;
}
}
return value;
}

// Sucrase version
// function _optionalChain(ops) {
// let lastAccessLHS = undefined;
// let value = ops[0];
// let i = 1;
// while (i < ops.length) {
// const op = ops[i];
// const fn = ops[i + 1];
// i += 2;
// if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
// return undefined;
// }
// if (op === 'access' || op === 'optionalAccess') {
// lastAccessLHS = value;
// value = fn(value);
// } else if (op === 'call' || op === 'optionalCall') {
// value = fn((...args) => value.call(lastAccessLHS, ...args));
// lastAccessLHS = undefined;
// }
// }
// return value;
// }
Loading