transform.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.requireOrImport = requireOrImport;
  6. exports.resolveHook = resolveHook;
  7. exports.setTransformConfig = setTransformConfig;
  8. exports.setTransformData = setTransformData;
  9. exports.shouldTransform = shouldTransform;
  10. exports.transformConfig = transformConfig;
  11. exports.transformHook = transformHook;
  12. exports.wrapFunctionWithLocation = wrapFunctionWithLocation;
  13. var _crypto = _interopRequireDefault(require("crypto"));
  14. var _path = _interopRequireDefault(require("path"));
  15. var _url = _interopRequireDefault(require("url"));
  16. var _utilsBundle = require("../utilsBundle");
  17. var _tsconfigLoader = require("../third_party/tsconfig-loader");
  18. var _module = _interopRequireDefault(require("module"));
  19. var _util = require("../util");
  20. var _compilationCache = require("./compilationCache");
  21. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  22. /**
  23. * Copyright (c) Microsoft Corporation.
  24. *
  25. * Licensed under the Apache License, Version 2.0 (the "License");
  26. * you may not use this file except in compliance with the License.
  27. * You may obtain a copy of the License at
  28. *
  29. * http://www.apache.org/licenses/LICENSE-2.0
  30. *
  31. * Unless required by applicable law or agreed to in writing, software
  32. * distributed under the License is distributed on an "AS IS" BASIS,
  33. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  34. * See the License for the specific language governing permissions and
  35. * limitations under the License.
  36. */
  37. const version = require('../../package.json').version;
  38. const cachedTSConfigs = new Map();
  39. let _transformConfig = {
  40. babelPlugins: [],
  41. external: []
  42. };
  43. let _externalMatcher = () => false;
  44. function setTransformConfig(config) {
  45. _transformConfig = config;
  46. _externalMatcher = (0, _util.createFileMatcher)(_transformConfig.external);
  47. }
  48. function transformConfig() {
  49. return _transformConfig;
  50. }
  51. function validateTsConfig(tsconfig) {
  52. var _tsconfig$baseUrl;
  53. if (!tsconfig.tsConfigPath) return;
  54. // Make 'baseUrl' absolute, because it is relative to the tsconfig.json, not to cwd.
  55. // When no explicit baseUrl is set, resolve paths relative to the tsconfig file.
  56. // See https://www.typescriptlang.org/tsconfig#paths
  57. const absoluteBaseUrl = _path.default.resolve(_path.default.dirname(tsconfig.tsConfigPath), (_tsconfig$baseUrl = tsconfig.baseUrl) !== null && _tsconfig$baseUrl !== void 0 ? _tsconfig$baseUrl : '.');
  58. // Only add the catch-all mapping when baseUrl is specified
  59. const pathsFallback = tsconfig.baseUrl ? [{
  60. key: '*',
  61. values: ['*']
  62. }] : [];
  63. return {
  64. allowJs: tsconfig.allowJs,
  65. absoluteBaseUrl,
  66. paths: Object.entries(tsconfig.paths || {}).map(([key, values]) => ({
  67. key,
  68. values
  69. })).concat(pathsFallback)
  70. };
  71. }
  72. function loadAndValidateTsconfigForFile(file) {
  73. const cwd = _path.default.dirname(file);
  74. if (!cachedTSConfigs.has(cwd)) {
  75. const loaded = (0, _tsconfigLoader.tsConfigLoader)({
  76. cwd
  77. });
  78. cachedTSConfigs.set(cwd, validateTsConfig(loaded));
  79. }
  80. return cachedTSConfigs.get(cwd);
  81. }
  82. const pathSeparator = process.platform === 'win32' ? ';' : ':';
  83. const builtins = new Set(_module.default.builtinModules);
  84. function resolveHook(filename, specifier) {
  85. if (specifier.startsWith('node:') || builtins.has(specifier)) return;
  86. if (!shouldTransform(filename)) return;
  87. if (isRelativeSpecifier(specifier)) return (0, _util.resolveImportSpecifierExtension)(_path.default.resolve(_path.default.dirname(filename), specifier));
  88. const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx');
  89. const tsconfig = loadAndValidateTsconfigForFile(filename);
  90. if (tsconfig && (isTypeScript || tsconfig.allowJs)) {
  91. let longestPrefixLength = -1;
  92. let pathMatchedByLongestPrefix;
  93. for (const {
  94. key,
  95. values
  96. } of tsconfig.paths) {
  97. let matchedPartOfSpecifier = specifier;
  98. const [keyPrefix, keySuffix] = key.split('*');
  99. if (key.includes('*')) {
  100. // * If pattern contains '*' then to match pattern "<prefix>*<suffix>" module name must start with the <prefix> and end with <suffix>.
  101. // * <MatchedStar> denotes part of the module name between <prefix> and <suffix>.
  102. // * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked.
  103. // https://github.com/microsoft/TypeScript/blob/f82d0cb3299c04093e3835bc7e29f5b40475f586/src/compiler/moduleNameResolver.ts#L1049
  104. if (keyPrefix) {
  105. if (!specifier.startsWith(keyPrefix)) continue;
  106. matchedPartOfSpecifier = matchedPartOfSpecifier.substring(keyPrefix.length, matchedPartOfSpecifier.length);
  107. }
  108. if (keySuffix) {
  109. if (!specifier.endsWith(keySuffix)) continue;
  110. matchedPartOfSpecifier = matchedPartOfSpecifier.substring(0, matchedPartOfSpecifier.length - keySuffix.length);
  111. }
  112. } else {
  113. if (specifier !== key) continue;
  114. matchedPartOfSpecifier = specifier;
  115. }
  116. if (keyPrefix.length <= longestPrefixLength) continue;
  117. for (const value of values) {
  118. let candidate = value;
  119. if (value.includes('*')) candidate = candidate.replace('*', matchedPartOfSpecifier);
  120. candidate = _path.default.resolve(tsconfig.absoluteBaseUrl, candidate.replace(/\//g, _path.default.sep));
  121. const existing = (0, _util.resolveImportSpecifierExtension)(candidate);
  122. if (existing) {
  123. longestPrefixLength = keyPrefix.length;
  124. pathMatchedByLongestPrefix = existing;
  125. }
  126. }
  127. }
  128. if (pathMatchedByLongestPrefix) return pathMatchedByLongestPrefix;
  129. }
  130. if (_path.default.isAbsolute(specifier)) {
  131. // Handle absolute file paths like `import '/path/to/file'`
  132. // Do not handle module imports like `import 'fs'`
  133. return (0, _util.resolveImportSpecifierExtension)(specifier);
  134. }
  135. }
  136. function shouldTransform(filename) {
  137. if (_externalMatcher(filename)) return false;
  138. return !(0, _compilationCache.belongsToNodeModules)(filename);
  139. }
  140. let transformData;
  141. function setTransformData(pluginName, value) {
  142. transformData.set(pluginName, value);
  143. }
  144. function transformHook(originalCode, filename, moduleUrl) {
  145. const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx') || filename.endsWith('.mts') || filename.endsWith('.cts');
  146. const hasPreprocessor = process.env.PW_TEST_SOURCE_TRANSFORM && process.env.PW_TEST_SOURCE_TRANSFORM_SCOPE && process.env.PW_TEST_SOURCE_TRANSFORM_SCOPE.split(pathSeparator).some(f => filename.startsWith(f));
  147. const pluginsPrologue = _transformConfig.babelPlugins;
  148. const pluginsEpilogue = hasPreprocessor ? [[process.env.PW_TEST_SOURCE_TRANSFORM]] : [];
  149. const hash = calculateHash(originalCode, filename, !!moduleUrl, pluginsPrologue, pluginsEpilogue);
  150. const {
  151. cachedCode,
  152. addToCache
  153. } = (0, _compilationCache.getFromCompilationCache)(filename, hash, moduleUrl);
  154. if (cachedCode !== undefined) return cachedCode;
  155. // We don't use any browserslist data, but babel checks it anyway.
  156. // Silence the annoying warning.
  157. process.env.BROWSERSLIST_IGNORE_OLD_DATA = 'true';
  158. const {
  159. babelTransform
  160. } = require('./babelBundle');
  161. transformData = new Map();
  162. const {
  163. code,
  164. map
  165. } = babelTransform(originalCode, filename, isTypeScript, !!moduleUrl, pluginsPrologue, pluginsEpilogue);
  166. if (code) addToCache(code, map, transformData);
  167. return code || '';
  168. }
  169. function calculateHash(content, filePath, isModule, pluginsPrologue, pluginsEpilogue) {
  170. const hash = _crypto.default.createHash('sha1').update(isModule ? 'esm' : 'no_esm').update(content).update(filePath).update(version).update(pluginsPrologue.map(p => p[0]).join(',')).update(pluginsEpilogue.map(p => p[0]).join(',')).digest('hex');
  171. return hash;
  172. }
  173. async function requireOrImport(file) {
  174. const revertBabelRequire = installTransform();
  175. const isModule = (0, _util.fileIsModule)(file);
  176. try {
  177. const esmImport = () => eval(`import(${JSON.stringify(_url.default.pathToFileURL(file))})`);
  178. if (isModule) return await esmImport();
  179. const result = require(file);
  180. const depsCollector = (0, _compilationCache.currentFileDepsCollector)();
  181. if (depsCollector) {
  182. const module = require.cache[file];
  183. if (module) collectCJSDependencies(module, depsCollector);
  184. }
  185. return result;
  186. } finally {
  187. revertBabelRequire();
  188. }
  189. }
  190. function installTransform() {
  191. (0, _compilationCache.installSourceMapSupportIfNeeded)();
  192. let reverted = false;
  193. const originalResolveFilename = _module.default._resolveFilename;
  194. function resolveFilename(specifier, parent, ...rest) {
  195. if (!reverted && parent) {
  196. const resolved = resolveHook(parent.filename, specifier);
  197. if (resolved !== undefined) specifier = resolved;
  198. }
  199. return originalResolveFilename.call(this, specifier, parent, ...rest);
  200. }
  201. _module.default._resolveFilename = resolveFilename;
  202. const revertPirates = _utilsBundle.pirates.addHook((code, filename) => {
  203. if (!shouldTransform(filename)) return code;
  204. return transformHook(code, filename);
  205. }, {
  206. exts: ['.ts', '.tsx', '.js', '.jsx', '.mjs']
  207. });
  208. return () => {
  209. reverted = true;
  210. _module.default._resolveFilename = originalResolveFilename;
  211. revertPirates();
  212. };
  213. }
  214. const collectCJSDependencies = (module, dependencies) => {
  215. module.children.forEach(child => {
  216. if (!(0, _compilationCache.belongsToNodeModules)(child.filename) && !dependencies.has(child.filename)) {
  217. dependencies.add(child.filename);
  218. collectCJSDependencies(child, dependencies);
  219. }
  220. });
  221. };
  222. function wrapFunctionWithLocation(func) {
  223. return (...args) => {
  224. const oldPrepareStackTrace = Error.prepareStackTrace;
  225. Error.prepareStackTrace = (error, stackFrames) => {
  226. const frame = _utilsBundle.sourceMapSupport.wrapCallSite(stackFrames[1]);
  227. const fileName = frame.getFileName();
  228. // Node error stacks for modules use file:// urls instead of paths.
  229. const file = fileName && fileName.startsWith('file://') ? _url.default.fileURLToPath(fileName) : fileName;
  230. return {
  231. file,
  232. line: frame.getLineNumber(),
  233. column: frame.getColumnNumber()
  234. };
  235. };
  236. const oldStackTraceLimit = Error.stackTraceLimit;
  237. Error.stackTraceLimit = 2;
  238. const obj = {};
  239. Error.captureStackTrace(obj);
  240. const location = obj.stack;
  241. Error.stackTraceLimit = oldStackTraceLimit;
  242. Error.prepareStackTrace = oldPrepareStackTrace;
  243. return func(location, ...args);
  244. };
  245. }
  246. function isRelativeSpecifier(specifier) {
  247. return specifier === '.' || specifier === '..' || specifier.startsWith('./') || specifier.startsWith('../');
  248. }