From c22ef6d0076632a08564f55e72677a51fec2c717 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 12 May 2021 09:43:26 +0200 Subject: [PATCH] refactor: Use preprocessor for polyfill --- addon/index.js | 36 ++++ index.js | 57 +----- lib/replace-imports-preprocessor.js | 23 +++ lib/transpile-modules.js | 182 ------------------ package.json | 3 +- tests/unit/followed-import-test.ts | 33 ++++ tests/unit/multi-line-import-test.ts | 37 ++++ tests/unit/single-import-test.ts | 33 ++++ .../ember-cached-decorator-polyfill/index.js | 44 ----- yarn.lock | 64 +++++- 10 files changed, 228 insertions(+), 284 deletions(-) create mode 100644 addon/index.js create mode 100644 lib/replace-imports-preprocessor.js delete mode 100644 lib/transpile-modules.js create mode 100644 tests/unit/followed-import-test.ts create mode 100644 tests/unit/multi-line-import-test.ts create mode 100644 tests/unit/single-import-test.ts delete mode 100644 vendor/ember-cached-decorator-polyfill/index.js diff --git a/addon/index.js b/addon/index.js new file mode 100644 index 0000000..da1660f --- /dev/null +++ b/addon/index.js @@ -0,0 +1,36 @@ +import { createCache, getValue } from '@glimmer/tracking/primitives/cache'; +import { assert } from '@ember/debug'; + +export function cached(...args) { + const [target, key, descriptor] = args; + + // Error on `@cached()`, `@cached(...args)`, and `@cached propName = value;` + assert( + 'You attempted to use @cached(), which is not necessary nor supported. Remove the parentheses and you will be good to go!', + target !== undefined + ); + assert( + `You attempted to use @cached on with ${ + args.length > 1 ? 'arguments' : 'an argument' + } ( @cached(${args + .map(d => `'${d}'`) + .join( + ', ' + )}), which is not supported. Dependencies are automatically tracked, so you can just use ${'`@cached`'}`, + typeof target === 'object' && + typeof key === 'string' && + typeof descriptor === 'object' && + args.length === 3 + ); + assert( + `The @cached decorator must be applied to getters. '${key}' is not a getter.`, + typeof descriptor.get == 'function' + ); + + const caches = new WeakMap(); + const getter = descriptor.get; + descriptor.get = function () { + if (!caches.has(this)) caches.set(this, createCache(getter.bind(this))); + return getValue(caches.get(this)); + }; +} diff --git a/index.js b/index.js index d5a9399..e371440 100644 --- a/index.js +++ b/index.js @@ -1,62 +1,15 @@ 'use strict'; -const { resolve } = require('path'); +const ReplaceImportsPreprocessor = require('./lib/replace-imports-preprocessor'); module.exports = { name: require('./package').name, - included() { - this._super.included.apply(this, arguments); - this._ensureThisImport(); - - this.import('vendor/ember-cached-decorator-polyfill/index.js'); - this.patchEmberModulesAPIPolyfill(); - }, - - treeForVendor(tree) { - const babel = this.addons.find(a => a.name === 'ember-cli-babel'); - - return babel.transpileTree(tree, { - babel: this.options.babel, - - 'ember-cli-babel': { - compileModules: false - } - }); - }, - - _ensureThisImport() { - if (!this.import) { - this._findHost = function findHostShim() { - let current = this; - let app; - do { - app = current.app || app; - // eslint-disable-next-line no-cond-assign - } while (current.parent.parent && (current = current.parent)); - return app; - }; - this.import = function importShim(asset, options) { - const app = this._findHost(); - app.import(asset, options); - }; + setupPreprocessorRegistry(type, registry) { + if (type !== 'parent') { + return; } - }, - - patchEmberModulesAPIPolyfill() { - const babel = this.parent.findOwnAddonByName - ? this.parent.findOwnAddonByName('ember-cli-babel') // parent is an addon - : this.parent.findAddonByName('ember-cli-babel'); // parent is an app - - if (babel.__CachedDecoratorPolyfillApplied) return; - babel.__CachedDecoratorPolyfillApplied = true; - - const { _getEmberModulesAPIPolyfill } = babel; - babel._getEmberModulesAPIPolyfill = function (...args) { - const plugins = _getEmberModulesAPIPolyfill.apply(this, args); - if (!plugins) return; - return [[resolve(__dirname, './lib/transpile-modules.js')], ...plugins]; - }; + registry.add('js', new ReplaceImportsPreprocessor()); } }; diff --git a/lib/replace-imports-preprocessor.js b/lib/replace-imports-preprocessor.js new file mode 100644 index 0000000..fc1df03 --- /dev/null +++ b/lib/replace-imports-preprocessor.js @@ -0,0 +1,23 @@ +const BroccoliReplace = require('broccoli-replace'); + +module.exports = class ReplaceImportsPreprocessor { + name = 'ember-cached-decorator-polyfill-preprocessor'; + + toTree(tree) { + return new BroccoliReplace(tree, { + files: ['**/*.js', '**/*.ts'], + patterns: [ + { + match: /import\s+{[\s(tracked,)]*cached[\s(,tracked)]*}\s*from\s*['"]@glimmer\/tracking['"]/m, + replacement: str => { + console.log({ str }); + return str.includes('tracked') + ? `import { tracked } from '@glimmer/tracking'; +import { cached } from 'ember-cached-decorator-polyfill';` + : `import { cached } from 'ember-cached-decorator-polyfill';`; + } + } + ] + }); + } +}; diff --git a/lib/transpile-modules.js b/lib/transpile-modules.js deleted file mode 100644 index 03aaf81..0000000 --- a/lib/transpile-modules.js +++ /dev/null @@ -1,182 +0,0 @@ -'use strict'; - -const path = require('path'); - -/** - * Based on `babel-plugin-ember-modules-api-polyfill`. - * @see https://github.com/ember-cli/babel-plugin-ember-modules-api-polyfill/blob/master/src/index.js - */ -module.exports = function (babel) { - const t = babel.types; - - const MODULE = '@glimmer/tracking'; - const IMPORT = 'cached'; - const GLOBAL = 'Ember._cached'; - const MEMBER_EXPRESSION = t.MemberExpression( - t.identifier('Ember'), - t.identifier('_cached') - ); - - const TSTypesRequiringModification = [ - 'TSAsExpression', - 'TSTypeAssertion', - 'TSNonNullExpression' - ]; - const isTypescriptNode = node => - node.type.startsWith('TS') && - !TSTypesRequiringModification.includes(node.type); - - return { - name: 'ember-cache-decorator-polyfill', - visitor: { - ImportDeclaration(path) { - let node = path.node; - let declarations = []; - let removals = []; - let specifiers = path.get('specifiers'); - let importPath = node.source.value; - - // Only walk specifiers if this is a module we have a mapping for - if (importPath === MODULE) { - // Iterate all the specifiers and attempt to locate their mapping - specifiers.forEach(specifierPath => { - let specifier = specifierPath.node; - let importName; - - // imported is the name of the module being imported, e.g. import foo from bar - const imported = specifier.imported; - - // local is the name of the module in the current scope, this is usually the same - // as the imported value, unless the module is aliased - const local = specifier.local; - - // We only care about these 2 specifiers - if ( - specifier.type !== 'ImportDefaultSpecifier' && - specifier.type !== 'ImportSpecifier' - ) { - if (specifier.type === 'ImportNamespaceSpecifier') { - throw new Error( - `Using \`import * as ${specifier.local.name} from '${importPath}'\` is not supported.` - ); - } - return; - } - - // Determine the import name, either default or named - if (specifier.type === 'ImportDefaultSpecifier') { - importName = 'default'; - } else { - importName = imported.name; - } - - if (importName !== IMPORT) return; - - removals.push(specifierPath); - - if ( - path.scope.bindings[local.name].referencePaths.find( - rp => rp.parent.type === 'ExportSpecifier' - ) - ) { - // not safe to use path.scope.rename directly - declarations.push( - t.variableDeclaration('var', [ - t.variableDeclarator( - t.identifier(local.name), - t.identifier(GLOBAL) - ) - ]) - ); - } else { - // Replace the occurences of the imported name with the global name. - let binding = path.scope.getBinding(local.name); - - binding.referencePaths.forEach(referencePath => { - if (!isTypescriptNode(referencePath.parentPath)) { - referencePath.replaceWith(MEMBER_EXPRESSION); - } - }); - } - }); - } - - if (removals.length > 0) { - if (removals.length === node.specifiers.length) { - path.replaceWithMultiple(declarations); - } else { - removals.forEach(specifierPath => specifierPath.remove()); - path.insertAfter(declarations); - } - } - }, - - ExportNamedDeclaration(path) { - let node = path.node; - if (!node.source) { - return; - } - - let replacements = []; - let removals = []; - let specifiers = path.get('specifiers'); - let importPath = node.source.value; - - // Only walk specifiers if this is a module we have a mapping for - if (importPath === MODULE) { - // Iterate all the specifiers and attempt to locate their mapping - specifiers.forEach(specifierPath => { - let specifier = specifierPath.node; - - // exported is the name of the module being export, - // e.g. `foo` in `export { computed as foo } from '@ember/object';` - const exported = specifier.exported; - - // local is the original name of the module, this is usually the same - // as the exported value, unless the module is aliased - const local = specifier.local; - - // We only care about the ExportSpecifier - if (specifier.type !== 'ExportSpecifier') { - return; - } - - // Determine the import name, either default or named - let importName = local.name; - - if (importName !== IMPORT) return; - - removals.push(specifierPath); - - let declaration; - const globalAsIdentifier = t.identifier(GLOBAL); - if (exported.name === 'default') { - declaration = t.exportDefaultDeclaration(globalAsIdentifier); - } else { - // Replace the node with a new `var name = Ember.something` - declaration = t.exportNamedDeclaration( - t.variableDeclaration('var', [ - t.variableDeclarator(exported, globalAsIdentifier) - ]), - [], - null - ); - } - replacements.push(declaration); - }); - } - - if (removals.length > 0 && removals.length === node.specifiers.length) { - path.replaceWithMultiple(replacements); - } else if (replacements.length > 0) { - removals.forEach(specifierPath => specifierPath.remove()); - path.insertAfter(replacements); - } - } - } - }; -}; - -// Provide the path to the package's base directory for caching with broccoli -// Ref: https://github.com/babel/broccoli-babel-transpiler#caching -module.exports.baseDir = () => path.resolve(__dirname, '..'); diff --git a/package.json b/package.json index ad728ad..648822b 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,14 @@ "test:ember-compatibility": "ember try:each" }, "dependencies": { + "@glimmer/tracking": "^1.0.4", + "broccoli-replace": "^2.0.2", "ember-cache-primitive-polyfill": "^1.0.1", "ember-cli-babel": "^7.21.0" }, "devDependencies": { "@ember/optional-features": "^2.0.0", "@glimmer/component": "^1.0.1", - "@glimmer/tracking": "^1.0.0", "@types/ember": "^3.16.0", "@types/ember-qunit": "^3.4.9", "@types/ember-resolver": "^5.0.9", diff --git a/tests/unit/followed-import-test.ts b/tests/unit/followed-import-test.ts new file mode 100644 index 0000000..97132aa --- /dev/null +++ b/tests/unit/followed-import-test.ts @@ -0,0 +1,33 @@ +import { module, test } from 'qunit'; +import { cached, tracked } from '@glimmer/tracking'; + +module('Unit | Import | followed import', function () { + test('it works', function (assert) { + class Person { + @tracked firstName = 'Jen'; + lastName = 'Weber'; + + @cached + get fullName() { + const fullName = `${this.firstName} ${this.lastName}`; + assert.step(fullName); + return fullName; + } + } + + const person = new Person(); + assert.verifySteps([], 'getter is not called after class initialization'); + + assert.strictEqual(person.fullName, 'Jen Weber'); + assert.verifySteps( + ['Jen Weber'], + 'getter was called after property access' + ); + + assert.strictEqual(person.fullName, 'Jen Weber'); + assert.verifySteps( + [], + 'getter was not called again after repeated property access' + ); + }); +}); diff --git a/tests/unit/multi-line-import-test.ts b/tests/unit/multi-line-import-test.ts new file mode 100644 index 0000000..22e1422 --- /dev/null +++ b/tests/unit/multi-line-import-test.ts @@ -0,0 +1,37 @@ +import { module, test } from 'qunit'; +// prettier-ignore +import { + cached, + tracked + } from "@glimmer/tracking"; + +module('Unit | Import | multi-line import', function () { + test('it works', function (assert) { + class Person { + @tracked firstName = 'Jen'; + lastName = 'Weber'; + + @cached + get fullName() { + const fullName = `${this.firstName} ${this.lastName}`; + assert.step(fullName); + return fullName; + } + } + + const person = new Person(); + assert.verifySteps([], 'getter is not called after class initialization'); + + assert.strictEqual(person.fullName, 'Jen Weber'); + assert.verifySteps( + ['Jen Weber'], + 'getter was called after property access' + ); + + assert.strictEqual(person.fullName, 'Jen Weber'); + assert.verifySteps( + [], + 'getter was not called again after repeated property access' + ); + }); +}); diff --git a/tests/unit/single-import-test.ts b/tests/unit/single-import-test.ts new file mode 100644 index 0000000..fd6a29d --- /dev/null +++ b/tests/unit/single-import-test.ts @@ -0,0 +1,33 @@ +import { module, test } from 'qunit'; +import { cached } from '@glimmer/tracking'; + +module('Unit | Import | single import', function () { + test('it works', function (assert) { + class Person { + firstName = 'Jen'; + lastName = 'Weber'; + + @cached + get fullName() { + const fullName = `${this.firstName} ${this.lastName}`; + assert.step(fullName); + return fullName; + } + } + + const person = new Person(); + assert.verifySteps([], 'getter is not called after class initialization'); + + assert.strictEqual(person.fullName, 'Jen Weber'); + assert.verifySteps( + ['Jen Weber'], + 'getter was called after property access' + ); + + assert.strictEqual(person.fullName, 'Jen Weber'); + assert.verifySteps( + [], + 'getter was not called again after repeated property access' + ); + }); +}); diff --git a/vendor/ember-cached-decorator-polyfill/index.js b/vendor/ember-cached-decorator-polyfill/index.js deleted file mode 100644 index 3b07ec1..0000000 --- a/vendor/ember-cached-decorator-polyfill/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import Ember from 'ember'; -import { assert } from '@ember/debug'; - -(() => { - 'use strict'; - - const { _createCache: createCache, _cacheGetValue: getValue } = Ember; - - function cached(...args) { - const [target, key, descriptor] = args; - - // Error on `@cached()`, `@cached(...args)`, and `@cached propName = value;` - assert( - 'You attempted to use @cached(), which is not necessary nor supported. Remove the parentheses and you will be good to go!', - target !== undefined - ); - assert( - `You attempted to use @cached on with ${ - args.length > 1 ? 'arguments' : 'an argument' - } ( @cached(${args - .map(d => `'${d}'`) - .join( - ', ' - )}), which is not supported. Dependencies are automatically tracked, so you can just use ${'`@cached`'}`, - typeof target === 'object' && - typeof key === 'string' && - typeof descriptor === 'object' && - args.length === 3 - ); - assert( - `The @cached decorator must be applied to getters. '${key}' is not a getter.`, - typeof descriptor.get == 'function' - ); - - const caches = new WeakMap(); - const getter = descriptor.get; - descriptor.get = function () { - if (!caches.has(this)) caches.set(this, createCache(getter.bind(this))); - return getValue(caches.get(this)); - }; - } - - Ember._cached = cached; -})(); diff --git a/yarn.lock b/yarn.lock index 531d97a..47828f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1048,10 +1048,10 @@ handlebars "^4.7.4" simple-html-tokenizer "^0.5.9" -"@glimmer/tracking@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@glimmer/tracking/-/tracking-1.0.1.tgz#77d8b16e9e0be2cc8e4a00b77259fdc208802840" - integrity sha512-Jt8rn4/6S6CObILaotYUT0lFWNPq1xJubXdU/9p6zxWtMrBXYJalVvI5hFYrCh/hG8Z/aLoERGnb2YRWtBSyzw== +"@glimmer/tracking@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@glimmer/tracking/-/tracking-1.0.4.tgz#f1bc1412fe5e2236d0f8d502994a8f88af1bbb21" + integrity sha512-F+oT8I55ba2puSGIzInmVrv/8QA2PcK1VD+GWgFMhF6WC97D+uZX7BFg+a3s/2N4FVBq5KHE+QxZzgazM151Yw== dependencies: "@glimmer/env" "^0.1.7" "@glimmer/validator" "^0.44.0" @@ -2005,6 +2005,17 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +applause@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/applause/-/applause-2.0.4.tgz#0b771418fdd1bb326a225ad031d41ba0b20cd0f4" + integrity sha512-wFhNjSoflbAEgelX3psyKSXV2iQFjuYW31DEhcCOD/bQ98VdfltLclK4p1mI6E58Qp4Q7+5RCbBdr+Nc9b5QhA== + dependencies: + lodash "^4.17.21" + optional-require "^1.0.2" + optionalDependencies: + cson-parser "^4.0.8" + js-yaml "^4.0.0" + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -2025,6 +2036,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -3230,7 +3246,7 @@ broccoli-file-creator@^2.1.1: broccoli-plugin "^1.1.0" mkdirp "^0.5.1" -broccoli-filter@^1.2.2, broccoli-filter@^1.2.3: +broccoli-filter@^1.2.2, broccoli-filter@^1.2.3, broccoli-filter@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/broccoli-filter/-/broccoli-filter-1.3.0.tgz#71e3a8e32a17f309e12261919c5b1006d6766de6" integrity sha512-VXJXw7eBfG82CFxaBDjYmyN7V72D4In2zwLVQJd/h3mBfF3CMdRTsv2L20lmRTtCv1sAHcB+LgMso90e/KYiLw== @@ -3493,6 +3509,15 @@ broccoli-plugin@^4.0.1, broccoli-plugin@^4.0.2, broccoli-plugin@^4.0.3: rimraf "^3.0.0" symlink-or-copy "^1.3.0" +broccoli-replace@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/broccoli-replace/-/broccoli-replace-2.0.2.tgz#eb40046bb124a7f651486aece947ce28bbec30b1" + integrity sha512-1e8uyGUo8HqiKKB4oWz5nUX1rlLSRgShLxczuwSXJlmGljVWerDGF0oW5VshGAuKKYkAoDsI3Cc0TKEgo4SWTg== + dependencies: + applause "^2.0.0" + broccoli-filter "^1.3.0" + minimatch "^3.0.4" + broccoli-slow-trees@^3.0.1, broccoli-slow-trees@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/broccoli-slow-trees/-/broccoli-slow-trees-3.1.0.tgz#8e48903f59e061bf1213963733b9e61dec2ee5d7" @@ -4156,6 +4181,11 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +coffeescript@1.12.7: + version "1.12.7" + resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" + integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -4534,6 +4564,13 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +cson-parser@^4.0.8: + version "4.0.9" + resolved "https://registry.yarnpkg.com/cson-parser/-/cson-parser-4.0.9.tgz#eef0cf77edd057f97861ef800300c8239224eedb" + integrity sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag== + dependencies: + coffeescript "1.12.7" + cssom@0.3.x, cssom@^0.3.4: version "0.3.8" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" @@ -7926,6 +7963,13 @@ js-yaml@^3.13.1, js-yaml@^3.2.5, js-yaml@^3.2.7: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -8452,6 +8496,11 @@ lodash@4.17.20, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17. resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -9294,6 +9343,11 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" +optional-require@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/optional-require/-/optional-require-1.0.3.tgz#275b8e9df1dc6a17ad155369c2422a440f89cb07" + integrity sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA== + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"