diff --git a/README.md b/README.md index 6eab0f4..c7b929c 100644 --- a/README.md +++ b/README.md @@ -1064,6 +1064,7 @@ Args: pass the name through as-is. The 2nd parameter is a full module descriptor. - `opts.resolverOptions`: An `object` passed to the resolvers. Used to configure the lifetime, injection mode and more of the loaded modules. +- `opts.esModules`: Loads modules using Node's native ES modules. This is only supported on Node 14.0+ and should only be used if you're using the [Native Node ES modules](https://nodejs.org/api/esm.html) Example: diff --git a/package-lock.json b/package-lock.json index f21663a..15ebd4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1541,6 +1541,29 @@ } } }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, "@rollup/pluginutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", @@ -1625,11 +1648,18 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "requires": { + "@types/node": "*" + } + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dev": true, "requires": { "@types/minimatch": "*", "@types/node": "*" @@ -1681,14 +1711,12 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, "@types/node": { "version": "14.14.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.2.tgz", - "integrity": "sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg==", - "dev": true + "integrity": "sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -1876,6 +1904,11 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -2151,7 +2184,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -2395,6 +2427,11 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2673,6 +2710,14 @@ "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + } + }, "doctrine": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", @@ -3088,6 +3133,19 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3100,6 +3158,14 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastq": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", + "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", + "requires": { + "reusify": "^1.0.4" + } + }, "fb-watchman": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", @@ -3122,7 +3188,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -3193,7 +3258,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -3279,17 +3343,39 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "growly": { "version": "1.3.0", @@ -3524,6 +3610,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + }, "import-fresh": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", @@ -3698,6 +3789,11 @@ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3710,6 +3806,14 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -3725,8 +3829,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-obj": { "version": "1.0.1", @@ -5397,7 +5500,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, "requires": { "graceful-fs": "^4.1.6" } @@ -5902,11 +6004,15 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.0.5" @@ -6373,8 +6479,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "performance-now": { "version": "2.1.0", @@ -6385,8 +6490,7 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pirates": { "version": "4.0.1", @@ -6747,6 +6851,11 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -6778,6 +6887,25 @@ "rollup-pluginutils": "^2.8.1" } }, + "rollup-plugin-copy": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.3.0.tgz", + "integrity": "sha512-euDjCUSBXZa06nqnwCNADbkAcYDfzwowfZQkto9K/TFhiH+QG7I4PUsEMwM9tDgomGWJc//z7KLW8t+tZwxADA==", + "requires": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==" + } + } + }, "rollup-plugin-node-resolve": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", @@ -6846,6 +6974,11 @@ "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", "dev": true }, + "run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==" + }, "rxjs": { "version": "6.6.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", @@ -7118,8 +7251,7 @@ "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "slice-ansi": { "version": "3.0.0", @@ -7651,7 +7783,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -7919,8 +8050,7 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "unset-value": { "version": "1.0.0", diff --git a/package.json b/package.json index 1dbcf16..36b397a 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,8 @@ }, "dependencies": { "camel-case": "^4.1.1", - "glob": "^7.1.6" + "glob": "^7.1.6", + "rollup-plugin-copy": "^3.3.0" }, "lint-staged": { "*.ts": [ @@ -116,7 +117,8 @@ "coveragePathIgnorePatterns": [ "/node_modules/", "__tests__", - "lib" + "lib", + "src/load-module-native.js" ], "moduleFileExtensions": [ "ts", diff --git a/rollup.config.js b/rollup.config.js index 976f40a..770be25 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,6 +2,8 @@ import typescript from 'rollup-plugin-typescript2' import replace from 'rollup-plugin-replace' import commonjs from 'rollup-plugin-commonjs' import resolve from 'rollup-plugin-node-resolve' +import copy from 'rollup-plugin-copy' + const comment = '/* removed in browser build */' const ignoredWarnings = ['UNUSED_EXTERNAL_IMPORT'] @@ -23,7 +25,7 @@ export default [ // Build 1: ES6 modules for Node. { input: 'src/awilix.ts', - external: ['glob', 'path', 'util', 'camel-case'], + external: ['glob', 'path', 'util', 'camel-case', './load-module-native.js'], treeshake: { moduleSideEffects: 'no-external' }, onwarn, output: [ @@ -32,7 +34,13 @@ export default [ format: 'es', }, ], - plugins: [typescript(tsOpts)], + plugins: [ + // Copy the native module loader + copy({ + targets: [{ src: 'src/load-module-native.js', dest: 'lib' }] + }), + typescript(tsOpts) + ], }, // Build 2: ES modules for browser builds. { diff --git a/src/__tests__/container.test.ts b/src/__tests__/container.test.ts index 1f5b81f..21b03bd 100644 --- a/src/__tests__/container.test.ts +++ b/src/__tests__/container.test.ts @@ -456,6 +456,12 @@ describe('container', function () { it('returns the container', function () { expect(container.loadModules([])).toBe(container) }) + + it('returns a Promise of the container if used with esModules true', async function () { + expect(await container.loadModules([], { esModules: true })).toBe( + container + ) + }) }) describe('setting a property on the cradle', function () { diff --git a/src/__tests__/load-modules.test.ts b/src/__tests__/load-modules.test.ts index 2640e1b..5f4de2d 100644 --- a/src/__tests__/load-modules.test.ts +++ b/src/__tests__/load-modules.test.ts @@ -38,6 +38,33 @@ describe('loadModules', function () { expect(container.resolve('someClass')).toBeInstanceOf(SomeClass) }) + it('registers loaded modules async when using native modules', async function () { + const container = createContainer() + + class SomeClass {} + + const modules: any = { + 'nope.js': undefined, + 'standard.js': jest.fn(() => 42), + 'default.js': { default: jest.fn(() => 1337) }, + 'someClass.js': SomeClass, + } + + const moduleLookupResult = lookupResultFor(modules) + const deps = { + container, + listModules: jest.fn(() => moduleLookupResult), + require: jest.fn(async (path) => modules[path]) + } + + const result = await loadModules(deps, 'anything', { esModules: true }) + expect(result).toEqual({ loadedModules: moduleLookupResult }) + expect(Object.keys(container.registrations).length).toBe(3) + expect(container.resolve('standard')).toBe(42) + expect(container.resolve('default')).toBe(1337) + expect(container.resolve('someClass')).toBeInstanceOf(SomeClass) + }) + it('registers non-default export modules containing RESOLVER token with the container', function () { const container = createContainer() diff --git a/src/container.ts b/src/container.ts index 7c1c40d..7aa4a75 100644 --- a/src/container.ts +++ b/src/container.ts @@ -3,6 +3,7 @@ import { GlobWithOptions, listModules } from './list-modules' import { LoadModulesOptions, loadModules as realLoadModules, + LoadModulesResult, } from './load-modules' import { Resolver, @@ -16,6 +17,7 @@ import { last, nameValueToObject, isClass } from './utils' import { InjectionMode, InjectionModeType } from './injection-mode' import { Lifetime } from './lifetime' import { AwilixResolutionError, AwilixTypeError } from './errors' +import { importModule } from './load-module-native.js' /** * The container returned from createContainer has some methods and properties. @@ -56,10 +58,11 @@ export interface AwilixContainer { * * @see src/load-modules.ts documentation. */ - loadModules( + loadModules( globPatterns: Array, - options?: LoadModulesOptions - ): this + options?: LoadModulesOptions + ): ESM extends false ? this : Promise + /** * Adds a single registration that using a pre-constructed resolver. */ @@ -564,6 +567,10 @@ export function createContainer( return resolver.resolve(container) } + function loadModules( + globPatterns: Array, + opts: LoadModulesOptions + ): ESM extends false ? AwilixContainer : Promise /** * Binds `lib/loadModules` to this container, and provides * real implementations of it's dependencies. @@ -573,10 +580,10 @@ export function createContainer( * * @see lib/loadModules.js documentation. */ - function loadModules( + function loadModules( globPatterns: Array, - opts: LoadModulesOptions - ) { + opts: LoadModulesOptions + ): Promise | AwilixContainer { const _loadModulesDeps = { require: options!.require || @@ -586,8 +593,15 @@ export function createContainer( listModules, container, } - realLoadModules(_loadModulesDeps, globPatterns, opts) - return container + if (opts?.esModules) { + _loadModulesDeps.require = importModule + return (realLoadModules(_loadModulesDeps, globPatterns, opts) as Promise< + LoadModulesResult + >).then(() => container) + } else { + realLoadModules(_loadModulesDeps, globPatterns, opts) + return container + } } /** diff --git a/src/list-modules.ts b/src/list-modules.ts index 99fa99b..8832ff6 100644 --- a/src/list-modules.ts +++ b/src/list-modules.ts @@ -23,6 +23,12 @@ export interface ModuleDescriptor { path: string opts: any } +/** + * Metadata of the module as well as the loaded module itself. + */ +export interface LoadedModuleDescriptor extends ModuleDescriptor { + value: unknown +} /** * A glob pattern with associated registration options. diff --git a/src/load-module-native.d.ts b/src/load-module-native.d.ts new file mode 100644 index 0000000..5731399 --- /dev/null +++ b/src/load-module-native.d.ts @@ -0,0 +1 @@ +export function importModule(path: string): Promise diff --git a/src/load-module-native.js b/src/load-module-native.js new file mode 100644 index 0000000..b0a512b --- /dev/null +++ b/src/load-module-native.js @@ -0,0 +1,5 @@ +// This is kept in a separate .js file to prevent TypeScript re-writing the import() statement to a require() statement +function importModule(path) { + return import(path) +} +module.exports = { importModule } diff --git a/src/load-modules.ts b/src/load-modules.ts index 12ad60f..715691f 100644 --- a/src/load-modules.ts +++ b/src/load-modules.ts @@ -1,4 +1,9 @@ -import { ModuleDescriptor, GlobWithOptions, listModules } from './list-modules' +import { + ModuleDescriptor, + LoadedModuleDescriptor, + GlobWithOptions, + listModules, +} from './list-modules' import { Lifetime } from './lifetime' import { RESOLVER, @@ -15,10 +20,11 @@ import { camelCase } from 'camel-case' * The options when invoking loadModules(). * @interface LoadModulesOptions */ -export interface LoadModulesOptions { +export interface LoadModulesOptions { cwd?: string formatName?: NameFormatter | BuiltInNameFormatters resolverOptions?: BuildResolverOptions + esModules?: ESM } /** @@ -46,13 +52,25 @@ export type NameFormatter = ( export interface LoadModulesDeps { listModules: typeof listModules container: AwilixContainer - require(path: string): any + require(path: string): any | Promise } const nameFormatters: Record = { camelCase: (s) => camelCase(s), } +/** + * The list of loaded modules + */ +export interface LoadModulesResult { + loadedModules: Array +} + +export function loadModules( + dependencies: LoadModulesDeps, + globPatterns: string | Array, + opts?: LoadModulesOptions +): ESM extends true ? Promise : LoadModulesResult /** * Given an array of glob strings, will call `require` * on them, and call their default exported function with the @@ -76,81 +94,133 @@ const nameFormatters: Record = { * @param {(string, ModuleDescriptor) => string} opts.formatName * Used to format the name the module is registered with in the container. * + * @param {boolean} opts.esModules + * Set to `true` to use Node's native ECMAScriptModules modules + * * @return {Object} * Returns an object describing the result. */ -export function loadModules( +export function loadModules( dependencies: LoadModulesDeps, globPatterns: string | Array, - opts?: LoadModulesOptions -) { + opts?: LoadModulesOptions +): Promise | LoadModulesResult { const container = dependencies.container opts = optsWithDefaults(opts, container) const modules = dependencies.listModules(globPatterns, opts) - const result = modules.map((m) => { - const items: Array<{ - name: string - path: string - opts: object - value: unknown - }> = [] + if (opts?.esModules) { + return loadEsModules(dependencies, container, modules, opts) + } else { + const result = modules.map((m) => { + const loaded = dependencies.require(m.path) + return parseLoadedModule(loaded, m) + }) + return registerModules(result, container, modules, opts) + } +} - const loaded = dependencies.require(m.path) +/** + * Loads the modules using native ES6 modules and the async import() + * @param {AwilixContainer} container + * @param {ModuleDescriptor[]} modules + * @param {LoadModulesOptions} opts + */ +async function loadEsModules( + dependencies: LoadModulesDeps, + container: AwilixContainer, + modules: ModuleDescriptor[], + opts: LoadModulesOptions +): Promise { + const importPromises = [] + for (const m of modules) { + importPromises.push(dependencies.require(m.path)) + } + const imports = await Promise.all(importPromises) + const result = [] + for (let i = 0; i < modules.length; i++) { + result.push(parseLoadedModule(imports[i], modules[i])) + } + return registerModules(result, container, modules, opts) +} - // Meh, it happens. - if (!loaded) { - return items - } +/** + * Parses the module which has been required + * + * @param {any} loaded + * @param {ModuleDescriptor} m + */ +function parseLoadedModule( + loaded: any, + m: ModuleDescriptor +): Array { + const items: Array = [] + // Meh, it happens. + if (!loaded) { + return items + } - if (isFunction(loaded)) { - // for module.exports = ... - items.push({ - name: m.name, - path: m.path, - value: loaded, - opts: m.opts, - }) + if (isFunction(loaded)) { + // for module.exports = ... + items.push({ + name: m.name, + path: m.path, + value: loaded, + opts: m.opts, + }) - return items + return items + } + + if (loaded.default && isFunction(loaded.default)) { + // ES6 default export + items.push({ + name: m.name, + path: m.path, + value: loaded.default, + opts: m.opts, + }) + } + + // loop through non-default exports, but require the RESOLVER property set for + // it to be a valid service module export. + for (const key of Object.keys(loaded)) { + if (key === 'default') { + // default case handled separately due to its different name (file name) + continue } - if (loaded.default && isFunction(loaded.default)) { - // ES6 default export + if (isFunction(loaded[key]) && RESOLVER in loaded[key]) { items.push({ - name: m.name, + name: key, path: m.path, - value: loaded.default, + value: loaded[key], opts: m.opts, }) } + } - // loop through non-default exports, but require the RESOLVER property set for - // it to be a valid service module export. - for (const key of Object.keys(loaded)) { - if (key === 'default') { - // default case handled separately due to its different name (file name) - continue - } - - if (isFunction(loaded[key]) && RESOLVER in loaded[key]) { - items.push({ - name: key, - path: m.path, - value: loaded[key], - opts: m.opts, - }) - } - } - - return items - }) + return items +} - result +/** + * Registers the modules + * + * @param {ModuleDescriptorVal[][]} modulesToRegister + * @param {AwilixContainer} container + * @param {ModuleDescriptor[]} modules + * @param {LoadModulesOptions} opts + */ +function registerModules( + modulesToRegister: LoadedModuleDescriptor[][], + container: AwilixContainer, + modules: ModuleDescriptor[], + opts: LoadModulesOptions +): LoadModulesResult { + modulesToRegister .reduce((acc, cur) => acc.concat(cur), []) .filter((x) => x) .forEach(registerDescriptor.bind(null, container, opts)) - return { loadedModules: modules, } @@ -159,10 +229,10 @@ export function loadModules( /** * Returns a new options object with defaults applied. */ -function optsWithDefaults( - opts: Partial | undefined, +function optsWithDefaults( + opts: Partial> | undefined, container: AwilixContainer -): LoadModulesOptions { +): LoadModulesOptions { return { // Does a somewhat-deep merge on the registration options. resolverOptions: { @@ -180,9 +250,9 @@ function optsWithDefaults( * @param {LoadModulesOptions} opts * @param {ModuleDescriptor} moduleDescriptor */ -function registerDescriptor( +function registerDescriptor( container: AwilixContainer, - opts: LoadModulesOptions, + opts: LoadModulesOptions, moduleDescriptor: ModuleDescriptor & { value: any } ) { const inlineConfig = moduleDescriptor.value[RESOLVER]