react-globalize
Advanced tools
Comparing version 0.4.1 to 1.0.0
'use strict'; | ||
var FormatCurrency = require('./currency'); | ||
var FormatDate = require('./date'); | ||
var FormatMessage = require('./message'); | ||
var FormatNumber = require('./number'); | ||
var FormatRelativeTime = require('./relative-time'); | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
module.exports = { | ||
var React = _interopDefault(require('react')); | ||
var Globalize = _interopDefault(require('globalize')); | ||
function alwaysArray(stringOrArray) { | ||
return Array.isArray(stringOrArray) ? stringOrArray : stringOrArray ? [stringOrArray] : []; | ||
} | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
var commonPropNames = ["elements", "locale"]; | ||
function capitalizeFirstLetter(string) { | ||
return string.charAt(0).toUpperCase() + string.slice(1); | ||
} | ||
function omit(set) { | ||
return function (element) { | ||
return set.indexOf(element) === -1; | ||
}; | ||
} | ||
function generator(fn, localPropNames, options) { | ||
var _class, _temp; | ||
options = options || {}; | ||
var Fn = capitalizeFirstLetter(fn); | ||
var beforeFormat = options.beforeFormat || function () {}; | ||
var afterFormat = options.afterFormat || function (props, formattedValue) { | ||
return formattedValue; | ||
}; | ||
var globalizePropNames = commonPropNames.concat(localPropNames); | ||
return _temp = _class = function (_React$Component) { | ||
_inherits(_class, _React$Component); | ||
function _class() { | ||
_classCallCheck(this, _class); | ||
return _possibleConstructorReturn(this, (_class.__proto__ || Object.getPrototypeOf(_class)).apply(this, arguments)); | ||
} | ||
_createClass(_class, [{ | ||
key: "componentWillMount", | ||
// eslint-disable-line no-undef | ||
value: function componentWillMount() { | ||
this.setup(this.props); | ||
} | ||
}, { | ||
key: "componentWillReceiveProps", | ||
value: function componentWillReceiveProps(nextProps) { | ||
this.setup(nextProps); | ||
} | ||
}, { | ||
key: "setup", | ||
value: function setup(props) { | ||
var _globalize; | ||
this.globalize = props.locale ? Globalize(props.locale) : Globalize; | ||
this.domProps = Object.keys(props).filter(omit(globalizePropNames)).reduce(function (memo, propKey) { | ||
memo[propKey] = props[propKey]; | ||
return memo; | ||
}, {}); | ||
this.globalizePropValues = localPropNames.map(function (element) { | ||
return props[element]; | ||
}); | ||
this.globalizePropValues[0] = props.children; | ||
beforeFormat.call(this, props); | ||
var formattedValue = (_globalize = this.globalize)[fn].apply(_globalize, _toConsumableArray(this.globalizePropValues)); | ||
this.value = alwaysArray(afterFormat.call(this, props, formattedValue)); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
return React.createElement.apply(React, ["span", this.domProps].concat(_toConsumableArray(this.value))); | ||
} | ||
}]); | ||
return _class; | ||
}(React.Component), _class.displayName = Fn, _temp; | ||
} | ||
var FormatCurrency = generator("formatCurrency", ["value", "currency", "options"]); | ||
var FormatDate = generator("formatDate", ["value", "options"]); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
function messageSetup(globalize, props, globalizePropValues) { | ||
var defaultMessage; | ||
var children = props.children; | ||
var scope = props.scope; | ||
function getDefaultMessage(children) { | ||
if (typeof children === "string") { | ||
return children; | ||
} else { | ||
throw new Error("Invalid default message type `" + (typeof children === "undefined" ? "undefined" : _typeof(children)) + "`"); | ||
} | ||
} | ||
// Set path - path as props supercedes default value. | ||
if (props.path) { | ||
// Override generator assumption. The generator assumes the globalizePropValues[0] | ||
// (path) and props.children to be mutually exclusive, but this isn't | ||
// true here for messages. Because, it's possible to use props.path (for | ||
// path) and props.children for defaultMessage, which are two different | ||
// variables. | ||
globalizePropValues[0] = props.path; | ||
} else { | ||
// Although the generator had already set globalizePropValues[0] (path) as | ||
// props.children, here its type is checked and its value is sanitized. | ||
defaultMessage = getDefaultMessage(children); | ||
globalizePropValues[0] = sanitizePath(defaultMessage); | ||
} | ||
// Scope path. | ||
if (scope) { | ||
globalizePropValues[0] = scope + "/" + globalizePropValues[0]; | ||
} | ||
// Development mode only. | ||
if (process.env.NODE_ENV !== "production") { | ||
var path = props.path ? props.path.split("/") : [globalizePropValues[0]]; | ||
var getMessage = function getMessage(globalize, path) { | ||
return globalize.cldr.get(["globalize-messages/{bundle}"].concat(path)); | ||
}; | ||
var setMessage = function setMessage(globalize, path, message) { | ||
var data = {}; | ||
function set(data, path, value) { | ||
var i; | ||
var node = data; | ||
var length = path.length; | ||
for (i = 0; i < length - 1; i++) { | ||
if (!node[path[i]]) { | ||
node[path[i]] = {}; | ||
} | ||
node = node[path[i]]; | ||
} | ||
node[path[i]] = value; | ||
} | ||
set(data, [globalize.cldr.attributes.bundle].concat(path), message); | ||
Globalize.loadMessages(data); | ||
}; | ||
if (globalize.cldr) { | ||
if (!getMessage(globalize, path)) { | ||
defaultMessage = defaultMessage || getDefaultMessage(children); | ||
setMessage(globalize, path, defaultMessage); | ||
} | ||
} | ||
} | ||
} | ||
function replaceElements(props, formatted) { | ||
var elements = props.elements; | ||
function _replaceElements(format, elements) { | ||
if (typeof format !== "string") { | ||
throw new Error("Missing or invalid string `" + format + "` (" + (typeof format === "undefined" ? "undefined" : _typeof(format)) + ")"); | ||
} | ||
if ((typeof elements === "undefined" ? "undefined" : _typeof(elements)) !== "object") { | ||
throw new Error("Missing or invalid elements `" + elements + "` (" + (typeof elements === "undefined" ? "undefined" : _typeof(elements)) + ")"); | ||
} | ||
// Given [x, y, z], it returns [x, element, y, element, z]. | ||
function spreadElementsInBetweenItems(array, element) { | ||
var getElement = typeof element === "function" ? element : function () { | ||
return element; | ||
}; | ||
return array.slice(1).reduce(function (ret, item, i) { | ||
ret.push(getElement(i), item); | ||
return ret; | ||
}, [array[0]]); | ||
} | ||
function splice(sourceArray, start, deleteCount, itemsArray) { | ||
[].splice.apply(sourceArray, [start, deleteCount].concat(itemsArray)); | ||
} | ||
return Object.keys(elements).reduce(function (nodes, key) { | ||
var element = elements[key]; | ||
// Insert array into the correct ret position. | ||
function replaceNode(array, i) { | ||
splice(nodes, i, 1, array); | ||
} | ||
var _loop = function _loop(i) { | ||
var node = nodes[i]; | ||
if (typeof node !== "string") { | ||
return "continue"; // eslint-disable-line no-continue | ||
} | ||
// Empty tags, e.g., `[foo/]`. | ||
var aux = node.split("[" + key + "/]"); | ||
if (aux.length > 1) { | ||
aux = spreadElementsInBetweenItems(aux, element); | ||
replaceNode(aux, i); | ||
return "continue"; // eslint-disable-line no-continue | ||
} | ||
// Start-end tags, e.g., `[foo]content[/foo]`. | ||
var regexp = new RegExp("\\[" + key + "\\][\\s\\S]*?\\[\\/" + key + "\\]", "g"); | ||
var regexp2 = new RegExp("\\[" + key + "\\]([\\s\\S]*?)\\[\\/" + key + "\\]"); | ||
aux = node.split(regexp); | ||
if (aux.length > 1) { | ||
var contents = node.match(regexp).map(function (content) { | ||
return content.replace(regexp2, "$1"); | ||
}); | ||
aux = spreadElementsInBetweenItems(aux, function (idx) { | ||
return React.cloneElement(element, {}, contents[idx]); | ||
}); | ||
replaceNode(aux, i); | ||
} | ||
}; | ||
for (var i = 0; i < nodes.length; i += 1) { | ||
var _ret = _loop(i); | ||
if (_ret === "continue") continue; | ||
} | ||
return nodes; | ||
}, [format]); | ||
} | ||
// Elements replacement. | ||
if (elements) { | ||
formatted = _replaceElements(formatted, elements); | ||
} | ||
return formatted; | ||
} | ||
function sanitizePath(pathString) { | ||
return pathString.trim().replace(/\{/g, "(").replace(/\}/g, ")").replace(/\//g, "|").replace(/\n/g, " ").replace(/ +/g, " ").replace(/"/g, "'"); | ||
} | ||
// Overload Globalize's `.formatMessage` to allow default message. | ||
var globalizeMessageFormatter = Globalize.messageFormatter; | ||
Globalize.messageFormatter = Globalize.prototype.messageFormatter = function (pathOrMessage) { | ||
var aux = {}; | ||
var sanitizedPath = sanitizePath(pathOrMessage); | ||
// Globalize runtime | ||
if (!this.cldr) { | ||
// On runtime, the only way for deciding between using sanitizedPath or | ||
// pathOrMessage as path is by checking which formatter exists. | ||
arguments[0] = sanitizedPath; | ||
aux = globalizeMessageFormatter.apply(this, arguments); | ||
arguments[0] = pathOrMessage; | ||
return aux || globalizeMessageFormatter.apply(this, arguments); | ||
} | ||
var sanitizedPathExists = this.cldr.get(["globalize-messages/{bundle}", sanitizedPath]) !== undefined; | ||
var pathExists = this.cldr.get(["globalize-messages/{bundle}", pathOrMessage]) !== undefined; | ||
// Want to distinguish between default message and path value - just checking | ||
// for sanitizedPath won't be enough, because sanitizedPath !== pathOrMessage | ||
// for paths like "salutations/hi". | ||
if (!sanitizedPathExists && !pathExists) { | ||
aux[this.cldr.attributes.bundle] = {}; | ||
aux[this.cldr.attributes.bundle][sanitizedPath] = pathOrMessage; | ||
Globalize.loadMessages(aux); | ||
sanitizedPathExists = true; | ||
} | ||
arguments[0] = sanitizedPathExists ? sanitizedPath : pathOrMessage; | ||
return globalizeMessageFormatter.apply(this, arguments); | ||
}; | ||
var FormatMessage = generator("formatMessage", ["path", "variables"], { | ||
beforeFormat: function beforeFormat(props) { | ||
messageSetup(this.globalize, props, this.globalizePropValues); | ||
}, | ||
afterFormat: function afterFormat(props, formattedValue) { | ||
return replaceElements(props, formattedValue); | ||
} | ||
}); | ||
var FormatNumber = generator("formatNumber", ["value", "options"]); | ||
var FormatRelativeTime = generator("formatRelativeTime", ["value", "unit", "options"]); | ||
var index = { | ||
FormatCurrency: FormatCurrency, | ||
@@ -15,2 +308,4 @@ FormatDate: FormatDate, | ||
FormatRelativeTime: FormatRelativeTime | ||
}; | ||
}; | ||
module.exports = index; |
{ | ||
"name": "react-globalize", | ||
"version": "0.4.1", | ||
"version": "1.0.0", | ||
"description": "Bringing the i18n functionality of Globalize, backed by CLDR, to React", | ||
"main": "dist/index.js", | ||
"module": "dist/index.esm.js", | ||
"browser": "dist/index.umd.js", | ||
"files": [ | ||
@@ -14,3 +16,6 @@ "dist/", | ||
"scripts": { | ||
"test": "grunt test" | ||
"build": "rollup -c", | ||
"lint": "eslint rollup.config.js src test", | ||
"test": "mocha test", | ||
"prepublish": "npm run build" | ||
}, | ||
@@ -34,16 +39,20 @@ "keywords": [ | ||
"bugs": "https://github.com/jquery-support/react-globalize/issues", | ||
"peerDependencies": { | ||
"globalize": ">= 1.0.0", | ||
"react": "^0.13 || ^0.14 || ^15.0.0" | ||
"dependencies": { | ||
"globalize": "^1.0.0", | ||
"react": "^16.0.0" | ||
}, | ||
"devDependencies": { | ||
"babel-eslint": "^4.1.6", | ||
"babel-preset-es2015": "^6.6.0", | ||
"babel-preset-react": "^6.5.0", | ||
"babel-cli": "^6.26.0", | ||
"babel-eslint": "^8.0.1", | ||
"babel-plugin-transform-class-properties": "^6.24.1", | ||
"babel-plugin-transform-object-rest-spread": "^6.26.0", | ||
"babel-preset-env": "^1.6.0", | ||
"babel-preset-react": "^6.24.1", | ||
"babel-register": "^6.7.2", | ||
"chai": "^3.5.0", | ||
"chai": "^4.1.2", | ||
"cldr-data": ">=25", | ||
"cldrjs": "^0.4.3", | ||
"create-react-class": "^15.5.2", | ||
"enzyme": "2.2.0", | ||
"enzyme": "^3.0.0", | ||
"enzyme-adapter-react-16": "^1.0.1", | ||
"eslint": "^1.10.3", | ||
@@ -53,11 +62,11 @@ "eslint-config-defaults": "^7.1.1", | ||
"globalize": "1.1.x", | ||
"grunt": "^0.4.5", | ||
"grunt-eslint": "^18.0.0", | ||
"grunt-esperanto": "^0.4.0", | ||
"grunt-mocha-test": "^0.12.7", | ||
"mocha": "^2.4.5", | ||
"react": "15.0.1", | ||
"react-addons-test-utils": "15.0.1", | ||
"react-dom": "15.0.1" | ||
"mocha": "^4.0.0", | ||
"react": "^16.0.0", | ||
"react-dom": "^16.0.0", | ||
"react-test-renderer": "^16.0.0", | ||
"rollup": "^0.50.0", | ||
"rollup-plugin-babel": "^3.0.2", | ||
"rollup-plugin-node-resolve": "^3.0.0", | ||
"rollup-watch": "^4.3.1" | ||
} | ||
} |
@@ -5,2 +5,9 @@ # react-globalize | ||
## React versions | ||
| react-globalize | react | | ||
| --- | --- | | ||
| 0.x | ^0.14.0, ^0.14.0, ^15.0.0 | | ||
| 1.x | ^16.0.0 | | ||
## Install | ||
@@ -258,5 +265,5 @@ | ||
``` | ||
VER=<version> # e.g., "0.4.0" | ||
VER=<version> # e.g., "1.0.1" | ||
git checkout --detach && | ||
grunt && | ||
npm run build && | ||
git add -f dist/* && | ||
@@ -263,0 +270,0 @@ git commit -a -m Build && |
import React from "react"; | ||
import generator from "./generator"; | ||
import "globalize/currency"; | ||
export default generator("formatCurrency", ["value", "currency", "options"]); |
import React from "react"; | ||
import generator from "./generator"; | ||
import "globalize/date"; | ||
export default generator("formatDate", ["value", "options"]); |
@@ -1,4 +0,4 @@ | ||
import reactCreateClass from "./react-create-class"; | ||
import React from "react"; | ||
import Globalize from "globalize"; | ||
import alwaysArray from "./util/always-array"; | ||
@@ -21,3 +21,3 @@ var commonPropNames = ["elements", "locale"]; | ||
var beforeFormat = options.beforeFormat || function() {}; | ||
var afterFormat = options.afterFormat || function(formattedValue) { | ||
var afterFormat = options.afterFormat || function(props, formattedValue) { | ||
return formattedValue; | ||
@@ -27,11 +27,14 @@ }; | ||
return reactCreateClass({ | ||
displayName: Fn, | ||
componentWillMount: function() { | ||
return class extends React.Component { | ||
static displayName = Fn; // eslint-disable-line no-undef | ||
componentWillMount() { | ||
this.setup(this.props); | ||
}, | ||
componentWillReceiveProps: function(nextProps) { | ||
} | ||
componentWillReceiveProps(nextProps) { | ||
this.setup(nextProps); | ||
}, | ||
setup: function(props) { | ||
} | ||
setup(props) { | ||
this.globalize = props.locale ? Globalize(props.locale) : Globalize; | ||
@@ -49,11 +52,12 @@ this.domProps = Object.keys(props).filter(omit(globalizePropNames)).reduce(function(memo, propKey) { | ||
beforeFormat.call(this, props); | ||
var formattedValue = this.globalize[fn].apply(this.globalize, this.globalizePropValues); | ||
this.value = afterFormat.call(this, formattedValue); | ||
}, | ||
render: function() { | ||
return React.DOM.span(this.domProps, this.value); | ||
var formattedValue = this.globalize[fn](...this.globalizePropValues); | ||
this.value = alwaysArray(afterFormat.call(this, props, formattedValue)); | ||
} | ||
}); | ||
render() { | ||
return React.createElement("span", this.domProps, ...this.value); | ||
} | ||
}; | ||
} | ||
export default generator; |
import Globalize from "globalize"; | ||
import React from "react"; | ||
import generator from "./generator"; | ||
import "globalize/message"; | ||
import "globalize/plural"; | ||
@@ -78,5 +76,5 @@ function messageSetup(globalize, props, globalizePropValues) { | ||
function _replaceElements(string, elements) { | ||
if (typeof string !== "string") { | ||
throw new Error("Missing or invalid string `" + string + "` (" + typeof string + ")"); | ||
function _replaceElements(format, elements) { | ||
if (typeof format !== "string") { | ||
throw new Error(`Missing or invalid string \`${format}\` (${typeof format})`); | ||
} | ||
@@ -102,42 +100,40 @@ if (typeof elements !== "object") { | ||
return Object.keys(elements).reduce(function(ret, key) { | ||
var element = elements[key]; | ||
return Object.keys(elements).reduce((nodes, key) => { | ||
const element = elements[key]; | ||
ret.forEach(function(string, i) { | ||
var aux, contents, regexp, regexp2; | ||
// Insert array into the correct ret position. | ||
function replaceNode(array, i) { | ||
splice(nodes, i, 1, array); | ||
} | ||
// Insert array into the correct ret position. | ||
function replaceRetItem(array) { | ||
splice(ret, i, 1, array); | ||
for (let i = 0; i < nodes.length; i += 1) { | ||
const node = nodes[i]; | ||
if (typeof node !== "string") { | ||
continue; // eslint-disable-line no-continue | ||
} | ||
if (typeof string !== "string") { | ||
return; // continue; | ||
} | ||
// Empty tags, e.g., `[foo/]`. | ||
aux = string.split("[" + key + "/]"); | ||
let aux = node.split(`[${key}/]`); | ||
if (aux.length > 1) { | ||
aux = spreadElementsInBetweenItems(aux, element); | ||
replaceRetItem(aux); | ||
return; // continue; | ||
replaceNode(aux, i); | ||
continue; // eslint-disable-line no-continue | ||
} | ||
// Start-end tags, e.g., `[foo]content[/foo]`. | ||
regexp = new RegExp("\\[" + key + "\\][\\s\\S]*?\\[\\/" + key + "\\]", "g"); | ||
regexp2 = new RegExp("\\[" + key + "\\]([\\s\\S]*?)\\[\\/" + key + "\\]"); | ||
aux = string.split(regexp); | ||
const regexp = new RegExp(`\\[${key}\\][\\s\\S]*?\\[\\/${key}\\]`, "g"); | ||
const regexp2 = new RegExp(`\\[${key}\\]([\\s\\S]*?)\\[\\/${key}\\]`); | ||
aux = node.split(regexp); | ||
if (aux.length > 1) { | ||
contents = string.match(regexp).map(function(content) { | ||
return content.replace(regexp2, "$1"); | ||
}); | ||
aux = spreadElementsInBetweenItems(aux, function(i) { | ||
return React.cloneElement(element, {}, contents[i]); | ||
}); | ||
replaceRetItem(aux); | ||
const contents = node.match(regexp).map(content => content.replace(regexp2, "$1")); | ||
aux = spreadElementsInBetweenItems( | ||
aux, | ||
idx => React.cloneElement(element, {}, contents[idx]), | ||
); | ||
replaceNode(aux, i); | ||
} | ||
}); | ||
} | ||
return ret; | ||
}, [string]); | ||
return nodes; | ||
}, [format]); | ||
} | ||
@@ -148,3 +144,3 @@ | ||
if (elements) { | ||
formatted = React.DOM.span.apply(React.DOM.span, [{}].concat(_replaceElements(formatted, elements))); | ||
formatted = _replaceElements(formatted, elements); | ||
} | ||
@@ -196,5 +192,5 @@ | ||
}, | ||
afterFormat: function(formattedValue) { | ||
return replaceElements(this.props, formattedValue); | ||
afterFormat: function(props, formattedValue) { | ||
return replaceElements(props, formattedValue); | ||
} | ||
}); |
import React from "react"; | ||
import generator from "./generator"; | ||
import "globalize/number"; | ||
export default generator("formatNumber", ["value", "options"]); |
import React from "react"; | ||
import generator from "./generator"; | ||
import "globalize/relative-time"; | ||
export default generator("formatRelativeTime", ["value", "unit", "options"]); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
79519
1321
0
282
25
23
5
2
+ Addedglobalize@^1.0.0
+ Addedreact@^16.0.0
+ Addedreact@16.14.0(transitive)
- Removedasap@2.0.6(transitive)
- Removedcore-js@1.2.7(transitive)
- Removedcreate-react-class@15.7.0(transitive)
- Removedencoding@0.1.13(transitive)
- Removedfbjs@0.8.18(transitive)
- Removediconv-lite@0.6.3(transitive)
- Removedis-stream@1.1.0(transitive)
- Removedisomorphic-fetch@2.2.1(transitive)
- Removednode-fetch@1.7.3(transitive)
- Removedpromise@7.3.1(transitive)
- Removedreact@15.7.0(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsetimmediate@1.0.5(transitive)
- Removedua-parser-js@0.7.39(transitive)
- Removedwhatwg-fetch@3.6.20(transitive)