diff --git a/package-lock.json b/package-lock.json index 34bbfbd798..8c1d91db9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2088,21 +2088,6 @@ "object.assign": "^4.1.0" } }, - "node_modules/babel-plugin-module-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz", - "integrity": "sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA==", - "dependencies": { - "find-babel-config": "^1.2.0", - "glob": "^7.1.6", - "pkg-up": "^3.1.0", - "reselect": "^4.0.0", - "resolve": "^1.13.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3785,34 +3770,6 @@ "node": ">=8" } }, - "node_modules/find-babel-config": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", - "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", - "dependencies": { - "json5": "^0.5.1", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/find-babel-config/node_modules/json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/find-babel-config/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, "node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", @@ -4422,6 +4379,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -5541,6 +5499,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, "engines": { "node": ">=6" } @@ -5624,7 +5583,8 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", @@ -5697,73 +5657,6 @@ "node": ">=8" } }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, "node_modules/playwright": { "resolved": "packages/playwright", "link": true @@ -6179,15 +6072,11 @@ "node": ">=0.10.0" } }, - "node_modules/reselect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz", - "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==" - }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, "dependencies": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" @@ -7512,7 +7401,6 @@ "@babel/plugin-syntax-optional-catch-binding": "7.8.3", "@babel/plugin-transform-modules-commonjs": "7.16.8", "@babel/preset-typescript": "7.16.7", - "babel-plugin-module-resolver": "4.1.0", "colors": "1.4.0", "commander": "8.3.0", "debug": "4.3.3", @@ -8328,7 +8216,6 @@ "@babel/plugin-syntax-optional-catch-binding": "7.8.3", "@babel/plugin-transform-modules-commonjs": "7.16.8", "@babel/preset-typescript": "7.16.7", - "babel-plugin-module-resolver": "4.1.0", "colors": "1.4.0", "commander": "8.3.0", "debug": "4.3.3", @@ -9173,18 +9060,6 @@ "object.assign": "^4.1.0" } }, - "babel-plugin-module-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz", - "integrity": "sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA==", - "requires": { - "find-babel-config": "^1.2.0", - "glob": "^7.1.6", - "pkg-up": "^3.1.0", - "reselect": "^4.0.0", - "resolve": "^1.13.1" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -10452,27 +10327,6 @@ "to-regex-range": "^5.0.1" } }, - "find-babel-config": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", - "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", - "requires": { - "json5": "^0.5.1", - "path-exists": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - } - } - }, "find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", @@ -10931,6 +10785,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, "requires": { "has": "^1.0.3" } @@ -11767,7 +11622,8 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "param-case": { "version": "3.0.4", @@ -11830,7 +11686,8 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "path-type": { "version": "4.0.0", @@ -11884,54 +11741,6 @@ "find-up": "^4.0.0" } }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - } - } - }, "playwright": { "version": "file:packages/playwright", "requires": { @@ -12276,15 +12085,11 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "reselect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz", - "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==" - }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, "requires": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" diff --git a/packages/playwright-core/src/cli/cli.ts b/packages/playwright-core/src/cli/cli.ts index 2b18e669fb..893deeb9ce 100755 --- a/packages/playwright-core/src/cli/cli.ts +++ b/packages/playwright-core/src/cli/cli.ts @@ -15,10 +15,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { fork } from 'child_process'; +import url from 'url'; if (process.env.PW_EXPERIMENTAL_TS_ESM) { - const NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ` --experimental-loader=${require.resolve('@playwright/test/lib/experimentalLoader')}`; + const NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ` --experimental-loader=${url.pathToFileURL(require.resolve('@playwright/test/lib/experimentalLoader'))}`; const innerProcess = fork(require.resolve('./innerCli'), process.argv.slice(2), { env: { ...process.env, NODE_OPTIONS } }); diff --git a/packages/playwright-test/package.json b/packages/playwright-test/package.json index 505b12cd79..57032b9491 100644 --- a/packages/playwright-test/package.json +++ b/packages/playwright-test/package.json @@ -45,7 +45,6 @@ "@babel/plugin-syntax-optional-catch-binding": "7.8.3", "@babel/plugin-transform-modules-commonjs": "7.16.8", "@babel/preset-typescript": "7.16.7", - "babel-plugin-module-resolver": "4.1.0", "colors": "1.4.0", "commander": "8.3.0", "debug": "4.3.3", diff --git a/packages/playwright-test/src/experimentalLoader.ts b/packages/playwright-test/src/experimentalLoader.ts index d309fb55a2..c2d47e8606 100644 --- a/packages/playwright-test/src/experimentalLoader.ts +++ b/packages/playwright-test/src/experimentalLoader.ts @@ -15,28 +15,27 @@ */ import fs from 'fs'; -import { transformHook } from './transform'; +import url from 'url'; +import { transformHook, resolveHook } from './transform'; async function resolve(specifier: string, context: { parentURL: string }, defaultResolve: any) { - if (specifier.endsWith('.js') || specifier.endsWith('.ts') || specifier.endsWith('.mjs')) - return defaultResolve(specifier, context, defaultResolve); - let url = new URL(specifier, context.parentURL).toString(); - url = url.substring('file://'.length); - for (const extension of ['.ts', '.js', '.tsx', '.jsx']) { - if (fs.existsSync(url + extension)) - return defaultResolve(specifier + extension, context, defaultResolve); + if (context.parentURL && context.parentURL.startsWith('file://')) { + const filename = url.fileURLToPath(context.parentURL); + const resolved = resolveHook(filename, specifier); + if (resolved !== undefined) + specifier = url.pathToFileURL(resolved).toString(); } return defaultResolve(specifier, context, defaultResolve); } -async function load(url: string, context: any, defaultLoad: any) { - if (url.endsWith('.ts') || url.endsWith('.tsx')) { - const filename = url.substring('file://'.length); +async function load(moduleUrl: string, context: any, defaultLoad: any) { + if (moduleUrl.startsWith('file://') && (moduleUrl.endsWith('.ts') || moduleUrl.endsWith('.tsx'))) { + const filename = url.fileURLToPath(moduleUrl); const code = fs.readFileSync(filename, 'utf-8'); const source = transformHook(code, filename, true); return { format: 'module', source }; } - return defaultLoad(url, context, defaultLoad); + return defaultLoad(moduleUrl, context, defaultLoad); } module.exports = { resolve, load }; diff --git a/packages/playwright-test/src/transform.ts b/packages/playwright-test/src/transform.ts index 7f4b8e507a..e95b3fe995 100644 --- a/packages/playwright-test/src/transform.ts +++ b/packages/playwright-test/src/transform.ts @@ -23,16 +23,15 @@ import * as sourceMapSupport from 'source-map-support'; import * as url from 'url'; import type { Location } from './types'; import { tsConfigLoader, TsConfigLoaderResult } from './third_party/tsconfig-loader'; +import Module from 'module'; -const version = 6; +const version = 7; const cacheDir = process.env.PWTEST_CACHE_DIR || path.join(os.tmpdir(), 'playwright-transform-cache'); const sourceMaps: Map = new Map(); type ParsedTsConfigData = { - absoluteBaseUrl: string, - singlePath: { [key: string]: string }, - hash: string, - alias: { [key: string]: string | ((s: string[]) => string) }, + absoluteBaseUrl: string; + paths: { key: string, values: string[] }[]; }; const cachedTSConfigs = new Map(); @@ -55,9 +54,8 @@ sourceMapSupport.install({ } }); -function calculateCachePath(tsconfigData: ParsedTsConfigData | undefined, content: string, filePath: string): string { +function calculateCachePath(content: string, filePath: string): string { const hash = crypto.createHash('sha1') - .update(tsconfigData?.hash || '') .update(process.env.PW_TEST_SOURCE_TRANSFORM || '') .update(process.env.PW_EXPERIMENTAL_TS_ESM ? 'esm' : 'no_esm') .update(content) @@ -69,43 +67,12 @@ function calculateCachePath(tsconfigData: ParsedTsConfigData | undefined, conten } function validateTsConfig(tsconfig: TsConfigLoaderResult): ParsedTsConfigData | undefined { - if (!tsconfig.tsConfigPath || !tsconfig.paths || !tsconfig.baseUrl) + if (!tsconfig.tsConfigPath || !tsconfig.baseUrl) return; - - const paths = tsconfig.paths; - // Path that only contains "*", ".", "/" and "\" is too ambiguous. - const ambiguousPath = Object.keys(paths).find(key => key.match(/^[*./\\]+$/)); - if (ambiguousPath) - return; - const multiplePath = Object.keys(paths).find(key => paths[key].length > 1); - if (multiplePath) - return; - // Only leave a single path mapping. - const singlePath = Object.fromEntries(Object.entries(paths).map(([key, values]) => ([key, values[0]]))); // Make 'baseUrl' absolute, because it is relative to the tsconfig.json, not to cwd. const absoluteBaseUrl = path.resolve(path.dirname(tsconfig.tsConfigPath), tsconfig.baseUrl); - const hash = JSON.stringify({ absoluteBaseUrl, singlePath }); - - const alias: ParsedTsConfigData['alias'] = {}; - for (const [key, value] of Object.entries(singlePath)) { - const regexKey = '^' + key.replace('*', '.*'); - alias[regexKey] = ([name]) => { - let relative: string; - if (key.endsWith('/*')) - relative = value.substring(0, value.length - 1) + name.substring(key.length - 1); - else - relative = value; - relative = relative.replace(/\//g, path.sep); - return path.resolve(absoluteBaseUrl, relative); - }; - } - - return { - absoluteBaseUrl, - singlePath, - hash, - alias, - }; + const paths = tsconfig.paths || { '*': ['*'] }; + return { absoluteBaseUrl, paths: Object.entries(paths).map(([key, values]) => ({ key, values })) }; } function loadAndValidateTsconfigForFile(file: string): ParsedTsConfigData | undefined { @@ -123,6 +90,35 @@ function loadAndValidateTsconfigForFile(file: string): ParsedTsConfigData | unde const pathSeparator = process.platform === 'win32' ? ';' : ':'; const scriptPreprocessor = process.env.PW_TEST_SOURCE_TRANSFORM ? require(process.env.PW_TEST_SOURCE_TRANSFORM) : undefined; +const builtins = new Set(Module.builtinModules); + +export function resolveHook(filename: string, specifier: string): string | undefined { + if (builtins.has(specifier)) + return; + const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx'); + if (!isTypeScript) + return; + const tsconfig = loadAndValidateTsconfigForFile(filename); + if (!tsconfig) + return; + for (const { key, values } of tsconfig.paths) { + const keyHasStar = key[key.length - 1] === '*'; + const matches = specifier.startsWith(keyHasStar ? key.substring(0, key.length - 1) : key); + if (!matches) + continue; + for (const value of values) { + const valueHasStar = value[value.length - 1] === '*'; + let candidate = valueHasStar ? value.substring(0, value.length - 1) : value; + if (valueHasStar && keyHasStar) + candidate += specifier.substring(key.length - 1); + candidate = path.resolve(tsconfig.absoluteBaseUrl, candidate.replace(/\//g, path.sep)); + for (const ext of ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx']) { + if (fs.existsSync(candidate + ext)) + return candidate; + } + } + } +} export function transformHook(code: string, filename: string, isModule = false): string { if (isComponentImport(filename)) @@ -138,8 +134,7 @@ export function transformHook(code: string, filename: string, isModule = false): if (!isTypeScript && !hasPreprocessor) return code; - const tsconfigData = isTypeScript ? loadAndValidateTsconfigForFile(filename) : undefined; - const cachePath = calculateCachePath(tsconfigData, code, filename); + const cachePath = calculateCachePath(code, filename); const codePath = cachePath + '.js'; const sourceMapPath = cachePath + '.map'; sourceMaps.set(filename, sourceMapPath); @@ -166,16 +161,6 @@ export function transformHook(code: string, filename: string, isModule = false): [require.resolve('@babel/plugin-proposal-export-namespace-from')] ); - if (tsconfigData) { - plugins.push([require.resolve('babel-plugin-module-resolver'), { - root: ['./'], - alias: tsconfigData.alias, - // Silences warning 'Could not resovle ...' that we trigger because we resolve - // into 'foo/bar', and not 'foo/bar.ts'. - loglevel: 'silent', - }]); - } - if (!isModule) { plugins.push([require.resolve('@babel/plugin-transform-modules-commonjs')]); plugins.push([require.resolve('@babel/plugin-proposal-dynamic-import')]); @@ -217,12 +202,30 @@ export function transformHook(code: string, filename: string, isModule = false): } export function installTransform(): () => void { - const exts = ['.ts', '.tsx']; + let reverted = false; + const originalResolveFilename = (Module as any)._resolveFilename; + function resolveFilename(this: any, specifier: string, parent: Module, ...rest: any[]) { + if (!reverted && parent) { + const resolved = resolveHook(parent.filename, specifier); + if (resolved !== undefined) + specifier = resolved; + } + return originalResolveFilename.call(this, specifier, parent, ...rest); + } + (Module as any)._resolveFilename = resolveFilename; + + const exts = ['.ts', '.tsx']; // When script preprocessor is engaged, we transpile JS as well. if (scriptPreprocessor) exts.push('.js', '.mjs'); - return pirates.addHook((code: string, filename: string) => transformHook(code, filename), { exts }); + const revertPirates = pirates.addHook((code: string, filename: string) => transformHook(code, filename), { exts }); + + return () => { + reverted = true; + (Module as any)._resolveFilename = originalResolveFilename; + revertPirates(); + }; } export function wrapFunctionWithLocation(func: (location: Location, ...args: A) => R): (...args: A) => R { diff --git a/tests/playwright-test/resolver.spec.ts b/tests/playwright-test/resolver.spec.ts index 236689a2ef..2fcf79a1ac 100644 --- a/tests/playwright-test/resolver.spec.ts +++ b/tests/playwright-test/resolver.spec.ts @@ -34,7 +34,7 @@ test('should respect path resolver', async ({ runInlineTest }) => { "paths": { "util/*": ["./foo/bar/util/*"], "util2/*": ["./foo/bar/util/*"], - "util3": ["./foo/bar/util/b"], + "util3": ["./does-not-exist", "./foo/bar/util/b"], }, }, }`, @@ -136,72 +136,32 @@ test('should respect baseurl', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); }); -test('should ignore for baseurl w/o paths', async ({ runInlineTest }) => { +test('should respect baseurl w/o paths', async ({ runInlineTest }) => { const result = await runInlineTest({ - 'tsconfig.json': `{ + 'foo/bar/util/b.ts': ` + export const foo = 42; + `, + 'dir2/tsconfig.json': `{ "compilerOptions": { "target": "ES2019", "module": "commonjs", "lib": ["esnext", "dom", "DOM.Iterable"], - "baseUrl": "./" + "baseUrl": "..", }, }`, - 'a.test.ts': ` - import { foo } from 'foo/b'; - `, - 'foo/b.ts': ` - export const foo = 1; + 'dir2/inner.spec.ts': ` + // This import should pick up ../foo/bar/util/b due to baseUrl. + import { foo } from 'foo/bar/util/b'; + const { test } = pwt; + test('test', ({}, testInfo) => { + expect(foo).toBe(42); + }); `, }); - expect(result.exitCode).toBe(1); - expect(result.output).toContain(`Cannot find module 'foo/b'`); -}); - -test('should ignore tsconfig with ambiguous paths', async ({ runInlineTest }) => { - const result = await runInlineTest({ - 'tsconfig.json': `{ - "compilerOptions": { - "target": "ES2019", - "module": "commonjs", - "lib": ["esnext", "dom", "DOM.Iterable"], - "baseUrl": "./", - "paths": { "*/*": ["*/*"] } - }, - }`, - 'a.test.ts': ` - import { foo } from 'foo/b'; - `, - 'foo/b.ts': ` - export const foo = 1; - `, - }); - - expect(result.exitCode).toBe(1); - expect(result.output).toContain(`Cannot find module 'foo/b'`); -}); - -test('should ignore tsconfig with multi-value paths', async ({ runInlineTest }) => { - const result = await runInlineTest({ - 'tsconfig.json': `{ - "compilerOptions": { - "target": "ES2019", - "module": "commonjs", - "lib": ["esnext", "dom", "DOM.Iterable"], - "baseUrl": "./", - "paths": { "foo/*": ["./foo/*", "./bar/*"] } - }, - }`, - 'a.test.ts': ` - import { foo } from 'foo/b'; - `, - 'foo/b.ts': ` - export const foo = 1; - `, - }); - - expect(result.exitCode).toBe(1); - expect(result.output).toContain(`Cannot find module 'foo/b'`); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + expect(result.output).not.toContain(`Could not`); }); test('should respect path resolver in experimental mode', async ({ runInlineTest }) => {