From 6fd5b932ec6fd469bcbb4b3ac2fb9571935540d5 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 3 Jun 2020 16:38:47 -0400 Subject: [PATCH] Add new package `@wordpress/lazy-import` for lazily installed packages (#22684) * Packages: Add package "lazy-import" * isShallowEqual: Lazily install dependencies for benchmark * Lazy Import: Avoid abbreviated package variable * Lazy Import: Add requirement for NPM 6.9.0+ * Lazy Import: Throw error specific for disconnected * Lazy Import: Delete cache on second require attempt * Bump execa and semver to consistent latest version * Create Block: Bump execa dependency * Update package-lock.json file * Fix npm-package-arg dependency * Lazy Import: Avoid mutating require cache for final import * Update package-lock.json Co-authored-by: Grzegorz Ziolkowski --- docs/manifest.json | 6 + package-lock.json | 448 +++++++++++++++++-- package.json | 12 +- packages/create-block/package.json | 2 +- packages/eslint-plugin/configs/jsdoc.js | 1 + packages/is-shallow-equal/benchmark/index.js | 127 ++++-- packages/lazy-import/.npmrc | 1 + packages/lazy-import/CHANGELOG.md | 5 + packages/lazy-import/README.md | 81 ++++ packages/lazy-import/lib/index.js | 139 ++++++ packages/lazy-import/package.json | 32 ++ packages/lazy-import/tsconfig.json | 8 + tsconfig.json | 1 + 13 files changed, 765 insertions(+), 98 deletions(-) create mode 100644 packages/lazy-import/.npmrc create mode 100644 packages/lazy-import/CHANGELOG.md create mode 100644 packages/lazy-import/README.md create mode 100644 packages/lazy-import/lib/index.js create mode 100644 packages/lazy-import/package.json create mode 100644 packages/lazy-import/tsconfig.json diff --git a/docs/manifest.json b/docs/manifest.json index 409bd97a9b2ad..461212aa9e3b2 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1487,6 +1487,12 @@ "markdown_source": "../packages/keycodes/README.md", "parent": "packages" }, + { + "title": "@wordpress/lazy-import", + "slug": "packages-lazy-import", + "markdown_source": "../packages/lazy-import/README.md", + "parent": "packages" + }, { "title": "@wordpress/library-export-default-webpack-plugin", "slug": "packages-library-export-default-webpack-plugin", diff --git a/package-lock.json b/package-lock.json index 84aaded590c6f..06fb775b44e8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1660,6 +1660,18 @@ "pump": "^3.0.0" } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1669,6 +1681,12 @@ "end-of-stream": "^1.1.0", "once": "^1.3.1" } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true } } }, @@ -1704,6 +1722,18 @@ "pump": "^3.0.0" } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1755,6 +1785,24 @@ "yallist": "^3.0.2" } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -1903,6 +1951,18 @@ "validate-npm-package-license": "^3.0.1" } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -3650,6 +3710,26 @@ "semver": "^6.2.0" }, "dependencies": { + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", @@ -3695,6 +3775,26 @@ "semver": "^6.2.0" }, "dependencies": { + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", @@ -4194,6 +4294,26 @@ } } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -4288,6 +4408,26 @@ "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -4787,6 +4927,26 @@ "figgy-pudding": "^3.5.1", "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2" + }, + "dependencies": { + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "@lerna/npm-install": { @@ -4819,12 +4979,30 @@ "pify": "^3.0.0" } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "write-json-file": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz", @@ -4872,11 +5050,29 @@ "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true } } }, @@ -5012,6 +5208,18 @@ } } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -5028,6 +5236,12 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -5081,6 +5295,26 @@ "semver": "^6.2.0" }, "dependencies": { + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -5338,6 +5572,26 @@ "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", @@ -6649,6 +6903,12 @@ "util-deprecate": "^1.0.2" }, "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "shallow-equal": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", @@ -7479,6 +7739,12 @@ "ajv-keywords": "^3.4.1" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -8189,6 +8455,12 @@ "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", "dev": true }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -8424,6 +8696,12 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, @@ -9219,6 +9497,12 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-vbt5fb0y1svMhu++1lwtKmZL76d0uPChFlw7kEzyUmTwfmpHRcFb8i0R8ElT69q/L+QLgK2hgECivIAvaEDwag==", + "dev": true + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -9319,6 +9603,15 @@ "@types/node": "*" } }, + "@types/semver": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz", + "integrity": "sha512-TbB0A8ACUWZt3Y6bQPstW9QNbhNeebdgLX4T/ZfkrswAfUzRiXrgd9seol+X379Wa589Pu4UEx9Uok0D4RjRCQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -10150,7 +10443,7 @@ "chalk": "^4.0.0", "check-node-version": "^3.1.1", "commander": "^4.1.0", - "execa": "^4.0.0", + "execa": "^4.0.2", "fast-glob": "^2.2.7", "inquirer": "^7.1.0", "lodash": "^4.17.15", @@ -10597,6 +10890,15 @@ "lodash": "^4.17.15" } }, + "@wordpress/lazy-import": { + "version": "file:packages/lazy-import", + "dev": true, + "requires": { + "execa": "^4.0.2", + "npm-package-arg": "^8.0.1", + "semver": "^7.3.2" + } + }, "@wordpress/library-export-default-webpack-plugin": { "version": "file:packages/library-export-default-webpack-plugin", "dev": true, @@ -12847,6 +13149,14 @@ "@babel/types": "^7.4.0", "istanbul-lib-coverage": "^2.0.5", "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "load-json-file": { @@ -15385,6 +15695,11 @@ "read-pkg": "^3.0.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -15492,6 +15807,12 @@ "read-pkg": "^3.0.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -19479,9 +19800,9 @@ "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==" }, "execa": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", - "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", + "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -19496,9 +19817,9 @@ }, "dependencies": { "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -21396,6 +21717,11 @@ "read-pkg": "^3.0.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -21490,6 +21816,12 @@ "read-pkg": "^3.0.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -22945,6 +23277,18 @@ "validate-npm-package-name": "^3.0.0" }, "dependencies": { + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -23486,15 +23830,6 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, "is-expression": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", @@ -23641,12 +23976,6 @@ "isobject": "^3.0.1" } }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, "is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -29476,6 +29805,13 @@ "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", "requires": { "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } } }, "make-fetch-happen": { @@ -32371,21 +32707,38 @@ } }, "npm-package-arg": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", - "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.0.1.tgz", + "integrity": "sha512-/h5Fm6a/exByzFSTm7jAyHbgOqErl9qSNJDQF32Si/ZzgwT2TERVxRxn3Jurw1wflgyVVAxnFR4fRHPM7y1ClQ==", "dev": true, "requires": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", + "hosted-git-info": "^3.0.2", + "semver": "^7.0.0", "validate-npm-package-name": "^3.0.0" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "hosted-git-info": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", + "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true } } @@ -32892,6 +33245,18 @@ "semver": "^5.4.1" }, "dependencies": { + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -38690,9 +39055,10 @@ "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" }, "semver": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true }, "semver-compare": { "version": "1.0.0", @@ -38850,18 +39216,6 @@ "kind-of": "^6.0.2" } }, - "shallow-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.0.0.tgz", - "integrity": "sha1-UI0YOLPeWQq4dXsBGyXkMJAJRfc=", - "dev": true - }, - "shallow-equals": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-equals/-/shallow-equals-1.0.0.tgz", - "integrity": "sha1-JLdL8cY0wR7Uxxgqbfb7MA3OQ5A=", - "dev": true - }, "shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", diff --git a/package.json b/package.json index 9735e950a1cbb..2f2a08748da50 100644 --- a/package.json +++ b/package.json @@ -92,10 +92,12 @@ "@types/eslint": "6.8.0", "@types/estree": "0.0.44", "@types/lodash": "4.14.149", + "@types/npm-package-arg": "6.1.0", "@types/prettier": "1.19.0", "@types/qs": "6.9.1", "@types/react-dom": "16.9.5", "@types/requestidlecallback": "0.3.1", + "@types/semver": "7.2.0", "@types/sprintf-js": "1.1.2", "@types/uuid": "7.0.2", "@types/webpack": "4.41.16", @@ -116,6 +118,7 @@ "@wordpress/jest-console": "file:packages/jest-console", "@wordpress/jest-preset-default": "file:packages/jest-preset-default", "@wordpress/jest-puppeteer-axe": "file:packages/jest-puppeteer-axe", + "@wordpress/lazy-import": "file:packages/lazy-import", "@wordpress/library-export-default-webpack-plugin": "file:packages/library-export-default-webpack-plugin", "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "@wordpress/postcss-plugins-preset": "file:packages/postcss-plugins-preset", @@ -142,13 +145,11 @@ "enzyme": "3.11.0", "eslint-plugin-eslint-comments": "3.1.2", "eslint-plugin-import": "2.20.2", - "execa": "4.0.0", + "execa": "4.0.2", "fast-glob": "2.2.7", - "fbjs": "0.8.17", "glob": "7.1.2", "husky": "3.0.5", "inquirer": "7.1.0", - "is-equal-shallow": "0.1.3", "jest-emotion": "10.0.32", "jest-junit": "10.0.0", "jest-serializer-enzyme": "1.0.0", @@ -175,10 +176,7 @@ "rimraf": "3.0.2", "rtlcss": "2.4.0", "sass-loader": "8.0.2", - "semver": "6.0.0", - "shallow-equal": "1.0.0", - "shallow-equals": "1.0.0", - "shallowequal": "1.1.0", + "semver": "7.3.2", "simple-git": "1.113.0", "source-map-loader": "0.2.4", "sprintf-js": "1.1.1", diff --git a/packages/create-block/package.json b/packages/create-block/package.json index c412ac17d0332..1113406587f62 100644 --- a/packages/create-block/package.json +++ b/packages/create-block/package.json @@ -33,7 +33,7 @@ "chalk": "^4.0.0", "check-node-version": "^3.1.1", "commander": "^4.1.0", - "execa": "^4.0.0", + "execa": "^4.0.2", "fast-glob": "^2.2.7", "inquirer": "^7.1.0", "lodash": "^4.17.15", diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js index e71bad9f05336..1b00f37a8ba9e 100644 --- a/packages/eslint-plugin/configs/jsdoc.js +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -64,6 +64,7 @@ const typescriptUtilityTypes = [ 'never', 'NodeJS', 'AsyncIterableIterator', + 'NodeRequire', ]; module.exports = { diff --git a/packages/is-shallow-equal/benchmark/index.js b/packages/is-shallow-equal/benchmark/index.js index 5779f87688eae..d734712d67fe9 100644 --- a/packages/is-shallow-equal/benchmark/index.js +++ b/packages/is-shallow-equal/benchmark/index.js @@ -1,7 +1,15 @@ 'use strict'; +/** + * External dependencies + */ const Benchmark = require( 'benchmark' ); +/** + * WordPress dependencies + */ +const lazyImport = require( '@wordpress/lazy-import' ); + const suite = new Benchmark.Suite(); const beforeObject = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 }; @@ -13,55 +21,88 @@ const afterArraySame = beforeArray; const afterArrayEqual = [ 1, 2, 3, 4, 5, 6, 7 ]; const afterArrayUnequal = [ 1, 2, 3, 4, 5, 'Unequal', 7 ]; -[ - [ - '@wordpress/is-shallow-equal (type specific)', - require( '..' ).isShallowEqualObjects, - require( '..' ).isShallowEqualArrays, - ], - [ '@wordpress/is-shallow-equal', require( '..' ) ], - [ 'shallowequal', require( 'shallowequal' ) ], - [ - 'shallow-equal (type specific)', - require( 'shallow-equal/objects' ), - require( 'shallow-equal/arrays' ), - ], - [ 'is-equal-shallow', require( 'is-equal-shallow' ) ], - [ 'shallow-equals', require( 'shallow-equals' ) ], - [ 'fbjs/lib/shallowEqual', require( 'fbjs/lib/shallowEqual' ) ], -].forEach( +Promise.all( [ + lazyImport( 'shallowequal@^1.1.0' ), + lazyImport( 'shallow-equal@^1.2.1' ), + lazyImport( 'is-equal-shallow@^0.1.3' ), + lazyImport( 'shallow-equals@^1.0.0' ), + new Promise( async ( resolve ) => { + try { + await lazyImport( 'fbjs@^1.0.0' ); + } catch ( error ) { + // The fjbs package throws an error when imported directly. Since + // lazyImport will automatically require the module for the resolved + // value, anticipate and disregard the error, as long as it's the + // expected error message. + if ( + 'The fbjs package should not be required without a full path.' !== + error.message + ) { + throw error; + } + } + + resolve( require( 'fbjs/lib/shallowEqual' ) ); + } ), +] ).then( ( [ - name, - isShallowEqualObjects, - isShallowEqualArrays = isShallowEqualObjects, + shallowequal, + shallowEqual, + isEqualShallow, + shallowEquals, + fbjsShallowEqual, ] ) => { - suite.add( name + ' (object, equal)', () => { - isShallowEqualObjects( beforeObject, afterObjectEqual ); - } ); + [ + [ + '@wordpress/is-shallow-equal (type specific)', + require( '..' ).isShallowEqualObjects, + require( '..' ).isShallowEqualArrays, + ], + [ '@wordpress/is-shallow-equal', require( '..' ) ], + [ 'shallowequal', shallowequal ], + [ + 'shallow-equal (type specific)', + shallowEqual.shallowEqualObjects, + shallowEqual.shallowEqualArrays, + ], + [ 'is-equal-shallow', isEqualShallow ], + [ 'shallow-equals', shallowEquals ], + [ 'fbjs/lib/shallowEqual', fbjsShallowEqual ], + ].forEach( + ( [ + name, + isShallowEqualObjects, + isShallowEqualArrays = isShallowEqualObjects, + ] ) => { + suite.add( name + ' (object, equal)', () => { + isShallowEqualObjects( beforeObject, afterObjectEqual ); + } ); - suite.add( name + ' (object, same)', () => { - isShallowEqualObjects( beforeObject, afterObjectSame ); - } ); + suite.add( name + ' (object, same)', () => { + isShallowEqualObjects( beforeObject, afterObjectSame ); + } ); - suite.add( name + ' (object, unequal)', () => { - isShallowEqualObjects( beforeObject, afterObjectUnequal ); - } ); + suite.add( name + ' (object, unequal)', () => { + isShallowEqualObjects( beforeObject, afterObjectUnequal ); + } ); - suite.add( name + ' (array, equal)', () => { - isShallowEqualArrays( beforeArray, afterArrayEqual ); - } ); + suite.add( name + ' (array, equal)', () => { + isShallowEqualArrays( beforeArray, afterArrayEqual ); + } ); - suite.add( name + ' (array, same)', () => { - isShallowEqualArrays( beforeArray, afterArraySame ); - } ); + suite.add( name + ' (array, same)', () => { + isShallowEqualArrays( beforeArray, afterArraySame ); + } ); - suite.add( name + ' (array, unequal)', () => { - isShallowEqualArrays( beforeArray, afterArrayUnequal ); - } ); + suite.add( name + ' (array, unequal)', () => { + isShallowEqualArrays( beforeArray, afterArrayUnequal ); + } ); + } + ); + + suite + // eslint-disable-next-line no-console + .on( 'cycle', ( event ) => console.log( event.target.toString() ) ) + .run( { async: true } ); } ); - -suite - // eslint-disable-next-line no-console - .on( 'cycle', ( event ) => console.log( event.target.toString() ) ) - .run( { async: true } ); diff --git a/packages/lazy-import/.npmrc b/packages/lazy-import/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/lazy-import/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/lazy-import/CHANGELOG.md b/packages/lazy-import/CHANGELOG.md new file mode 100644 index 0000000000000..988f95e47afed --- /dev/null +++ b/packages/lazy-import/CHANGELOG.md @@ -0,0 +1,5 @@ + + +## Unreleased + +- Initial release. diff --git a/packages/lazy-import/README.md b/packages/lazy-import/README.md new file mode 100644 index 0000000000000..03a6e14b78cc5 --- /dev/null +++ b/packages/lazy-import/README.md @@ -0,0 +1,81 @@ +# Lazy Import + +Import an NPM module, even if not installed locally or defined as a dependency of the project. Uses a locally installed package if available. Otherwise, the package will be downloaded dynamically on-demand. + +## Installation + +Install the module + +```bash +npm install @wordpress/lazy-import --save +``` + +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ + +## Requirements + +NPM 6.9.0 or newer is required, since it uses the [package aliases feature](https://github.com/npm/rfcs/blob/latest/implemented/0001-package-aliases.md) to maintain multiple versions of the same package. + +## Usage + +Usage is intended to mimic the behavior of the [dynamic `import` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports), receiving the name (and optional version specifier) of an NPM package and returning a promise which resolves to the required module. + +_**Note:** Currently, this alignment to `import` is superficial, and the module resolution still uses [CommonJS `require`](https://nodejs.org/docs/latest-v12.x/api/modules.html#modules_require_id), rather than the newer [ES Modules support](https://nodejs.org/docs/latest-v14.x/api/esm.html). Future versions of this package will likely support ES Modules, once an LTS release of Node.js including unflagged ES Modules support becomes available._ + +The string passed to `lazyImport` can be formatted exactly as you would provide to `npm install`, including an optional version specifier (including [version ranges](https://docs.npmjs.com/misc/semver#ranges)). If the version specifier is omitted, it will be treated as equivalent to `*`, using the version of a locally installed package if available, otherwise installing the latest available version. + +```js +const lazyImport = require( '@wordpress/lazy-import' ); + +lazyImport( 'is-equal-shallow@^0.1.3' ).then( ( isEqualShallow ) => { + console.log( isEqualShallow( { a: true, b: true }, { a: true, b: true } ) ); + // true +} ); +``` + +If you're using Node v14.3.0 or newer, you can also take advantage of [top-level await](https://v8.dev/features/top-level-await) to simplify top-level imports: + +```js +const lazyImport = require( '@wordpress/lazy-import' ); + +const isEqualShallow = await lazyImport( 'is-equal-shallow@^0.1.3' ); +console.log( isEqualShallow( { a: true, b: true }, { a: true, b: true } ) ); +// true +``` + +`lazyImport` optionally accepts a second argument, an options object: + +```js +const lazyImport = require( '@wordpress/lazy-import' ); + +function onInstall() { + console.log( 'Installing…' ); +} + +lazyImport( 'is-equal-shallow@^0.1.3', { onInstall } ).then( /* ... */ ); +``` + +Note that `lazyImport` can throw an error when offline and unable to install the dependency using NPM. You may want to anticipate this and provide remediation steps for a failed install, such as logging a warning messsage: + +```js +try { + await lazyImport( 'is-equal-shallow@^0.1.3' ); +} catch ( error ) { + if ( error.code === 'ENOTFOUND' ) { + console.log( 'Unable to connect to NPM registry!' ); + } +} +``` + +### Options + +#### `onInstall` + +- Type: `Function` +- Required: No + +Function to call if and when the module is being installed. Since installation can cause a delay in script execution, this can be useful to output logging information or display a spinner. + +An installation can be assumed to finish once the returned promise is resolved. + +

Code is Poetry.

diff --git a/packages/lazy-import/lib/index.js b/packages/lazy-import/lib/index.js new file mode 100644 index 0000000000000..bb68fd7936bce --- /dev/null +++ b/packages/lazy-import/lib/index.js @@ -0,0 +1,139 @@ +/** + * External dependencies + */ +const npmPackageArg = require( 'npm-package-arg' ); +const semver = require( 'semver' ); +const execa = require( 'execa' ); +const { join } = require( 'path' ); +const { createHash } = require( 'crypto' ); + +/** + * @typedef WPLazyImportOptions + * + * @property {()=>void} onInstall Callback to invoke when install starts. + */ + +/** + * Returns an md5 hash of the given string. + * + * @param {string} text Text for which to generate hash. + * + * @return {string} md5 hash of string. + */ +function md5( text ) { + return createHash( 'md5' ).update( text ).digest( 'hex' ); +} + +/** + * Given an arg string as would be passed to `npm install`, returns a string to + * use as the alias name of the package to install locally if needed. + * + * @param {string} arg A string that you might pass to `npm install`. + * + * @return {string} Module to use as local package alias. + */ +function getLocalModuleName( arg ) { + return '@wordpress/lazy-import.' + md5( arg ); +} + +/** + * Installs npm package by arg name at the given prefix path. Creates the prefix + * path if it doesn't yet exist. + * + * @param {string} arg Package install arg (name and version specification). + * @param {string} alias Alias to use for local module name. + * + * @return {Promise} Promise resolving once package is installed. + */ +async function install( arg, alias ) { + await execa( 'npm', [ 'install', '--no-save', alias + '@npm:' + arg ] ); +} + +/** + * Given an arg string as would be passed to `npm install`, requires the package + * corresponding to the name and specifier parsed from the arg. If the package + * is not installed (or not installed at that version), the package is installed + * to a temporary directory at `node_modules/.wp-lazy` relative to where the + * current working directory. + * + * @param {string} arg A string that you might pass + * to `npm install`. + * @param {Partial} [options] Optional options object. + * + * @return {Promise} Promise resolving to required module. + */ +async function lazyImport( arg, options = {} ) { + const { rawSpec, name } = npmPackageArg( arg ); + + if ( ! name ) { + throw new TypeError( + `Unable to parse package name from \`${ arg }\`.` + ); + } + + const localModule = getLocalModuleName( arg ); + + // Try first from the temporary install path, since the second attempt will + // need to verify both availability and version. Version isn't necessary to + // account for in this first attempt. + try { + return require( localModule ); + } catch ( error ) { + if ( error.code !== 'MODULE_NOT_FOUND' ) { + throw error; + } + } + + try { + const resolved = require( name ); + + const { version } = require( join( name, 'package.json' ) ); + if ( semver.satisfies( version, rawSpec ) ) { + // Only return with the resolved module if the version is valid per + // the parsed arg. Otherwise, fall through to install stage. + return resolved; + } + } catch ( error ) { + if ( error.code !== 'MODULE_NOT_FOUND' ) { + throw error; + } + } + + // If this point is reached, the module cannot be found and must be + // installed. + if ( options.onInstall ) { + options.onInstall(); + } + + try { + await install( arg, localModule ); + } catch ( error ) { + // Format connectivity error to one which can be easily evaluated. + if ( + error.stderr && + error.stderr.startsWith( 'npm ERR! code ENOTFOUND' ) + ) { + error = new Error( 'Unable to connect to NPM registry' ); + error.code = 'ENOTFOUND'; + } + + throw error; + } + + // Note: For those familiar with the internals of module importing, the + // second attempt here at requiring the same package as earlier may stand + // out as being susceptible to conflicting with module caching. + // + // https://nodejs.org/api/modules.html#modules_require_cache + // + // Testing appears to indicate that module results are not cached if it + // fails to resolve, which is what we can assume would have happened in the + // first attempt to require the module. Thus, it's not required to reset any + // values from `require.cache`. + // + // See: https://github.com/WordPress/gutenberg/pull/22684#discussion_r434583858 + + return require( localModule ); +} + +module.exports = lazyImport; diff --git a/packages/lazy-import/package.json b/packages/lazy-import/package.json new file mode 100644 index 0000000000000..127f77d5861fb --- /dev/null +++ b/packages/lazy-import/package.json @@ -0,0 +1,32 @@ +{ + "name": "@wordpress/lazy-import", + "version": "1.0.0-prerelease", + "description": "Lazily import a module, installing it automatically if missing.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/lazy-import/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/lazy-import" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "engines": { + "npm": ">=6.9.0" + }, + "main": "lib/index.js", + "types": "build-types", + "dependencies": { + "execa": "^4.0.2", + "npm-package-arg": "^8.0.1", + "semver": "^7.3.2" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/lazy-import/tsconfig.json b/packages/lazy-import/tsconfig.json new file mode 100644 index 0000000000000..426ab13d0aa8f --- /dev/null +++ b/packages/lazy-import/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "lib", + "declarationDir": "build-types" + }, + "include": [ "lib/**/*" ] +} diff --git a/tsconfig.json b/tsconfig.json index 74595c259b008..05ca9f0742154 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ { "path": "packages/i18n" }, { "path": "packages/icons" }, { "path": "packages/is-shallow-equal" }, + { "path": "packages/lazy-import" }, { "path": "packages/prettier-config" }, { "path": "packages/primitives" }, { "path": "packages/priority-queue" },