| 1 | /** |
| 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | * |
| 4 | * This source code is licensed under the MIT license found in the |
| 5 | * LICENSE file in the root directory of this source tree. |
| 6 | */ |
| 7 | 'use strict'; |
| 8 | |
| 9 | // Most of our tests call jest.resetModules in a beforeEach and the |
| 10 | // re-require all the React modules. However, the JSX runtime is injected by |
| 11 | // the compiler, so those bindings don't get updated. This causes warnings |
| 12 | // logged by the JSX runtime to not have a component stack, because component |
| 13 | // stack relies on the the secret internals object that lives on the React |
| 14 | // module, which because of the resetModules call is longer the same one. |
| 15 | // |
| 16 | // To workaround this issue, use a transform that calls require() again before |
| 17 | // every JSX invocation. |
| 18 | // |
| 19 | // Longer term we should migrate all our tests away from using require() and |
| 20 | // resetModules, and use import syntax instead so this kind of thing doesn't |
| 21 | // happen. |
| 22 | |
| 23 | module.exports = function replaceJSXImportWithLazy(babel) { |
| 24 | const {types: t} = babel; |
| 25 | |
| 26 | function getInlineRequire(moduleName) { |
| 27 | return t.callExpression(t.identifier('require'), [ |
| 28 | t.stringLiteral(moduleName), |
| 29 | ]); |
| 30 | } |
| 31 | |
| 32 | return { |
| 33 | visitor: { |
| 34 | CallExpression: function (path, pass) { |
| 35 | let callee = path.node.callee; |
| 36 | if (callee.type === 'SequenceExpression') { |
| 37 | callee = callee.expressions[callee.expressions.length - 1]; |
| 38 | } |
| 39 | if (callee.type === 'Identifier') { |
| 40 | // Sometimes we seem to hit this before the imports are transformed |
| 41 | // into requires and so we hit this case. |
| 42 | switch (callee.name) { |
| 43 | case '_jsxDEV': |
| 44 | path.node.callee = t.memberExpression( |
| 45 | getInlineRequire('react/jsx-dev-runtime'), |
| 46 | t.identifier('jsxDEV') |
| 47 | ); |
| 48 | return; |
| 49 | case '_jsx': |
| 50 | path.node.callee = t.memberExpression( |
| 51 | getInlineRequire('react/jsx-runtime'), |
| 52 | t.identifier('jsx') |
| 53 | ); |
| 54 | return; |
| 55 | case '_jsxs': |
| 56 | path.node.callee = t.memberExpression( |
| 57 | getInlineRequire('react/jsx-runtime'), |
| 58 | t.identifier('jsxs') |
| 59 | ); |
| 60 | return; |
| 61 | } |
| 62 | return; |
| 63 | } |
| 64 | if (callee.type !== 'MemberExpression') { |
| 65 | return; |
| 66 | } |
| 67 | if (callee.property.type !== 'Identifier') { |
| 68 | // Needs to be jsx, jsxs, jsxDEV. |
| 69 | return; |
| 70 | } |
| 71 | if (callee.object.type !== 'Identifier') { |
| 72 | // Needs to be _reactJsxDevRuntime or _reactJsxRuntime. |
| 73 | return; |
| 74 | } |
| 75 | // Replace the cached identifier with a new require call. |
| 76 | // Relying on the identifier name is a little flaky. Should ideally pick |
| 77 | // this from the import. For some reason it sometimes has the react prefix |
| 78 | // and other times it doesn't. |
| 79 | switch (callee.object.name) { |
| 80 | case '_reactJsxDevRuntime': |
| 81 | case '_jsxDevRuntime': |
| 82 | callee.object = getInlineRequire('react/jsx-dev-runtime'); |
| 83 | return; |
| 84 | case '_reactJsxRuntime': |
| 85 | case '_jsxRuntime': |
| 86 | callee.object = getInlineRequire('react/jsx-runtime'); |
| 87 | return; |
| 88 | } |
| 89 | }, |
| 90 | }, |
| 91 | }; |
| 92 | }; |